use atomic int instead of mutex on the whole id.

This commit is contained in:
Joe Ardent 2023-07-26 08:51:47 -07:00
parent 703eb550e1
commit 8a79e737fc

View file

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