handle clock drift #18
This commit is contained in:
parent
c7738222f9
commit
28ece966b4
|
@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
Nothing unreleased.
|
Nothing unreleased.
|
||||||
|
|
||||||
|
## [4.2.1] - 2022-04-21
|
||||||
|
|
||||||
|
Handle clock drift. #18
|
||||||
|
|
||||||
## [4.2.0] - 2022-04-21
|
## [4.2.0] - 2022-04-21
|
||||||
|
|
||||||
Handle clock drift. #18
|
Handle clock drift. #18
|
||||||
|
@ -282,7 +286,8 @@ Project created as an alternative Java implementation of [ULID spec](https://git
|
||||||
- Added `LICENSE`
|
- Added `LICENSE`
|
||||||
- Added test cases
|
- Added test cases
|
||||||
|
|
||||||
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.0...HEAD
|
[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.1...HEAD
|
||||||
|
[4.2.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.0...ulid-creator-4.2.1
|
||||||
[4.2.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.2...ulid-creator-4.2.0
|
[4.2.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.2...ulid-creator-4.2.0
|
||||||
[4.1.2]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.1...ulid-creator-4.1.2
|
[4.1.2]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.1...ulid-creator-4.1.2
|
||||||
[4.1.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.0...ulid-creator-4.1.1
|
[4.1.1]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.1.0...ulid-creator-4.1.1
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2021 Fabio Lima
|
Copyright (c) 2020-2022 Fabio Lima
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -39,7 +39,7 @@ Add these lines to your `pom.xml`.
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.f4b6a3</groupId>
|
<groupId>com.github.f4b6a3</groupId>
|
||||||
<artifactId>ulid-creator</artifactId>
|
<artifactId>ulid-creator</artifactId>
|
||||||
<version>4.2.0</version>
|
<version>4.2.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
|
||||||
|
|
|
@ -47,7 +47,7 @@ public final class UlidFactory {
|
||||||
private final LongFunction<Ulid> ulidFunction;
|
private final LongFunction<Ulid> ulidFunction;
|
||||||
|
|
||||||
public UlidFactory() {
|
public UlidFactory() {
|
||||||
this(new UlidFunction(), null);
|
this(new UlidFunction(getRandomSupplier(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private UlidFactory(LongFunction<Ulid> ulidFunction) {
|
private UlidFactory(LongFunction<Ulid> ulidFunction) {
|
||||||
|
@ -67,7 +67,7 @@ public final class UlidFactory {
|
||||||
* @return {@link UlidFactory}
|
* @return {@link UlidFactory}
|
||||||
*/
|
*/
|
||||||
public static UlidFactory newInstance() {
|
public static UlidFactory newInstance() {
|
||||||
return new UlidFactory(new UlidFunction());
|
return newInstance(getRandomSupplier(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +77,7 @@ public final class UlidFactory {
|
||||||
* @return {@link UlidFactory}
|
* @return {@link UlidFactory}
|
||||||
*/
|
*/
|
||||||
public static UlidFactory newInstance(Random random) {
|
public static UlidFactory newInstance(Random random) {
|
||||||
return new UlidFactory(new UlidFunction(random));
|
return newInstance(getRandomSupplier(random));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,26 +92,13 @@ public final class UlidFactory {
|
||||||
return new UlidFactory(new UlidFunction(randomSupplier));
|
return new UlidFactory(new UlidFunction(randomSupplier));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new factory.
|
|
||||||
*
|
|
||||||
* The given random supplier must return an array of 10 bytes.
|
|
||||||
*
|
|
||||||
* @param randomSupplier a random supplier that returns 10 bytes
|
|
||||||
* @param clock a custom clock instance for tests
|
|
||||||
* @return {@link UlidFactory}
|
|
||||||
*/
|
|
||||||
protected static UlidFactory newInstance(Supplier<byte[]> randomSupplier, Clock clock) {
|
|
||||||
return new UlidFactory(new UlidFunction(randomSupplier), clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new monotonic factory.
|
* Returns a new monotonic factory.
|
||||||
*
|
*
|
||||||
* @return {@link UlidFactory}
|
* @return {@link UlidFactory}
|
||||||
*/
|
*/
|
||||||
public static UlidFactory newMonotonicInstance() {
|
public static UlidFactory newMonotonicInstance() {
|
||||||
return new UlidFactory(new MonotonicFunction());
|
return newMonotonicInstance(getRandomSupplier(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +108,7 @@ public final class UlidFactory {
|
||||||
* @return {@link UlidFactory}
|
* @return {@link UlidFactory}
|
||||||
*/
|
*/
|
||||||
public static UlidFactory newMonotonicInstance(Random random) {
|
public static UlidFactory newMonotonicInstance(Random random) {
|
||||||
return new UlidFactory(new MonotonicFunction(random));
|
return newMonotonicInstance(getRandomSupplier(random));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,14 +165,6 @@ public final class UlidFactory {
|
||||||
// it must return an array of 10 bytes
|
// it must return an array of 10 bytes
|
||||||
private Supplier<byte[]> randomSupplier;
|
private Supplier<byte[]> randomSupplier;
|
||||||
|
|
||||||
public UlidFunction() {
|
|
||||||
this(new SecureRandom());
|
|
||||||
}
|
|
||||||
|
|
||||||
public UlidFunction(Random random) {
|
|
||||||
this(getRandomSupplier(random));
|
|
||||||
}
|
|
||||||
|
|
||||||
public UlidFunction(Supplier<byte[]> randomSupplier) {
|
public UlidFunction(Supplier<byte[]> randomSupplier) {
|
||||||
this.randomSupplier = randomSupplier;
|
this.randomSupplier = randomSupplier;
|
||||||
}
|
}
|
||||||
|
@ -201,8 +180,8 @@ public final class UlidFactory {
|
||||||
*/
|
*/
|
||||||
protected static final class MonotonicFunction implements LongFunction<Ulid> {
|
protected static final class MonotonicFunction implements LongFunction<Ulid> {
|
||||||
|
|
||||||
private long lastTime = 0;
|
private long lastTime;
|
||||||
private Ulid lastUlid = null;
|
private Ulid lastUlid;
|
||||||
|
|
||||||
// Used to preserve monotonicity when the system clock is
|
// Used to preserve monotonicity when the system clock is
|
||||||
// adjusted by NTP after a small clock drift or when the
|
// adjusted by NTP after a small clock drift or when the
|
||||||
|
@ -212,16 +191,12 @@ public final class UlidFactory {
|
||||||
// it must return an array of 10 bytes
|
// it must return an array of 10 bytes
|
||||||
private Supplier<byte[]> randomSupplier;
|
private Supplier<byte[]> randomSupplier;
|
||||||
|
|
||||||
public MonotonicFunction() {
|
|
||||||
this(new SecureRandom());
|
|
||||||
}
|
|
||||||
|
|
||||||
public MonotonicFunction(Random random) {
|
|
||||||
this(getRandomSupplier(random));
|
|
||||||
}
|
|
||||||
|
|
||||||
public MonotonicFunction(Supplier<byte[]> randomSupplier) {
|
public MonotonicFunction(Supplier<byte[]> randomSupplier) {
|
||||||
this.randomSupplier = randomSupplier;
|
this.randomSupplier = randomSupplier;
|
||||||
|
|
||||||
|
// initialize internal state
|
||||||
|
this.lastTime = Clock.systemUTC().millis();
|
||||||
|
this.lastUlid = new Ulid(lastTime, randomSupplier.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -233,10 +208,10 @@ public final class UlidFactory {
|
||||||
if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) {
|
if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) {
|
||||||
this.lastUlid = lastUlid.increment();
|
this.lastUlid = lastUlid.increment();
|
||||||
} else {
|
} else {
|
||||||
|
this.lastTime = time;
|
||||||
this.lastUlid = new Ulid(time, this.randomSupplier.get());
|
this.lastUlid = new Ulid(time, this.randomSupplier.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastTime = lastUlid.getTime();
|
|
||||||
return new Ulid(this.lastUlid);
|
return new Ulid(this.lastUlid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,9 +223,10 @@ public final class UlidFactory {
|
||||||
* @return a random supplier that returns 10 bytes
|
* @return a random supplier that returns 10 bytes
|
||||||
*/
|
*/
|
||||||
protected static Supplier<byte[]> getRandomSupplier(Random random) {
|
protected static Supplier<byte[]> getRandomSupplier(Random random) {
|
||||||
|
Random entropy = random != null ? random : new SecureRandom();
|
||||||
return () -> {
|
return () -> {
|
||||||
byte[] payload = new byte[Ulid.RANDOM_BYTES];
|
byte[] payload = new byte[Ulid.RANDOM_BYTES];
|
||||||
random.nextBytes(payload);
|
entropy.nextBytes(payload);
|
||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,7 @@ import com.github.f4b6a3.ulid.UlidFactory;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class UlidFactoryDefaultfTest extends UlidFactoryTest {
|
public class UlidFactoryDefaultfTest extends UlidFactoryTest {
|
||||||
|
|
||||||
|
@ -33,97 +29,6 @@ public class UlidFactoryDefaultfTest extends UlidFactoryTest {
|
||||||
checkCreationTime(list, startTime, endTime);
|
checkCreationTime(list, startTime, endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClockDrift() {
|
|
||||||
|
|
||||||
long diff = UlidFactory.MonotonicFunction.CLOCK_DRIFT_TOLERANCE;
|
|
||||||
long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli();
|
|
||||||
long times[] = { time, time + 0, time + 1, time + 2, time + 3 - diff, time + 4 - diff, time + 5 };
|
|
||||||
|
|
||||||
Clock clock = new Clock() {
|
|
||||||
private int i;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long millis() {
|
|
||||||
return times[i++ % times.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ZoneId getZone() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Clock withZone(ZoneId zone) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Instant instant() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Supplier<byte[]> randomSupplier = UlidFactory.getRandomSupplier(new Random());
|
|
||||||
UlidFactory factory = UlidFactory.newInstance(randomSupplier, clock);
|
|
||||||
|
|
||||||
long ms1 = factory.create().getTime(); // time
|
|
||||||
long ms2 = factory.create().getTime(); // time + 0
|
|
||||||
long ms3 = factory.create().getTime(); // time + 1
|
|
||||||
long ms4 = factory.create().getTime(); // time + 2
|
|
||||||
long ms5 = factory.create().getTime(); // time + 3 - 10000 (CLOCK DRIFT)
|
|
||||||
long ms6 = factory.create().getTime(); // time + 4 - 10000 (CLOCK DRIFT)
|
|
||||||
long ms7 = factory.create().getTime(); // time + 5
|
|
||||||
assertEquals(times[0], ms1);
|
|
||||||
assertEquals(times[1], ms2);
|
|
||||||
assertEquals(times[2], ms3);
|
|
||||||
assertEquals(times[3], ms4);
|
|
||||||
assertEquals(times[4], ms5);
|
|
||||||
assertEquals(times[5], ms6);
|
|
||||||
assertEquals(times[6], ms7);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLeapSecond() {
|
|
||||||
|
|
||||||
long second = Instant.parse("2021-12-31T23:59:59.000Z").getEpochSecond();
|
|
||||||
long leapSecond = second - 1; // simulate a leap second
|
|
||||||
long times[] = { second, leapSecond };
|
|
||||||
|
|
||||||
Clock clock = new Clock() {
|
|
||||||
private int i;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long millis() {
|
|
||||||
return times[i++ % times.length] * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ZoneId getZone() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Clock withZone(ZoneId zone) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Instant instant() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Supplier<byte[]> randomSupplier = UlidFactory.getRandomSupplier(new Random());
|
|
||||||
UlidFactory factory = UlidFactory.newInstance(randomSupplier, clock);
|
|
||||||
|
|
||||||
long ms1 = factory.create().getTime(); // second
|
|
||||||
long ms2 = factory.create().getTime(); // leap second
|
|
||||||
|
|
||||||
assertEquals(times[0] * 1000, ms1);
|
|
||||||
assertEquals(times[1] * 1000, ms2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetUlidInParallel() throws InterruptedException {
|
public void testGetUlidInParallel() throws InterruptedException {
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClockDrift() {
|
public void testGetMonotonicUlidAfterClockDrift() {
|
||||||
|
|
||||||
long diff = UlidFactory.MonotonicFunction.CLOCK_DRIFT_TOLERANCE;
|
long diff = UlidFactory.MonotonicFunction.CLOCK_DRIFT_TOLERANCE;
|
||||||
long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli();
|
long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli();
|
||||||
|
@ -85,7 +85,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLeapSecond() {
|
public void testGetMonotonicUlidAfterLeapSecond() {
|
||||||
|
|
||||||
long second = Instant.parse("2021-12-31T23:59:59.000Z").getEpochSecond();
|
long second = Instant.parse("2021-12-31T23:59:59.000Z").getEpochSecond();
|
||||||
long leapSecond = second - 1; // simulate a leap second
|
long leapSecond = second - 1; // simulate a leap second
|
||||||
|
|
Loading…
Reference in New Issue