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