diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8edc7e6..984fad2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
Nothing unreleased.
+## [4.2.1] - 2022-04-21
+
+Handle clock drift. #18
+
## [4.2.0] - 2022-04-21
Handle clock drift. #18
@@ -282,7 +286,8 @@ Project created as an alternative Java implementation of [ULID spec](https://git
- Added `LICENSE`
- 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.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
diff --git a/LICENSE b/LICENSE
index 67098b5..8a995c3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
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
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index ca9cf3c..9a3e647 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ Add these lines to your `pom.xml`.
com.github.f4b6a3
ulid-creator
- 4.2.0
+ 4.2.1
```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).
diff --git a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java
index e2e4403..4aaef93 100644
--- a/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java
+++ b/src/main/java/com/github/f4b6a3/ulid/UlidFactory.java
@@ -47,7 +47,7 @@ public final class UlidFactory {
private final LongFunction ulidFunction;
public UlidFactory() {
- this(new UlidFunction(), null);
+ this(new UlidFunction(getRandomSupplier(null)));
}
private UlidFactory(LongFunction ulidFunction) {
@@ -67,7 +67,7 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
public static UlidFactory newInstance() {
- return new UlidFactory(new UlidFunction());
+ return newInstance(getRandomSupplier(null));
}
/**
@@ -77,7 +77,7 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
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));
}
- /**
- * 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 randomSupplier, Clock clock) {
- return new UlidFactory(new UlidFunction(randomSupplier), clock);
- }
-
/**
* Returns a new monotonic factory.
*
* @return {@link UlidFactory}
*/
public static UlidFactory newMonotonicInstance() {
- return new UlidFactory(new MonotonicFunction());
+ return newMonotonicInstance(getRandomSupplier(null));
}
/**
@@ -121,7 +108,7 @@ public final class UlidFactory {
* @return {@link UlidFactory}
*/
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
private Supplier randomSupplier;
- public UlidFunction() {
- this(new SecureRandom());
- }
-
- public UlidFunction(Random random) {
- this(getRandomSupplier(random));
- }
-
public UlidFunction(Supplier randomSupplier) {
this.randomSupplier = randomSupplier;
}
@@ -201,8 +180,8 @@ public final class UlidFactory {
*/
protected static final class MonotonicFunction implements LongFunction {
- private long lastTime = 0;
- private Ulid lastUlid = null;
+ private long lastTime;
+ private Ulid lastUlid;
// Used to preserve monotonicity when the system clock is
// 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
private Supplier randomSupplier;
- public MonotonicFunction() {
- this(new SecureRandom());
- }
-
- public MonotonicFunction(Random random) {
- this(getRandomSupplier(random));
- }
-
public MonotonicFunction(Supplier randomSupplier) {
this.randomSupplier = randomSupplier;
+
+ // initialize internal state
+ this.lastTime = Clock.systemUTC().millis();
+ this.lastUlid = new Ulid(lastTime, randomSupplier.get());
}
@Override
@@ -233,10 +208,10 @@ public final class UlidFactory {
if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) {
this.lastUlid = lastUlid.increment();
} else {
+ this.lastTime = time;
this.lastUlid = new Ulid(time, this.randomSupplier.get());
}
- this.lastTime = lastUlid.getTime();
return new Ulid(this.lastUlid);
}
}
@@ -248,9 +223,10 @@ public final class UlidFactory {
* @return a random supplier that returns 10 bytes
*/
protected static Supplier getRandomSupplier(Random random) {
+ Random entropy = random != null ? random : new SecureRandom();
return () -> {
byte[] payload = new byte[Ulid.RANDOM_BYTES];
- random.nextBytes(payload);
+ entropy.nextBytes(payload);
return payload;
};
}
diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryDefaultfTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryDefaultfTest.java
index 55d9729..eca2121 100644
--- a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryDefaultfTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryDefaultfTest.java
@@ -8,11 +8,7 @@ import com.github.f4b6a3.ulid.UlidFactory;
import static org.junit.Assert.*;
-import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneId;
import java.util.Random;
-import java.util.function.Supplier;
public class UlidFactoryDefaultfTest extends UlidFactoryTest {
@@ -33,97 +29,6 @@ public class UlidFactoryDefaultfTest extends UlidFactoryTest {
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 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 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
public void testGetUlidInParallel() throws InterruptedException {
diff --git a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java
index 582c5f5..1f6f06c 100644
--- a/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java
+++ b/src/test/java/com/github/f4b6a3/ulid/UlidFactoryMonotonicTest.java
@@ -36,7 +36,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
}
@Test
- public void testClockDrift() {
+ public void testGetMonotonicUlidAfterClockDrift() {
long diff = UlidFactory.MonotonicFunction.CLOCK_DRIFT_TOLERANCE;
long time = Instant.parse("2021-12-31T23:59:59.000Z").toEpochMilli();
@@ -85,7 +85,7 @@ public class UlidFactoryMonotonicTest extends UlidFactoryTest {
}
@Test
- public void testLeapSecond() {
+ public void testGetMonotonicUlidAfterLeapSecond() {
long second = Instant.parse("2021-12-31T23:59:59.000Z").getEpochSecond();
long leapSecond = second - 1; // simulate a leap second