use crate::builder::Builder;
use crate::clock::ClockDriftStrategy;
use crate::error::*;
use crate::id::SnowflakeId;
use chrono::prelude::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
pub struct SharedSnowflake {
pub(crate) state: AtomicU64,
pub(crate) start_time: i64,
pub(crate) data_center_id: u16,
pub(crate) machine_id: u16,
pub(crate) bit_len_time: u8,
pub(crate) bit_len_sequence: u8,
pub(crate) bit_len_data_center_id: u8,
pub(crate) bit_len_machine_id: u8,
pub(crate) clock_drift_strategy: ClockDriftStrategy,
pub(crate) max_clock_drift_ms: Option<i64>,
}
pub struct Snowflake(pub(crate) Arc<SharedSnowflake>);
impl Snowflake {
pub fn new() -> Result<Self, Error> {
Builder::new().finalize()
}
pub fn builder<'a>() -> Builder<'a> {
Builder::new()
}
pub(crate) fn new_inner(shared: Arc<SharedSnowflake>) -> Self {
Self(shared)
}
pub fn next_id(&self) -> Result<SnowflakeId, Error> {
let sequence_mask = (1u64 << self.0.bit_len_sequence) - 1;
let time_shift = self.0.bit_len_sequence;
let time_max = (1u64 << self.0.bit_len_time) - 1;
#[cfg(feature = "tracing")]
tracing::trace!("generating next snowflake id");
loop {
let current_state = self.0.state.load(Ordering::Relaxed);
let last_time = current_state >> time_shift;
let elapsed_time = current_elapsed_time(self.0.start_time) as u64;
if elapsed_time < last_time {
#[cfg(feature = "metrics")]
metrics::counter!("snowflake_clock_drift_events_total").increment(1);
#[cfg(feature = "tracing")]
tracing::warn!(
last_time,
current_time = elapsed_time,
strategy = ?self.0.clock_drift_strategy,
"clock drift detected"
);
match self.0.clock_drift_strategy {
ClockDriftStrategy::Wait => {
if let Some(max_drift) = self.0.max_clock_drift_ms {
let drift = last_time - elapsed_time;
if drift > max_drift as u64 {
return Err(Error::ClockDriftExceeded {
drift_ms: drift,
max_ms: max_drift,
});
}
}
til_next_millis(self.0.start_time + last_time as i64);
continue;
}
ClockDriftStrategy::Error => {
return Err(Error::ClockDrift {
last_time,
current_time: elapsed_time,
});
}
ClockDriftStrategy::LastTimestamp => {
let sequence = (current_state & sequence_mask) + 1;
if sequence > sequence_mask {
til_next_millis(self.0.start_time + last_time as i64);
continue;
}
let new_state = (last_time << time_shift) | sequence;
if self
.0
.state
.compare_exchange_weak(
current_state,
new_state,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
let id = (last_time
<< (self.0.bit_len_data_center_id
+ self.0.bit_len_machine_id
+ self.0.bit_len_sequence))
| (u64::from(self.0.data_center_id)
<< (self.0.bit_len_machine_id + self.0.bit_len_sequence))
| (u64::from(self.0.machine_id) << self.0.bit_len_sequence)
| sequence;
return Ok(SnowflakeId::new(id));
}
continue;
}
}
}
let (next_time, next_sequence) = if elapsed_time == last_time {
let sequence = (current_state & sequence_mask) + 1;
if sequence > sequence_mask {
#[cfg(feature = "metrics")]
metrics::counter!("snowflake_sequence_exhaustion_total").increment(1);
#[cfg(feature = "tracing")]
tracing::debug!("sequence exhausted, waiting for next millisecond");
til_next_millis(self.0.start_time + last_time as i64);
continue; }
(last_time, sequence)
} else {
(elapsed_time, 0)
};
if next_time > time_max {
#[cfg(feature = "tracing")]
tracing::error!(time = next_time, max = time_max, "time limit exceeded");
return Err(Error::OverTimeLimit);
}
let new_state = (next_time << time_shift) | next_sequence;
if self
.0
.state
.compare_exchange_weak(
current_state,
new_state,
Ordering::AcqRel,
Ordering::Relaxed,
)
.is_ok()
{
let id = (next_time
<< (self.0.bit_len_data_center_id
+ self.0.bit_len_machine_id
+ self.0.bit_len_sequence))
| (u64::from(self.0.data_center_id)
<< (self.0.bit_len_machine_id + self.0.bit_len_sequence))
| (u64::from(self.0.machine_id) << self.0.bit_len_sequence)
| next_sequence;
#[cfg(feature = "metrics")]
{
metrics::counter!("snowflake_ids_generated_total").increment(1);
metrics::gauge!("snowflake_sequence_utilization")
.set(next_sequence as f64 / sequence_mask as f64);
}
#[cfg(feature = "tracing")]
tracing::trace!(
time = next_time,
sequence = next_sequence,
"snowflake id generated"
);
return Ok(SnowflakeId::new(id));
}
}
}
pub fn decompose(&self, id: SnowflakeId) -> DecomposedSnowflake {
DecomposedSnowflake::decompose(
id.as_u64(),
self.0.bit_len_time,
self.0.bit_len_sequence,
self.0.bit_len_data_center_id,
self.0.bit_len_machine_id,
)
}
}
impl Clone for Snowflake {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
const MICROS_PER_MILLI: i64 = 1_000_000;
pub(crate) fn to_snowflake_time(time: DateTime<Utc>) -> i64 {
time.timestamp_nanos_opt().unwrap_or(0) / MICROS_PER_MILLI
}
fn current_elapsed_time(start_time: i64) -> i64 {
to_snowflake_time(Utc::now()) - start_time
}
fn til_next_millis(last_timestamp: i64) {
let mut now = to_snowflake_time(Utc::now());
while now <= last_timestamp {
std::hint::spin_loop();
now = to_snowflake_time(Utc::now());
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DecomposedSnowflake {
pub id: SnowflakeId,
pub time: u64,
pub sequence: u64,
pub data_center_id: u64,
pub machine_id: u64,
}
impl DecomposedSnowflake {
pub fn decompose(
id: u64,
bit_len_time: u8,
bit_len_sequence: u8,
bit_len_data_center_id: u8,
bit_len_machine_id: u8,
) -> Self {
let total_bits = bit_len_time as u32
+ bit_len_sequence as u32
+ bit_len_data_center_id as u32
+ bit_len_machine_id as u32;
assert_eq!(total_bits, 63, "Total bit length must be 63");
let sequence_shift = 0;
let machine_id_shift = sequence_shift + bit_len_sequence;
let data_center_id_shift = machine_id_shift + bit_len_machine_id;
let time_shift = data_center_id_shift + bit_len_data_center_id;
let sequence_mask = (1u64 << bit_len_sequence) - 1;
let machine_id_mask = (1u64 << bit_len_machine_id) - 1;
let data_center_id_mask = (1u64 << bit_len_data_center_id) - 1;
Self {
id: SnowflakeId::new(id),
time: id >> time_shift,
data_center_id: (id >> data_center_id_shift) & data_center_id_mask,
machine_id: (id >> machine_id_shift) & machine_id_mask,
sequence: (id >> sequence_shift) & sequence_mask,
}
}
#[must_use]
pub fn to_id(&self) -> SnowflakeId {
self.id
}
#[must_use]
pub fn nanos_time(&self) -> i64 {
(self.time as i64) * MICROS_PER_MILLI
}
#[must_use]
pub fn int64(&self) -> i64 {
self.id.int64()
}
#[must_use]
pub fn string(&self) -> String {
self.id.string()
}
#[must_use]
pub fn base2(&self) -> String {
self.id.base2()
}
#[must_use]
pub fn base32(&self) -> String {
self.id.base32()
}
#[must_use]
pub fn hex(&self) -> String {
self.id.hex()
}
#[must_use]
pub fn base36(&self) -> String {
self.id.base36()
}
#[must_use]
pub fn base58(&self) -> String {
self.id.base58()
}
#[must_use]
pub fn base64(&self) -> String {
self.id.base64()
}
#[must_use]
pub fn bytes(&self) -> Vec<u8> {
self.id.bytes()
}
#[must_use]
pub fn int_bytes(&self) -> [u8; 8] {
self.id.int_bytes()
}
#[must_use]
pub fn elapsed_millis(&self) -> u64 {
self.time
}
}
impl std::fmt::Display for DecomposedSnowflake {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"id={}, time={}, data_center={}, machine={}, seq={}",
self.id, self.time, self.data_center_id, self.machine_id, self.sequence
)
}
}
impl From<&DecomposedSnowflake> for SnowflakeId {
fn from(decomposed: &DecomposedSnowflake) -> Self {
decomposed.id
}
}