use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::id_helper::IdInfo;
use crate::options::IGOptions;
#[cfg(feature = "metrics")]
#[derive(Debug, Clone)]
pub struct IdGeneratorMetrics {
pub total_generated: u64,
pub clock_backwards_count: u64,
pub cas_conflict_count: u64,
}
#[derive(Debug)]
pub struct FastIdGenerator {
base_time: i64,
worker_id: u64,
timestamp_shift: u8,
seq_bits: u8,
seq_mask: u64,
max_seq: u64,
state: AtomicU64,
#[cfg(feature = "metrics")]
total_generated: AtomicU64,
#[cfg(feature = "metrics")]
clock_backwards_count: AtomicU64,
#[cfg(feature = "metrics")]
cas_conflict_count: AtomicU64,
}
impl FastIdGenerator {
pub fn new(options: &IGOptions) -> Self {
let seq_bits = options.seq_bit_length;
let wid_bits = options.worker_id_bit_length;
Self {
base_time: options.base_time,
worker_id: options.worker_id as u64,
timestamp_shift: wid_bits + seq_bits,
seq_bits,
seq_mask: (1u64 << seq_bits) - 1,
max_seq: (1u64 << seq_bits) - 1,
state: AtomicU64::new(0),
#[cfg(feature = "metrics")]
total_generated: AtomicU64::new(0),
#[cfg(feature = "metrics")]
clock_backwards_count: AtomicU64::new(0),
#[cfg(feature = "metrics")]
cas_conflict_count: AtomicU64::new(0),
}
}
#[inline]
pub fn next_id(&self) -> u64 {
let mut now = (current_time_millis() - self.base_time) as u64;
loop {
let state = self.state.load(Ordering::Acquire);
let last_timestamp = state >> self.seq_bits;
let last_seq = state & self.seq_mask;
if now == last_timestamp {
let new_seq = last_seq + 1;
if new_seq > self.max_seq {
std::hint::spin_loop();
now = (current_time_millis() - self.base_time) as u64;
continue;
}
let new_state = (now << self.seq_bits) | new_seq;
if self
.state
.compare_exchange_weak(state, new_state, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return (now << self.timestamp_shift)
| (self.worker_id << self.seq_bits)
| new_seq;
}
} else if now > last_timestamp {
let new_state = now << self.seq_bits; if self
.state
.compare_exchange_weak(state, new_state, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
return (now << self.timestamp_shift) | (self.worker_id << self.seq_bits);
}
} else {
let drift = last_timestamp - now;
if drift > 10 {
panic!(
"⏰ Clock rollback detected ({} ms). ID generation halted.",
drift
);
}
std::hint::spin_loop();
now = (current_time_millis() - self.base_time) as u64;
}
}
}
#[inline]
pub fn next_ids_batch(&self, count: usize) -> Vec<u64> {
let mut ids = Vec::with_capacity(count);
for _ in 0..count {
ids.push(self.next_id());
}
ids
}
#[cfg(feature = "metrics")]
pub fn metrics(&self) -> IdGeneratorMetrics {
IdGeneratorMetrics {
total_generated: self.total_generated.load(Ordering::Relaxed),
clock_backwards_count: self.clock_backwards_count.load(Ordering::Relaxed),
cas_conflict_count: self.cas_conflict_count.load(Ordering::Relaxed),
}
}
#[inline]
pub fn extract_timestamp(&self, id: u64) -> i64 {
let ts = id >> self.timestamp_shift;
(ts as i64) + self.base_time
}
#[inline]
pub fn extract_worker_id(&self, id: u64) -> u16 {
let wid_bits = self.timestamp_shift - self.seq_bits;
((id >> self.seq_bits) & ((1u64 << wid_bits) - 1)) as u16
}
#[inline]
pub fn worker_id(&self) -> u16 {
self.worker_id as u16
}
#[inline]
pub fn extract_sequence(&self, id: u64) -> u32 {
(id & self.seq_mask) as u32
}
#[inline]
pub fn extract_id_info(&self, id: u64) -> IdInfo {
let timestamp = self.extract_timestamp(id);
IdInfo {
timestamp,
worker_id: self.extract_worker_id(id),
sequence: self.extract_sequence(id),
system_time: UNIX_EPOCH + Duration::from_millis(timestamp as u64),
}
}
}
#[inline]
fn current_time_millis() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as i64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::options::DEFAULT_BASE_TIME;
use std::sync::Arc;
use std::thread;
fn create_test_options() -> IGOptions {
IGOptions {
method: 1,
base_time: DEFAULT_BASE_TIME,
worker_id: 1,
worker_id_bit_length: 10,
seq_bit_length: 12,
max_seq_number: 0,
min_seq_number: 5,
top_over_cost_count: 2000,
}
}
#[test]
fn test_basic_generation() {
let gen = FastIdGenerator::new(&create_test_options());
let id = gen.next_id();
assert!(id > 0);
assert_eq!(gen.extract_worker_id(id), 1);
}
#[test]
fn test_id_info_extraction() {
let gen = FastIdGenerator::new(&create_test_options());
let id = gen.next_id();
let info = gen.extract_id_info(id);
assert_eq!(info.worker_id, 1);
assert!(info.timestamp > DEFAULT_BASE_TIME);
}
#[test]
fn test_sequence_increments() {
let gen = FastIdGenerator::new(&create_test_options());
let id1 = gen.next_id();
let id2 = gen.next_id();
let seq1 = gen.extract_sequence(id1);
let seq2 = gen.extract_sequence(id2);
assert_eq!(seq2, seq1 + 1);
}
#[test]
fn test_concurrent_generation() {
let gen = Arc::new(FastIdGenerator::new(&create_test_options()));
let mut handles = vec![];
for _ in 0..4 {
let gen = Arc::clone(&gen);
handles.push(thread::spawn(move || {
let mut ids = vec![];
for _ in 0..1000 {
ids.push(gen.next_id());
}
ids
}));
}
let mut all_ids = vec![];
for handle in handles {
all_ids.extend(handle.join().unwrap());
}
let mut sorted_ids = all_ids.clone();
sorted_ids.sort();
sorted_ids.dedup();
assert_eq!(all_ids.len(), sorted_ids.len(), "Found duplicate IDs");
}
#[test]
fn test_id_uniqueness() {
let gen = FastIdGenerator::new(&create_test_options());
let mut ids = std::collections::HashSet::new();
for _ in 0..10000 {
let id = gen.next_id();
assert!(ids.insert(id), "Duplicate ID found: {}", id);
}
}
}