diff --git a/src/julid.rs b/src/julid.rs index 3f3edae..b769b00 100644 --- a/src/julid.rs +++ b/src/julid.rs @@ -1,17 +1,20 @@ use core::{fmt, str::FromStr}; -use std::{sync::Mutex, time::Duration}; +use std::{ + sync::atomic::{AtomicU64, Ordering}, + time::Duration, +}; use rand::{random, thread_rng, Rng}; use crate::base32::{self, DecodeError}; -/// This ID is used to ensure monotonicity for new IDs. -static LAST_ID: Mutex = Mutex::new(Julid::alpha()); +/// This is used to ensure monotonicity for new IDs. +static LAST_SORTABLE: AtomicU64 = AtomicU64::new(0); /// The number of bits in a Julid's time portion pub const TIME_BITS: u8 = 48; /// The number of bits in the monotonic counter for intra-millisecond IDs -pub const MBITS: u8 = 16; +pub const COUNTER_BITS: u8 = 16; /// The number of random bits + bits in the monotonic counter pub const UNIQUE_BITS: u8 = 80; pub const RANDOM_BITS: u8 = 64; @@ -44,36 +47,36 @@ impl Julid { pub fn new() -> Self { let lsb: u64 = random(); loop { - if let Ok(mut guard) = LAST_ID.try_lock() { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or(Duration::ZERO) - .as_millis() as u64; - let ots = guard.timestamp(); - if ots < ts { - let new = Julid::new_time(ts, lsb); - *guard = new; - break new; - } else { - let counter = guard.counter().saturating_add(1); - let tbits = ots & bitmask!(TIME_BITS); - let msb = (tbits << MBITS) + counter as u64; - let new: Julid = (msb, lsb).into(); - *guard = new; - break new; + let ts = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or(Duration::ZERO) + .as_millis() as u64; + let last = LAST_SORTABLE.load(Ordering::SeqCst); + let ots = last >> COUNTER_BITS; + if ots < ts { + let msb = ts << COUNTER_BITS; + if LAST_SORTABLE + .compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed) + .is_ok() + { + break (msb, lsb).into(); + } + } else { + let counter = ((last & bitmask!(COUNTER_BITS) as u64) as u16).saturating_add(1); + let msb = (ots << COUNTER_BITS) + counter as u64; + if LAST_SORTABLE + .compare_exchange(last, msb, Ordering::SeqCst, Ordering::Relaxed) + .is_ok() + { + break (msb, lsb).into(); } } + // we didn't update the global counter, try again let micros = thread_rng().gen_range(10..50); std::thread::sleep(Duration::from_micros(micros)); } } - fn new_time(time: u64, lsb: u64) -> Self { - let tbits = time & bitmask!(TIME_BITS); - let msb = tbits << MBITS; - (msb, lsb).into() - } - /// Creates a Julid from a Crockford Base32 encoded string /// /// An DecodeError will be returned when the given string is not formated @@ -103,6 +106,10 @@ impl Julid { Julid(0) } + /// The 'Omega Julid'. + /// + /// The Omega Julid is special form of Julid that is specified to have + /// all 128 bits set to one. pub const fn omega() -> Self { Julid(u128::MAX) } @@ -113,12 +120,12 @@ impl Julid { } pub const fn counter(&self) -> u16 { - let mask = bitmask!(MBITS); + let mask = bitmask!(COUNTER_BITS); ((self.0 >> RANDOM_BITS) & mask) as u16 } pub const fn sortable(&self) -> u64 { - let mask = bitmask!(TIME_BITS + MBITS); + let mask = bitmask!(TIME_BITS + COUNTER_BITS); ((self.0 >> RANDOM_BITS) & mask) as u64 }