use std::cmp::Ordering;
use std::sync::Mutex;
use std::time::SystemTime;
pub fn id() -> u128 {
let mut guard = GLOBAL_GENERATOR.lock().expect("global tbid generator");
match *guard {
None => {
*guard = Some(TbidGenerator::new());
drop(guard);
id()
}
Some(ref mut generator) => generator.next(),
}
}
static GLOBAL_GENERATOR: Mutex<Option<TbidGenerator>> = Mutex::new(None);
struct TbidGenerator {
ms_since_epoch: u128,
random: u128, }
impl TbidGenerator {
fn new() -> TbidGenerator {
let ms_since_epoch = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
TbidGenerator {
ms_since_epoch,
random: random_u80(),
}
}
fn next(&mut self) -> u128 {
self.next_from_system_time(SystemTime::now())
}
fn next_from_system_time(&mut self, now: SystemTime) -> u128 {
*self = self.next_state_from_system_time(now);
assert!(is_u80(self.random));
self.ms_since_epoch << 80 | self.random
}
fn next_state_from_system_time(&self, now: SystemTime) -> TbidGenerator {
let previous_ms_since_epoch = self.ms_since_epoch;
let next_ms_since_epoch = now
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0);
match next_ms_since_epoch.cmp(&previous_ms_since_epoch) {
Ordering::Greater => {
TbidGenerator {
ms_since_epoch: next_ms_since_epoch,
random: random_u80(),
}
}
Ordering::Equal | Ordering::Less => {
match self.next_random() {
Some(next_random) => TbidGenerator {
ms_since_epoch: previous_ms_since_epoch,
random: next_random,
},
None => {
TbidGenerator {
ms_since_epoch: previous_ms_since_epoch
.checked_add(1)
.expect("impossible overflow"),
random: random_u80(),
}
}
}
}
}
}
fn next_random(&self) -> Option<u128> {
assert!(is_u80(self.random));
if self.random == U80_MASK {
None
} else {
Some(self.random + 1)
}
}
}
const U80_MASK: u128 = 0x_FFFF_FFFF_FFFF_FFFF_FFFF;
fn is_u80(val: u128) -> bool {
val <= U80_MASK
}
fn random_u80() -> u128 {
random_u128() & U80_MASK
}
fn random_u128() -> u128 {
let a = random_u64();
let b = random_u64();
let a = a as u128;
let b = b as u128;
a | (b << 64)
}
fn random_u64() -> u64 {
std::hash::Hasher::finish(&std::hash::BuildHasher::build_hasher(
&std::collections::hash_map::RandomState::new(),
))
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant, SystemTime};
use super::{id, TbidGenerator, U80_MASK};
struct TestResult {
id_start: u128,
id_end: u128,
}
fn run_test_for_a_few_ms(mut id_generator: impl FnMut() -> u128) -> TestResult {
let test_duration = Duration::from_millis(10);
let end_instant = Instant::now() + test_duration;
let mut id_prev = 0;
let mut id_start = None;
let mut id_end = None;
while Instant::now() < end_instant {
let id_next = id_generator();
assert!(id_prev < id_next, "ids not monotonically increasing");
let id_next_random_part = id_next & U80_MASK;
assert_ne!(id_next_random_part, 0);
std::thread::yield_now();
id_start = id_start.or(Some(id_next));
id_end = Some(id_next);
id_prev = id_next
}
let id_start = id_start.unwrap();
let id_end = id_end.unwrap();
TestResult { id_start, id_end }
}
fn assert_ids_monotonic_and_multiple_timestamps(id_generator: impl FnMut() -> u128) {
let TestResult { id_start, id_end } = run_test_for_a_few_ms(id_generator);
let timestamp_start = id_start & !U80_MASK;
let timestamp_end = id_end & !U80_MASK;
assert_ne!(timestamp_start, timestamp_end);
}
fn assert_ids_monotonic_and_single_timestamp(id_generator: impl FnMut() -> u128) {
let TestResult { id_start, id_end } = run_test_for_a_few_ms(id_generator);
let timestamp_start = id_start & !U80_MASK;
let timestamp_end = id_end & !U80_MASK;
assert_eq!(timestamp_start, timestamp_end);
}
#[test]
fn test_ids_with_normal_clock() {
assert_ids_monotonic_and_multiple_timestamps(|| id());
}
#[test]
fn test_random_overflow_still_monotonic() {
let future_duration_from_now = Duration::from_secs(1_000_000);
let future_time = SystemTime::now()
.checked_add(future_duration_from_now)
.unwrap();
let future_duration_since_epoch = future_time
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
let mut idgen = TbidGenerator {
ms_since_epoch: future_duration_since_epoch,
random: U80_MASK - 10,
};
assert_ids_monotonic_and_multiple_timestamps(|| idgen.next());
}
#[test]
fn test_reverse_time() {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
let mut idgen = TbidGenerator {
ms_since_epoch: now,
random: 0,
};
let mut count = 0;
assert_ids_monotonic_and_single_timestamp(|| {
let past_time_base = SystemTime::UNIX_EPOCH - Duration::from_secs(1_000_000);
let past_time = past_time_base + Duration::from_millis(count);
count += 1;
idgen.next_from_system_time(past_time)
});
}
#[test]
fn test_reverse_time_then_catch_up() {
let now = SystemTime::now();
let now_ms = now
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_millis();
let mut idgen = TbidGenerator {
ms_since_epoch: now_ms,
random: 0,
};
let mut count = 0;
assert_ids_monotonic_and_multiple_timestamps(|| {
let past_time_base = now - Duration::from_millis(2);
let past_time = past_time_base + Duration::from_millis(count);
count += 1;
idgen.next_from_system_time(past_time)
});
}
#[test]
fn test_prior_to_unix_epoch() {
let mut idgen = TbidGenerator {
ms_since_epoch: 0,
random: 0,
};
let mut count = 0;
assert_ids_monotonic_and_single_timestamp(|| {
let past_time_base = SystemTime::UNIX_EPOCH - Duration::from_secs(1_000_000);
let past_time = past_time_base + Duration::from_millis(count);
count += 1;
idgen.next_from_system_time(past_time)
});
}
}