feat: handle clock drift #18
Added drift tolerance of 10 seconds to preserve monotonicity when the system clock is adjusted by NTP after a small clock drift or when the system clock jumps back by 1 second due to leap second. The random component is incremented when the current time: - is the same as the previous time; - has moved backwards up to 10 seconds. The time component is also incremented if the random component is exceeded, which is quite rare.
This commit is contained in:
		
							parent
							
								
									5abd3f46ff
								
							
						
					
					
						commit
						745bd55ffc
					
				| 
						 | 
					@ -6,7 +6,11 @@ All notable changes to this project will be documented in this file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Nothing unreleased.
 | 
					Nothing unreleased.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [4.1.1] - 2021-11-06
 | 
					## [4.2.0] - 2022-04-21
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Handle clock drift. #18
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [4.1.2] - 2021-11-06
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Compare internal fields as unsigned integers.
 | 
					Compare internal fields as unsigned integers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -278,7 +282,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.1.2...HEAD
 | 
					[unreleased]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.2.0...HEAD
 | 
				
			||||||
 | 
					[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
 | 
				
			||||||
[4.1.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.0.0...ulid-creator-4.1.0
 | 
					[4.1.0]: https://github.com/f4b6a3/ulid-creator/compare/ulid-creator-4.0.0...ulid-creator-4.1.0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.1.2</version>
 | 
					  <version>4.2.0</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).
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * 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
 | 
				
			||||||
| 
						 | 
					@ -584,12 +584,12 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
				
			||||||
	 * millisecond;
 | 
						 * millisecond;
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * (2) This method can generate monotonic increasing ULIDs 99.999999999999992%
 | 
						 * (2) This method can generate monotonic increasing ULIDs 99.999999999999992%
 | 
				
			||||||
	 * ((2^80 - 10^9) / (2^80)) of the time, considering an unrealistic rate of
 | 
						 * ((2^80 - 10^9) / (2^80)) of the time within a single millisecond interval,
 | 
				
			||||||
	 * 1,000,000,000 ULIDs per millisecond.
 | 
						 * considering an unrealistic rate of 1,000,000,000 ULIDs per millisecond.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * Due to (1) and (2), it does not throw the error message recommended by the
 | 
						 * Due to (1) and (2), it does not throw the error message recommended by the
 | 
				
			||||||
	 * specification. When an overflow occurs in the last 80 bits, the random
 | 
						 * specification. When an overflow occurs in the random 80 bits, the time
 | 
				
			||||||
	 * component simply wraps around.
 | 
						 * component is simply incremented.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @return a ULID
 | 
						 * @return a ULID
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
| 
						 | 
					@ -599,8 +599,7 @@ public final class Ulid implements Serializable, Comparable<Ulid> {
 | 
				
			||||||
		long newLsb = this.lsb + 1; // increment the LEAST significant bits
 | 
							long newLsb = this.lsb + 1; // increment the LEAST significant bits
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (newLsb == INCREMENT_OVERFLOW) {
 | 
							if (newLsb == INCREMENT_OVERFLOW) {
 | 
				
			||||||
			// carrying the extra bit by incrementing the MOST significant bits
 | 
								newMsb += 1; // increment the MOST significant bits
 | 
				
			||||||
			newMsb = (newMsb & 0xffffffffffff0000L) | ((newMsb + 1) & 0x000000000000ffffL);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return new Ulid(newMsb, newLsb);
 | 
							return new Ulid(newMsb, newLsb);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * 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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * 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
 | 
				
			||||||
| 
						 | 
					@ -25,6 +25,7 @@
 | 
				
			||||||
package com.github.f4b6a3.ulid;
 | 
					package com.github.f4b6a3.ulid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.security.SecureRandom;
 | 
					import java.security.SecureRandom;
 | 
				
			||||||
 | 
					import java.time.Clock;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
import java.util.function.LongFunction;
 | 
					import java.util.function.LongFunction;
 | 
				
			||||||
import java.util.function.Supplier;
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
| 
						 | 
					@ -42,14 +43,20 @@ import java.util.function.Supplier;
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public final class UlidFactory {
 | 
					public final class UlidFactory {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Clock clock; // for tests
 | 
				
			||||||
	private final LongFunction<Ulid> ulidFunction;
 | 
						private final LongFunction<Ulid> ulidFunction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public UlidFactory() {
 | 
						public UlidFactory() {
 | 
				
			||||||
		this.ulidFunction = new UlidFunction();
 | 
							this(new UlidFunction(), null);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UlidFactory(LongFunction<Ulid> ulidFunction) {
 | 
						private UlidFactory(LongFunction<Ulid> ulidFunction) {
 | 
				
			||||||
 | 
							this(ulidFunction, null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private UlidFactory(LongFunction<Ulid> ulidFunction, Clock clock) {
 | 
				
			||||||
		this.ulidFunction = ulidFunction;
 | 
							this.ulidFunction = ulidFunction;
 | 
				
			||||||
 | 
							this.clock = clock != null ? clock : Clock.systemUTC();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -85,6 +92,19 @@ 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.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
| 
						 | 
					@ -116,13 +136,26 @@ public final class UlidFactory {
 | 
				
			||||||
		return new UlidFactory(new MonotonicFunction(randomSupplier));
 | 
							return new UlidFactory(new MonotonicFunction(randomSupplier));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Returns a new monotonic 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 newMonotonicInstance(Supplier<byte[]> randomSupplier, Clock clock) {
 | 
				
			||||||
 | 
							return new UlidFactory(new MonotonicFunction(randomSupplier), clock);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Returns a UUID.
 | 
						 * Returns a UUID.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @return a ULID
 | 
						 * @return a ULID
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Ulid create() {
 | 
						public Ulid create() {
 | 
				
			||||||
		return create(System.currentTimeMillis());
 | 
							return create(clock.millis());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
| 
						 | 
					@ -168,9 +201,14 @@ public final class UlidFactory {
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected static final class MonotonicFunction implements LongFunction<Ulid> {
 | 
						protected static final class MonotonicFunction implements LongFunction<Ulid> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private long lastTime = -1;
 | 
							private long lastTime = 0;
 | 
				
			||||||
		private Ulid lastUlid = null;
 | 
							private Ulid lastUlid = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Used to preserve monotonicity when the system clock is
 | 
				
			||||||
 | 
							// adjusted by NTP after a small clock drift or when the
 | 
				
			||||||
 | 
							// system clock jumps back by 1 second due to leap second.
 | 
				
			||||||
 | 
							protected static final int CLOCK_DRIFT_TOLERANCE = 10_000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// it must return an array of 10 bytes
 | 
							// it must return an array of 10 bytes
 | 
				
			||||||
		private Supplier<byte[]> randomSupplier;
 | 
							private Supplier<byte[]> randomSupplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -189,13 +227,16 @@ public final class UlidFactory {
 | 
				
			||||||
		@Override
 | 
							@Override
 | 
				
			||||||
		public synchronized Ulid apply(final long time) {
 | 
							public synchronized Ulid apply(final long time) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (time == this.lastTime) {
 | 
								// Check if the current time is the same as the previous time or has moved
 | 
				
			||||||
 | 
								// backwards after a small system clock adjustment or after a leap second.
 | 
				
			||||||
 | 
								// Drift tolerance = (previous_time - 10s) < current_time <= previous_time
 | 
				
			||||||
 | 
								if ((time > this.lastTime - CLOCK_DRIFT_TOLERANCE) && (time <= this.lastTime)) {
 | 
				
			||||||
				this.lastUlid = lastUlid.increment();
 | 
									this.lastUlid = lastUlid.increment();
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				this.lastUlid = new Ulid(time, this.randomSupplier.get());
 | 
									this.lastUlid = new Ulid(time, this.randomSupplier.get());
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			this.lastTime = time;
 | 
								this.lastTime = lastUlid.getTime();
 | 
				
			||||||
			return new Ulid(this.lastUlid);
 | 
								return new Ulid(this.lastUlid);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,11 @@ 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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +33,97 @@ 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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,8 +8,12 @@ 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.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class UlidFactoryMonotonicTest extends UlidFactoryTest {
 | 
					public class UlidFactoryMonotonicTest extends UlidFactoryTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,6 +35,95 @@ public class UlidFactoryMonotonicTest 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.newMonotonicInstance(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(ms1 + 0, ms2); // clock repeats.
 | 
				
			||||||
 | 
							assertEquals(ms1 + 1, ms3); // clock advanced.
 | 
				
			||||||
 | 
							assertEquals(ms1 + 2, ms4); // clock advanced.
 | 
				
			||||||
 | 
							assertEquals(ms1 + 2, ms5); // CLOCK DRIFT! DON'T MOVE BACKWARDS!
 | 
				
			||||||
 | 
							assertEquals(ms1 + 2, ms6); // CLOCK DRIFT! DON'T MOVE BACKWARDS!
 | 
				
			||||||
 | 
							assertEquals(ms1 + 5, ms7); // clock advanced.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@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.newMonotonicInstance(randomSupplier, clock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							long ms1 = factory.create().getTime(); // second
 | 
				
			||||||
 | 
							long ms2 = factory.create().getTime(); // leap second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assertEquals(ms1, ms2); // LEAP SECOND! DON'T MOVE BACKWARDS!
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void checkOrdering(Ulid[] list) {
 | 
						private void checkOrdering(Ulid[] list) {
 | 
				
			||||||
		Ulid[] other = Arrays.copyOf(list, list.length);
 | 
							Ulid[] other = Arrays.copyOf(list, list.length);
 | 
				
			||||||
		Arrays.sort(other);
 | 
							Arrays.sort(other);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue