handle clock drift #18

This commit is contained in:
Fabio Lima 2022-04-21 21:34:33 -03:00
parent c7738222f9
commit 28ece966b4
6 changed files with 24 additions and 138 deletions

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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;
}; };
} }

View File

@ -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 {

View File

@ -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