use crate::CLEAR_VARIANT_MASK;
use crate::CLEAR_VERSION_MASK;
use crate::VARIANT_SHIFT;
use crate::VERSION_SHIFT;
#[cfg(not(feature = "v7-crypto-rand"))]
use crate::nums::MathRandom;
use rand::RngExt;
#[cfg(feature = "v7-crypto-rand")]
use rand::rngs::ThreadRng;
use std::cell::RefCell;
use std::error;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
#[cfg(feature = "v7-global-monotonicity")]
use std::sync::LazyLock;
#[cfg(feature = "v7-global-monotonicity")]
use std::sync::Mutex;
use std::time::Duration;
use std::time::SystemTime;
use std::time::SystemTimeError;
use std::time::UNIX_EPOCH;
thread_local! {
static CONTEXT: RefCell<UUIDv7Context> =
const { RefCell::new(UUIDv7Context { ts: 0, a: 0, b: 0 }) };
}
thread_local! {
static RNG: RefCell<EntropySource> = RefCell::new(EntropySource::new())
}
#[cfg(feature = "v7-global-monotonicity")]
static STRICT_CONTEXT: LazyLock<Mutex<UUIDv7Context>> =
LazyLock::new(|| Mutex::new(UUIDv7Context { ts: 0, a: 0, b: 0 }));
#[cfg(not(feature = "v7-global-monotonicity"))]
thread_local! {
static STRICT_CONTEXT: RefCell<UUIDv7Context> =
const { RefCell::new(UUIDv7Context { ts: 0, a: 0, b: 0 }) };
}
const RANDOM_A_BITS: u8 = 12;
const RANDOM_B_BITS: u8 = 62;
const TIMESTAMP_BITS: u8 = 48;
const RANDOM_A_TOTAL_VALUES: u16 = 1 << RANDOM_A_BITS;
const RANDOM_A_MAX: u16 = RANDOM_A_TOTAL_VALUES - 1;
const RANDOM_B_MAX: u64 = (1 << RANDOM_B_BITS) - 1;
const TIMESTAMP_MAX: u64 = (1 << TIMESTAMP_BITS) - 1;
const TIMESTAMP_SHIFT: u8 = 80;
const RANDOM_A_SHIFT: u128 = 64;
const VERSION: u8 = 7; const VARIANT: u8 = 2;
pub fn generate() -> Result<String, UUIDv7Error> {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let random_b = RNG.with_borrow_mut(|entropy_source| entropy_source.random());
Ok(generate_with(timestamp, random_b))
}
pub fn generate_strict() -> Result<String, UUIDv7Error> {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let random_b = RNG.with_borrow_mut(|entropy_source| entropy_source.random());
generate_strict_with(timestamp, random_b)
}
pub fn generate_raw() -> Result<u128, UUIDv7Error> {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let random_b = RNG.with_borrow_mut(|entropy_source| entropy_source.random());
Ok(generate_raw_with(timestamp, random_b))
}
pub fn generate_strict_raw() -> Result<u128, UUIDv7Error> {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let random_b = RNG.with_borrow_mut(|entropy_source| entropy_source.random());
generate_strict_raw_with(timestamp, random_b)
}
pub fn uuid_to_parts(uuid: &str) -> Result<(u128, u16, u64), crate::GWIDError> {
let value = crate::uuid_to_raw(uuid)?;
let unix_ts_ms = (value >> TIMESTAMP_SHIFT) & TIMESTAMP_MAX as u128;
let random_a = (value >> RANDOM_A_SHIFT) & RANDOM_A_MAX as u128;
let random_b = value & RANDOM_B_MAX as u128;
Ok((unix_ts_ms, random_a as u16, random_b as u64))
}
fn generate_raw_with(timestamp: Duration, random_b: u64) -> u128 {
let mut unix_ts_ms = timestamp.as_millis() & TIMESTAMP_MAX as u128;
let mut random_a = derive_random_a(timestamp) & RANDOM_A_MAX;
let random_b = random_b & RANDOM_B_MAX;
CONTEXT
.with_borrow_mut(|ctx| ensure_monotonicity(&mut unix_ts_ms, &mut random_a, random_b, ctx));
raw_uuid_from_parts(unix_ts_ms, random_a, random_b)
}
fn generate_with(timestamp: Duration, random_b: u64) -> String {
crate::encode(generate_raw_with(timestamp, random_b))
}
fn generate_strict_raw_with(timestamp: Duration, random_b: u64) -> Result<u128, UUIDv7Error> {
let mut unix_ts_ms = timestamp.as_millis() & TIMESTAMP_MAX as u128;
let mut random_a = derive_random_a(timestamp) & RANDOM_A_MAX;
let mut random_b = random_b & RANDOM_B_MAX;
#[cfg(feature = "v7-global-monotonicity")]
let mut ctx = STRICT_CONTEXT
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
#[cfg(feature = "v7-global-monotonicity")]
ensure_strict_monotonicity(&mut unix_ts_ms, &mut random_a, &mut random_b, &mut ctx)?;
#[cfg(not(feature = "v7-global-monotonicity"))]
STRICT_CONTEXT.with_borrow_mut(|ctx| {
ensure_strict_monotonicity(&mut unix_ts_ms, &mut random_a, &mut random_b, ctx)
})?;
Ok(raw_uuid_from_parts(unix_ts_ms, random_a, random_b))
}
fn generate_strict_with(timestamp: Duration, random_b: u64) -> Result<String, UUIDv7Error> {
Ok(crate::encode(generate_strict_raw_with(
timestamp, random_b,
)?))
}
fn derive_random_a(from_timestamp: Duration) -> u16 {
let sub_ms_nanos = from_timestamp.subsec_nanos() % 1_000_000;
((RANDOM_A_TOTAL_VALUES as u64 * sub_ms_nanos as u64) / 1_000_000) as u16
}
fn raw_uuid_from_parts(unix_ts_ms: u128, random_a: u16, random_b: u64) -> u128 {
(unix_ts_ms << TIMESTAMP_SHIFT) & CLEAR_VERSION_MASK
| ((VERSION as u128) << VERSION_SHIFT)
| ((random_a as u128) << RANDOM_A_SHIFT) & CLEAR_VARIANT_MASK
| ((VARIANT as u128) << VARIANT_SHIFT)
| (random_b as u128)
}
fn ensure_monotonicity(
unix_ts_ms: &mut u128,
random_a: &mut u16,
random_b: u64,
ctx: &mut UUIDv7Context,
) {
*unix_ts_ms = u128::max(ctx.ts, *unix_ts_ms);
if *unix_ts_ms == ctx.ts {
*random_a = u16::max(ctx.a, *random_a);
}
ctx.ts = *unix_ts_ms;
ctx.a = *random_a;
ctx.b = random_b;
}
fn ensure_strict_monotonicity(
unix_ts_ms: &mut u128,
random_a: &mut u16,
random_b: &mut u64,
ctx: &mut UUIDv7Context,
) -> Result<(), UUIDv7Error> {
*unix_ts_ms = u128::max(ctx.ts, *unix_ts_ms);
if *unix_ts_ms == ctx.ts && *random_a <= ctx.a {
*random_a = u16::max(ctx.a, *random_a);
if *random_a == ctx.a && *random_b <= ctx.b {
if ctx.b < RANDOM_B_MAX {
*random_b = u64::max(ctx.b + 1, *random_b);
}
else if ctx.a < RANDOM_A_MAX {
*random_a = ctx.a + 1;
*random_b = 0;
}
else if ctx.ts < TIMESTAMP_MAX as u128 {
*unix_ts_ms = ctx.ts + 1;
*random_a = 0;
*random_b = 0;
}
else {
return Err(UUIDv7Error::Exhausted);
}
}
}
ctx.ts = *unix_ts_ms;
ctx.a = *random_a;
ctx.b = *random_b;
Ok(())
}
#[derive(Debug)]
pub enum UUIDv7Error {
Exhausted,
SystemTimeError(SystemTimeError),
}
impl Display for UUIDv7Error {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use UUIDv7Error::*;
match self {
Exhausted => {
let e = "The 'timestamp', 'random_a', and 'random_b' fields have all overflowed! Rethink your threading strategy!";
write!(f, "{e}")
}
SystemTimeError(err) => write!(f, "System time error {err}"),
}
}
}
impl From<SystemTimeError> for UUIDv7Error {
fn from(error: SystemTimeError) -> Self {
Self::SystemTimeError(error)
}
}
impl error::Error for UUIDv7Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::SystemTimeError(error) => Some(error),
_ => None,
}
}
}
#[derive(Debug)]
struct UUIDv7Context {
pub(crate) ts: u128,
pub(crate) a: u16,
pub(crate) b: u64,
}
#[cfg(not(feature = "v7-crypto-rand"))]
trait RandomProvider {
fn random(&mut self) -> u64;
}
#[cfg(not(feature = "v7-crypto-rand"))]
impl RandomProvider for MathRandom {
fn random(&mut self) -> u64 {
self.random_u64()
}
}
struct EntropySource {
#[cfg(not(feature = "v7-crypto-rand"))]
rng: MathRandom,
#[cfg(feature = "v7-crypto-rand")]
rng: ThreadRng,
}
impl EntropySource {
#[rustfmt::skip]
pub fn new() -> Self {
#[cfg(not(feature = "v7-crypto-rand"))]
{ Self { rng: MathRandom::initialize(Some(rand::rng().random())) } }
#[cfg(feature = "v7-crypto-rand")]
{ Self { rng: rand::rng() } }
}
pub fn random(&mut self) -> u64 {
self.rng.random()
}
}
#[cfg(test)]
mod test {
use super::*;
use rand::RngExt;
use std::time::{Duration, UNIX_EPOCH};
use std::{
collections::HashSet,
sync::{Arc, Barrier},
thread,
};
fn fresh_ctx() -> UUIDv7Context {
UUIDv7Context { ts: 0, a: 0, b: 0 }
}
#[test]
fn it_works() {
let _uuid = crate::v7::generate_strict().unwrap();
}
#[test]
#[ignore]
fn generate_until_duplicate_or_manually_stopped() {
let mut uuids = HashSet::new();
let mut prev: u128 = 0;
loop {
if uuids.len() > 100_000_000 {
uuids.clear();
println!("RESET UUID SET");
std::thread::sleep(Duration::from_secs(3));
}
let current_id = generate_strict().unwrap();
let current_id_u128 = crate::uuid_to_raw_unchecked(¤t_id);
assert!(
uuids.insert(current_id_u128),
"duplicate ID encountered! {current_id}"
);
assert!(
current_id_u128 > prev,
"ids should be stricty increasing! current={current_id_u128} is less than previous={prev}"
);
prev = current_id_u128;
println!("{current_id}");
}
}
#[test]
fn non_monotonic_clock_drift_timestamp() {
let mut ctx = UUIDv7Context {
ts: 100,
a: 100,
b: 100,
};
let mut ts = 99;
let mut a = 100;
let b = 100;
ensure_monotonicity(&mut ts, &mut a, b, &mut ctx);
assert_eq!(ts, 100);
assert_eq!(a, 100);
assert_eq!(b, 100);
}
#[test]
fn non_monotonic_clock_drift_random_a_sub_ms() {
let mut ctx = UUIDv7Context {
ts: 100,
a: 100,
b: 100,
};
let mut ts = 100;
let mut a = 99;
let b = 100;
ensure_monotonicity(&mut ts, &mut a, b, &mut ctx);
assert_eq!(ts, 100);
assert_eq!(a, 100);
assert_eq!(b, 100);
}
#[test]
fn monotonic_generation() {
let mut previous: u128 = 0;
for _ in 0..100_000 {
let uuid = generate_strict().unwrap().replace("-", "");
let current = u128::from_str_radix(&uuid, 16).unwrap();
assert!(
current > previous,
"expected monotonic generation (current should be > previous) : previous={previous} ; current={current}"
);
previous = current;
}
}
#[test]
fn multiple_ids_same_ms() {
let sys_time = UNIX_EPOCH + Duration::new(1_672_576_496, 123_456_700);
let ts = sys_time.duration_since(UNIX_EPOCH).unwrap();
let a = generate_strict_with(ts, rand::rng().random::<u64>()).unwrap();
let b = generate_strict_with(ts, rand::rng().random::<u64>()).unwrap();
let a_u128 = crate::uuid_to_raw_unchecked(&a);
let b_u128 = crate::uuid_to_raw_unchecked(&b);
assert!(
a_u128 < b_u128,
"expected id generated first to be less than id generated second : first={a_u128} ; second={b_u128}",
);
println!("{}", "-".repeat(100));
}
#[test]
fn example_from_rfc() {
let expected: u16 = 1870;
let jan_1_2023_12_34_56_1234567 = UNIX_EPOCH + Duration::new(1_672_576_496, 123_456_700);
let duration = jan_1_2023_12_34_56_1234567
.duration_since(UNIX_EPOCH)
.unwrap();
let _timestamp = duration.as_millis();
let random_a = crate::v7::derive_random_a(duration);
assert_eq!(
expected, random_a,
"expected = {expected} ; got = {random_a}"
);
}
#[test]
fn uuid_has_correct_format() {
let uuid = generate_strict_with(Duration::from_millis(1_700_000_000_000), 12345).unwrap();
assert_eq!(uuid.len(), 36);
assert_eq!(uuid.chars().filter(|&c| c == '-').count(), 4);
assert_eq!(&uuid[8..9], "-");
assert_eq!(&uuid[13..14], "-");
assert_eq!(&uuid[18..19], "-");
assert_eq!(&uuid[23..24], "-");
assert!(uuid.chars().all(|c| c.is_ascii_hexdigit() || c == '-'));
}
#[test]
fn uuid_has_correct_version_and_variant() {
let uuid = generate_strict_with(Duration::from_millis(1_700_000_000_000), 12345).unwrap();
assert_eq!(&uuid[14..15], "7");
assert!(matches!(&uuid[19..20], "8" | "9" | "a" | "b"));
}
#[test]
fn uuid_to_parts_extracts_correctly() {
let timestamp_system_time = UNIX_EPOCH + Duration::new(1_672_576_496, 123_456_700);
let timestamp = timestamp_system_time.duration_since(UNIX_EPOCH).unwrap();
let unix_ts_ms = timestamp.as_millis();
let random_a = derive_random_a(timestamp);
let random_b: u64 = 123456700;
let uuid = generate_with(timestamp, random_b);
let parts = uuid_to_parts(&uuid).unwrap();
assert_eq!(
parts.0, unix_ts_ms,
"unix_ts_ms | expected {unix_ts_ms} got {}",
parts.0
);
assert_eq!(
parts.1, random_a,
"random_a | expected {random_a} got {}",
parts.1
);
assert_eq!(
parts.2, random_b,
"random_b | expected {random_b} got {}",
parts.2
);
let uuid = generate_strict_with(timestamp, random_b).unwrap();
let parts = uuid_to_parts(&uuid).unwrap();
assert_eq!(
parts.0, unix_ts_ms,
"[strict] unix_ts_ms | expected {unix_ts_ms} got {}",
parts.0
);
assert_eq!(
parts.1, random_a,
"[strict] random_a | expected {random_a} got {}",
parts.1
);
assert_eq!(
parts.2, random_b,
"[strict] random_b | expected {random_b} got {}",
parts.2
);
}
#[test]
fn uuid_to_parts_rejects_invalid_uuid() {
let result = uuid_to_parts("not-a-valid-uuid");
assert!(result.is_err());
}
#[test]
fn timestamp_is_encoded_correctly() {
let timestamp = 1_700_000_000_000u128;
let uuid = generate_strict_with(Duration::from_millis(timestamp as u64), 12345).unwrap();
let value = crate::uuid_to_raw_unchecked(&uuid);
let decoded_timestamp = (value >> TIMESTAMP_SHIFT) & TIMESTAMP_MAX as u128;
assert_eq!(decoded_timestamp, timestamp);
}
#[test]
fn random_a_uses_submillisecond_precision() {
assert_eq!(derive_random_a(Duration::from_nanos(0)), 0);
assert_eq!(derive_random_a(Duration::from_nanos(500_000)), 2048);
assert_eq!(derive_random_a(Duration::from_nanos(999_999)), 4095);
}
#[test]
fn random_a_is_nondecreasing() {
let mut previous = 0;
for nanos in (1_000..1_000_000).step_by(1_000) {
let current = derive_random_a(Duration::from_nanos(nanos));
assert!(current >= previous);
previous = current;
}
}
#[test]
fn monotonicity_increments_random_b_when_timestamp_and_random_a_are_unchanged() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 100;
ctx.b = 200;
let mut timestamp = 100;
let mut random_a = 100;
let mut random_b = 150;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(timestamp, 100);
assert_eq!(random_a, 100);
assert_eq!(
random_b, 201,
"expected randomb_b to equal 201, got={random_b}"
);
}
#[test]
fn monotonicity_preserves_higher_random_a() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 100;
ctx.b = 200;
let mut timestamp = 100;
let mut random_a = 101;
let mut random_b = 50;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(random_a, 101);
assert_eq!(random_b, 50);
}
#[test]
fn monotonicity_preserves_higher_random_b_when_timestamp_and_random_a_match() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 100;
ctx.b = 200;
let mut timestamp = 101;
let mut random_a = 100;
let mut random_b = 300;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(random_b, 300);
}
#[test]
fn monotonicity_does_not_increment_random_b_when_random_a_differs() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 100;
ctx.b = 200;
let mut timestamp = 100;
let mut random_a = 101;
let mut random_b = 50;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(random_b, 50);
}
#[test]
fn monotonicity_clamps_backwards_timestamp() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 100;
ctx.b = 200;
let mut timestamp = 99;
let mut random_a = 100;
let mut random_b = 200;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(timestamp, 100);
assert_eq!(random_a, 100);
assert_eq!(random_b, 201);
}
#[test]
fn monotonicity_allows_timestamp_to_advance() {
let mut ctx = fresh_ctx();
ctx.ts = 100;
ctx.a = 4095;
ctx.b = RANDOM_B_MAX;
let mut timestamp = 101;
let mut random_a = 10;
let mut random_b = 20;
ensure_strict_monotonicity(&mut timestamp, &mut random_a, &mut random_b, &mut ctx).unwrap();
assert_eq!(timestamp, 101);
assert_eq!(random_a, 10);
assert_eq!(random_b, 20);
}
#[test]
fn concurrent_generation_produces_thread_local_strict_monotonic_uuids() {
const THREADS: usize = 8;
const UUIDS_PER_THREAD: usize = 10_000;
let barrier = Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
(0..UUIDS_PER_THREAD)
.map(|_| generate_strict().unwrap())
.collect::<Vec<_>>()
})
})
.collect();
for (idx, handle) in handles.into_iter().enumerate() {
let uuids = handle.join().unwrap();
assert!(
uuids.windows(2).all(|w| w[0] < w[1]),
"thread {idx} did not produce strictly monotonic UUIDs!"
);
}
}
}
#[cfg(all(test, feature = "v7-global-monotonicity"))]
mod strict_global_monotonicity {
use std::{
collections::HashSet,
sync::{Arc, Barrier},
thread,
};
use super::*;
fn generate_strict_and<CB>(mut cb: CB) -> Result<(), UUIDv7Error>
where
CB: FnMut(&str),
{
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?;
let random_b = rand::rng().random::<u64>();
let mut unix_ts_ms = timestamp.as_millis() & TIMESTAMP_MAX as u128;
let mut random_a = derive_random_a(timestamp) & RANDOM_A_MAX;
let mut random_b = random_b & RANDOM_B_MAX;
let mut ctx = STRICT_CONTEXT
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
ensure_strict_monotonicity(&mut unix_ts_ms, &mut random_a, &mut random_b, &mut ctx)?;
let uuid = (unix_ts_ms << TIMESTAMP_SHIFT) & CLEAR_VERSION_MASK
| ((VERSION as u128) << VERSION_SHIFT)
| ((random_a as u128) << RANDOM_A_SHIFT) & CLEAR_VARIANT_MASK
| ((VARIANT as u128) << VARIANT_SHIFT)
| (random_b as u128);
cb(&crate::encode(uuid));
Ok(())
}
#[test]
fn concurrent_generation_produces_global_monotonicy() {
use std::sync::atomic::{AtomicUsize, Ordering};
const THREADS: usize = 8;
const UUIDS_PER_THREAD: usize = 10_000;
let barrier = Arc::new(Barrier::new(THREADS));
let index = Arc::new(AtomicUsize::new(0));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let barrier = Arc::clone(&barrier);
let index = Arc::clone(&index);
thread::spawn(move || {
let mut generated = Vec::with_capacity(UUIDS_PER_THREAD);
barrier.wait();
for _ in 0..UUIDS_PER_THREAD {
generate_strict_and(|uuid| {
let uuid_value = crate::uuid_to_raw_unchecked(uuid);
let i = index.fetch_add(1, Ordering::Relaxed);
generated.push((i, uuid_value));
})
.unwrap();
}
generated
})
})
.collect();
let mut uuids: Vec<_> = handles
.into_iter()
.flat_map(|handle| handle.join().unwrap())
.collect();
assert_eq!(uuids.len(), THREADS * UUIDS_PER_THREAD);
uuids.sort_unstable_by_key(|(index, _)| *index);
let mut prev_index: isize = -1;
let mut prev: u128 = 0;
for (index, uuid) in &uuids {
assert!(
prev_index < *index as isize,
"index not in order! {prev_index} {}",
*index
);
assert!(
prev < *uuid,
"not globally strictly mono! {prev} is greater than {}",
*uuid
);
prev = *uuid;
prev_index = *index as isize;
}
let unique_uuids: HashSet<_> = uuids.iter().map(|(_, uuid)| uuid).collect();
assert_eq!(
unique_uuids.len(),
uuids.len(),
"Duplicate UUIDs generated!"
);
}
#[test]
fn concurrent_generation_produces_unique_uuids() {
const THREADS: usize = 8;
const UUIDS_PER_THREAD: usize = 10_000;
let barrier = Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
(0..UUIDS_PER_THREAD)
.map(|_| generate_strict().unwrap())
.collect::<Vec<_>>()
})
})
.collect();
let uuids: Vec<String> = handles
.into_iter()
.flat_map(|handle| handle.join().unwrap())
.collect();
assert_eq!(uuids.len(), THREADS * UUIDS_PER_THREAD);
let unique_uuids: HashSet<_> = uuids.iter().collect();
assert_eq!(unique_uuids.len(), uuids.len());
}
#[test]
fn concurrent_generation_with_same_timestamp_is_unique() {
const THREADS: usize = 8;
const UUIDS_PER_THREAD: usize = 10_000;
let barrier = Arc::new(Barrier::new(THREADS));
let timestamp = Duration::from_millis(1_700_000_000_000);
let handles: Vec<_> = (0..THREADS)
.map(|_| {
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
barrier.wait();
(0..UUIDS_PER_THREAD)
.map(|_| generate_strict_with(timestamp, 0).unwrap())
.collect::<Vec<_>>()
})
})
.collect();
let uuids: Vec<String> = handles
.into_iter()
.flat_map(|handle| handle.join().unwrap())
.collect();
let uuids_len = uuids.len();
let expect_len = THREADS * UUIDS_PER_THREAD;
assert_eq!(
uuids_len, expect_len,
"expected uuids.len={uuids_len} and expected_len={expect_len} to be equal"
);
let unique_uuids: HashSet<_> = uuids.iter().collect();
let unique_uuids_len = unique_uuids.len();
assert_eq!(
unique_uuids_len, uuids_len,
"expected unique_uuids_len={unique_uuids_len} to equal uuids_len={uuids_len}"
);
}
}