mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
271 lines
8.4 KiB
Rust
271 lines
8.4 KiB
Rust
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
|
|
pub const FLUXER_EPOCH_MS: u64 = 1_420_070_400_000;
|
|
pub const WORKER_ID_BITS: u64 = 10;
|
|
pub const SEQUENCE_BITS: u64 = 12;
|
|
pub const WORKER_ID_SHIFT: u64 = SEQUENCE_BITS;
|
|
pub const TIMESTAMP_SHIFT: u64 = WORKER_ID_BITS + SEQUENCE_BITS;
|
|
pub const MAX_WORKER_ID: u32 = (1 << WORKER_ID_BITS) - 1;
|
|
pub const MAX_SEQUENCE: u16 = (1 << SEQUENCE_BITS) - 1;
|
|
|
|
#[cfg(test)]
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct SnowflakeParts {
|
|
pub timestamp_ms: u64,
|
|
pub worker_id: u16,
|
|
pub sequence: u16,
|
|
}
|
|
|
|
pub trait SnowflakeClock: Clone + Send + Sync + 'static {
|
|
fn now_ms(&self) -> u64;
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct SystemClock;
|
|
|
|
pub struct SnowflakeGenerator<C = SystemClock>
|
|
where
|
|
C: SnowflakeClock,
|
|
{
|
|
worker_id: u16,
|
|
sequence: u16,
|
|
last_timestamp: Option<u64>,
|
|
clock: C,
|
|
}
|
|
|
|
impl SnowflakeClock for SystemClock {
|
|
fn now_ms(&self) -> u64 {
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_millis() as u64
|
|
}
|
|
}
|
|
|
|
impl SnowflakeGenerator<SystemClock> {
|
|
pub fn new(worker_id: u32) -> anyhow::Result<Self> {
|
|
Self::with_clock(worker_id, SystemClock)
|
|
}
|
|
}
|
|
|
|
impl<C> SnowflakeGenerator<C>
|
|
where
|
|
C: SnowflakeClock,
|
|
{
|
|
pub fn with_clock(worker_id: u32, clock: C) -> anyhow::Result<Self> {
|
|
let worker_id = validate_worker_id(worker_id)?;
|
|
Ok(Self {
|
|
worker_id,
|
|
sequence: 0,
|
|
last_timestamp: None,
|
|
clock,
|
|
})
|
|
}
|
|
|
|
pub async fn generate(&mut self) -> anyhow::Result<u64> {
|
|
let mut timestamp = self.current_relative_timestamp()?;
|
|
match self.last_timestamp {
|
|
Some(last_timestamp) if timestamp <= last_timestamp => {
|
|
timestamp = last_timestamp;
|
|
self.sequence = (self.sequence + 1) & MAX_SEQUENCE;
|
|
if self.sequence == 0 {
|
|
timestamp = self.wait_until_next_timestamp(last_timestamp).await?;
|
|
}
|
|
}
|
|
_ => self.sequence = 0,
|
|
}
|
|
self.last_timestamp = Some(timestamp);
|
|
Ok(create_snowflake_from_relative_timestamp(
|
|
timestamp,
|
|
self.worker_id,
|
|
self.sequence,
|
|
))
|
|
}
|
|
|
|
pub async fn generate_many(&mut self, count: u32) -> anyhow::Result<Vec<u64>> {
|
|
let mut ids = Vec::with_capacity(count as usize);
|
|
for _ in 0..count {
|
|
ids.push(self.generate().await?);
|
|
}
|
|
Ok(ids)
|
|
}
|
|
|
|
fn current_relative_timestamp(&self) -> anyhow::Result<u64> {
|
|
to_relative_timestamp(self.clock.now_ms())
|
|
}
|
|
|
|
async fn wait_until_next_timestamp(&self, last_timestamp: u64) -> anyhow::Result<u64> {
|
|
loop {
|
|
let timestamp = self.current_relative_timestamp()?;
|
|
if timestamp > last_timestamp {
|
|
return Ok(timestamp);
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(1)).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn create_snowflake(timestamp_ms: u64, worker_id: u32, sequence: u32) -> anyhow::Result<u64> {
|
|
let relative_timestamp = to_relative_timestamp(timestamp_ms)?;
|
|
let worker_id = validate_worker_id(worker_id)?;
|
|
let sequence = validate_sequence(sequence)?;
|
|
Ok(create_snowflake_from_relative_timestamp(
|
|
relative_timestamp,
|
|
worker_id,
|
|
sequence,
|
|
))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn parse_snowflake(snowflake: u64) -> SnowflakeParts {
|
|
let relative_timestamp = snowflake >> TIMESTAMP_SHIFT;
|
|
let worker_id = ((snowflake >> WORKER_ID_SHIFT) & u64::from(MAX_WORKER_ID)) as u16;
|
|
let sequence = (snowflake & u64::from(MAX_SEQUENCE)) as u16;
|
|
SnowflakeParts {
|
|
timestamp_ms: relative_timestamp + FLUXER_EPOCH_MS,
|
|
worker_id,
|
|
sequence,
|
|
}
|
|
}
|
|
|
|
fn create_snowflake_from_relative_timestamp(
|
|
relative_timestamp: u64,
|
|
worker_id: u16,
|
|
sequence: u16,
|
|
) -> u64 {
|
|
(relative_timestamp << TIMESTAMP_SHIFT)
|
|
| (u64::from(worker_id) << WORKER_ID_SHIFT)
|
|
| u64::from(sequence)
|
|
}
|
|
|
|
fn to_relative_timestamp(timestamp_ms: u64) -> anyhow::Result<u64> {
|
|
if timestamp_ms < FLUXER_EPOCH_MS {
|
|
anyhow::bail!("timestamp must be on or after the Fluxer epoch");
|
|
}
|
|
Ok(timestamp_ms - FLUXER_EPOCH_MS)
|
|
}
|
|
|
|
fn validate_worker_id(worker_id: u32) -> anyhow::Result<u16> {
|
|
if worker_id > MAX_WORKER_ID {
|
|
anyhow::bail!("worker ID must be between 0 and {MAX_WORKER_ID}");
|
|
}
|
|
Ok(worker_id as u16)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn validate_sequence(sequence: u32) -> anyhow::Result<u16> {
|
|
if sequence > u32::from(MAX_SEQUENCE) {
|
|
anyhow::bail!("sequence must be between 0 and {MAX_SEQUENCE}");
|
|
}
|
|
Ok(sequence as u16)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
|
|
#[derive(Clone)]
|
|
struct ManualClock {
|
|
now: Arc<AtomicU64>,
|
|
}
|
|
|
|
impl ManualClock {
|
|
fn new(now_ms: u64) -> Self {
|
|
Self {
|
|
now: Arc::new(AtomicU64::new(now_ms)),
|
|
}
|
|
}
|
|
|
|
fn set(&self, now_ms: u64) {
|
|
self.now.store(now_ms, Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
impl SnowflakeClock for ManualClock {
|
|
fn now_ms(&self) -> u64 {
|
|
self.now.load(Ordering::SeqCst)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn constants_match_compatible_layout() {
|
|
assert_eq!(FLUXER_EPOCH_MS, 1_420_070_400_000);
|
|
assert_eq!(TIMESTAMP_SHIFT, 22);
|
|
assert_eq!(MAX_WORKER_ID, 1023);
|
|
assert_eq!(MAX_SEQUENCE, 4095);
|
|
}
|
|
|
|
#[test]
|
|
fn creates_and_parses_known_snowflake() {
|
|
let snowflake = create_snowflake(FLUXER_EPOCH_MS + 1_000_000, 3, 77).unwrap();
|
|
let parsed = parse_snowflake(snowflake);
|
|
assert_eq!(parsed.timestamp_ms, FLUXER_EPOCH_MS + 1_000_000);
|
|
assert_eq!(parsed.worker_id, 3);
|
|
assert_eq!(parsed.sequence, 77);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn generator_starts_sequence_at_zero() {
|
|
let clock = ManualClock::new(FLUXER_EPOCH_MS + 1000);
|
|
let mut generator = SnowflakeGenerator::with_clock(42, clock).unwrap();
|
|
let id = generator.generate().await.unwrap();
|
|
let parsed = parse_snowflake(id);
|
|
assert_eq!(parsed.timestamp_ms, FLUXER_EPOCH_MS + 1000);
|
|
assert_eq!(parsed.worker_id, 42);
|
|
assert_eq!(parsed.sequence, 0);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn generator_increments_sequence_within_same_millisecond() {
|
|
let clock = ManualClock::new(FLUXER_EPOCH_MS + 1000);
|
|
let mut generator = SnowflakeGenerator::with_clock(7, clock).unwrap();
|
|
let first = parse_snowflake(generator.generate().await.unwrap());
|
|
let second = parse_snowflake(generator.generate().await.unwrap());
|
|
assert_eq!(first.sequence, 0);
|
|
assert_eq!(second.sequence, 1);
|
|
assert_eq!(second.timestamp_ms, first.timestamp_ms);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn generator_remains_monotonic_when_clock_moves_backwards() {
|
|
let clock = ManualClock::new(FLUXER_EPOCH_MS + 1002);
|
|
let mut generator = SnowflakeGenerator::with_clock(1, clock.clone()).unwrap();
|
|
let first = generator.generate().await.unwrap();
|
|
clock.set(FLUXER_EPOCH_MS + 1001);
|
|
let second = generator.generate().await.unwrap();
|
|
assert!(second > first);
|
|
assert_eq!(parse_snowflake(second).timestamp_ms, FLUXER_EPOCH_MS + 1002);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn generator_waits_for_next_millisecond_on_sequence_overflow() {
|
|
let clock = ManualClock::new(FLUXER_EPOCH_MS + 1000);
|
|
let mut generator = SnowflakeGenerator::with_clock(1, clock.clone()).unwrap();
|
|
for _ in 0..=MAX_SEQUENCE {
|
|
generator.generate().await.unwrap();
|
|
}
|
|
let advance_clock = clock.clone();
|
|
tokio::spawn(async move {
|
|
tokio::time::sleep(Duration::from_millis(2)).await;
|
|
advance_clock.set(FLUXER_EPOCH_MS + 1001);
|
|
});
|
|
let id = generator.generate().await.unwrap();
|
|
let parsed = parse_snowflake(id);
|
|
assert_eq!(parsed.timestamp_ms, FLUXER_EPOCH_MS + 1001);
|
|
assert_eq!(parsed.sequence, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn validates_worker_and_sequence_bounds() {
|
|
assert!(create_snowflake(FLUXER_EPOCH_MS, 1023, 4095).is_ok());
|
|
assert!(create_snowflake(FLUXER_EPOCH_MS, 1024, 0).is_err());
|
|
assert!(create_snowflake(FLUXER_EPOCH_MS, 0, 4096).is_err());
|
|
assert!(create_snowflake(FLUXER_EPOCH_MS - 1, 0, 0).is_err());
|
|
}
|
|
}
|