sequence_generator/
sequence_generator.rs

1use std::borrow::BorrowMut;
2use std::cell::{Cell, RefCell};
3use std::rc::Rc;
4use std::thread::sleep;
5use std::time::{Duration, SystemTime, SystemTimeError};
6
7pub type SequenceGeneratorSystemTimeError = SystemTimeError;
8
9fn timestamp_from_custom_epoch(
10    custom_epoch: SystemTime,
11    micros_ten_power: u8,
12) -> Result<u64, SequenceGeneratorSystemTimeError> {
13    let timestamp: u128 = SystemTime::now().duration_since(custom_epoch)?.as_micros();
14    let micros_power_adjustment_factor: u64 = 10_u64.pow(micros_ten_power.into());
15    let calculated_timestamp = (timestamp as u64) / micros_power_adjustment_factor;
16    Ok(calculated_timestamp)
17}
18
19#[derive(Debug)]
20pub struct SequenceProperties {
21    pub unused_bits: u8,
22    pub timestamp_bits: u8,
23    pub node_id_bits: u8,
24    pub sequence_bits: u8,
25    pub custom_epoch: SystemTime,
26    current_timestamp: Rc<RefCell<Option<u64>>>,
27    last_timestamp: Rc<RefCell<Option<u64>>>,
28    pub micros_ten_power: u8,
29    pub node_id: u16,
30    pub sequence: Cell<u16>,
31    pub max_sequence: u16,
32    pub backoff_cooldown_start_ns: u64,
33    partial_cached_id: Rc<RefCell<Option<u64>>>,
34}
35
36impl SequenceProperties {
37    pub fn new(
38        custom_epoch: SystemTime,
39        node_id_bits: u8,
40        node_id: u16,
41        sequence_bits: u8,
42        micros_ten_power: u8,
43        unused_bits: u8,
44        backoff_cooldown_start_ns: u64,
45    ) -> Self {
46        if unused_bits > 7 {
47            panic!(
48                "ERROR: unused_bits '{}' is larger than the maximum value of 7.",
49                unused_bits
50            )
51        }
52        if sequence_bits > 16 {
53            panic!(
54                "ERROR: sequence_bits '{}' is larger than the maximum value of 16.",
55                sequence_bits
56            )
57        }
58        if sequence_bits == 0 {
59            panic!(
60                "ERROR: sequence_bits '{}' must be larger or equal than 1.",
61                sequence_bits
62            )
63        }
64        if node_id_bits > 16 {
65            panic!(
66                "ERROR: node_id_bits '{}' is larger than the maximum value of 16.",
67                node_id_bits
68            )
69        }
70        if node_id_bits == 0 {
71            panic!(
72                "ERROR: node_id_bits '{}' must be larger or equal than 1.",
73                node_id_bits
74            )
75        }
76        let timestamp_bits = (64_u8)
77            .checked_sub(sequence_bits)
78            .unwrap_or_else(|| {panic!(
79                "ERROR: Sum of bits is too large, maximum value 64. Sequence bits '{}'", sequence_bits)})
80            .checked_sub(node_id_bits)
81            .unwrap_or_else(|| {panic!(
82                "ERROR: Sum of bits is too large, maximum value 64. Node ID bits '{}', Sequence bits '{}'",
83                node_id_bits, sequence_bits
84            )})
85            .checked_sub(unused_bits)
86            .unwrap_or_else(|| {panic!(
87                "ERROR: Sum of bits is too large, maximum value 64. Unused bits '{}', Sequence bits '{}', Node ID bits '{}'", 
88                unused_bits, sequence_bits, node_id_bits
89            )});
90
91        SequenceProperties {
92            custom_epoch,
93            timestamp_bits,
94            node_id_bits,
95            sequence_bits,
96            micros_ten_power,
97            node_id,
98            unused_bits,
99            sequence: Cell::new(0_u16),
100            current_timestamp: Rc::new(RefCell::new(None)),
101            last_timestamp: Rc::new(RefCell::new(None)),
102            max_sequence: (2_u16).pow(sequence_bits.into()),
103            backoff_cooldown_start_ns,
104            partial_cached_id: Rc::new(RefCell::new(None)),
105        }
106    }
107    pub fn set_last_timestamp(&self, timestamp: &mut Option<u64>) {
108        if let Some(last_timestamp) = timestamp.take() {
109            let _ = self
110                .last_timestamp
111                .as_ref()
112                .borrow_mut()
113                .insert(last_timestamp);
114        }
115    }
116    pub fn set_current_timestamp(&self) {
117        let _ = self.current_timestamp.as_ref().borrow_mut().insert(timestamp_from_custom_epoch(
118            self.custom_epoch,
119            self.micros_ten_power,
120        ).unwrap_or_else(|error| {panic!("ERROR: Could not calculate current timestamp from custom epoch {:?} and micros power of {:?}. Error: {}",
121        self.custom_epoch, self.micros_ten_power, error)}));
122    }
123    pub fn set_partial_cached_id(&self, cached_id: &mut Option<u64>) {
124        let _ = self
125            .partial_cached_id
126            .as_ref()
127            .borrow_mut()
128            .insert(cached_id.take().unwrap());
129    }
130}
131
132pub fn generate_id(
133    properties: &SequenceProperties,
134) -> Result<u64, SequenceGeneratorSystemTimeError> {
135    properties.set_last_timestamp(&mut properties.current_timestamp.clone().take().take());
136    properties.set_current_timestamp();
137    if let Some(last_timestamp) = properties.last_timestamp.take() {
138        let current_timestamp = properties.current_timestamp.borrow().unwrap();
139        if current_timestamp < last_timestamp {
140            println!("ERROR: System Clock moved backwards. Current timestamp '{}' is earlier than last registered '{}'.", 
141                current_timestamp, last_timestamp);
142            if properties.sequence.get() == properties.max_sequence {
143                wait_next_timestamp(
144                    last_timestamp,
145                    properties.custom_epoch,
146                    properties.micros_ten_power,
147                    properties.backoff_cooldown_start_ns,
148                )?;
149                // After timestamp changed reset to start a new sequence
150                properties.sequence.set(0);
151            } else {
152                wait_until_last_timestamp(
153                    last_timestamp,
154                    properties.custom_epoch,
155                    properties.micros_ten_power,
156                    properties.backoff_cooldown_start_ns,
157                )?;
158            }
159            properties.set_current_timestamp();
160        } else if properties.current_timestamp.borrow().unwrap() != last_timestamp {
161            properties.sequence.set(0);
162        }
163    }
164    let new_id = to_id(properties);
165    let new_sequence = properties.sequence.get() + 1;
166    properties.sequence.set(new_sequence);
167    if new_sequence == properties.max_sequence {
168        wait_next_timestamp(
169            properties.current_timestamp.borrow().unwrap(),
170            properties.custom_epoch,
171            properties.micros_ten_power,
172            properties.backoff_cooldown_start_ns,
173        )?;
174        properties.set_current_timestamp();
175        // After timestamp changed reset to start a new sequence
176        properties.sequence.set(0);
177    }
178    Ok(new_id)
179}
180
181fn wait_next_timestamp(
182    last_timestamp: u64,
183    custom_epoch: SystemTime,
184    micros_ten_power: u8,
185    backoff_cooldown_start_ns: u64,
186) -> Result<(), SequenceGeneratorSystemTimeError> {
187    let mut current_timestamp = timestamp_from_custom_epoch(custom_epoch, micros_ten_power)?;
188    let backoff_cooldown_ns: u64 = backoff_cooldown_start_ns;
189    while current_timestamp <= last_timestamp {
190        sleep(Duration::from_nanos(backoff_cooldown_ns));
191        current_timestamp = timestamp_from_custom_epoch(custom_epoch, micros_ten_power)?;
192        // Double the cooldown wait period (exponential backoff)
193        backoff_cooldown_ns
194            .checked_add(backoff_cooldown_ns)
195            .unwrap_or_else(|| {
196                panic!(
197                    "ERROR: Cannot double backoff cooldown, maximum value reached '{}'",
198                    backoff_cooldown_ns
199                )
200            });
201    }
202    Ok(())
203}
204
205fn wait_until_last_timestamp(
206    last_timestamp: u64,
207    custom_epoch: SystemTime,
208    micros_ten_power: u8,
209    backoff_cooldown_start_ns: u64,
210) -> Result<(), SequenceGeneratorSystemTimeError> {
211    let mut current_timestamp = timestamp_from_custom_epoch(custom_epoch, micros_ten_power)?;
212    let backoff_cooldown_ns: u64 = backoff_cooldown_start_ns;
213    while current_timestamp < last_timestamp {
214        sleep(Duration::from_nanos(backoff_cooldown_ns));
215        current_timestamp = timestamp_from_custom_epoch(custom_epoch, micros_ten_power)?;
216        // Double the cooldown wait period (exponential backoff)
217        backoff_cooldown_ns
218            .checked_add(backoff_cooldown_ns)
219            .unwrap_or_else(|| {
220                panic!(
221                    "ERROR: Cannot double backoff cooldown, maximum value reached '{}'",
222                    backoff_cooldown_ns
223                )
224            });
225    }
226    Ok(())
227}
228
229fn to_id_cached(properties: &SequenceProperties) -> u64 {
230    let mut id = properties.partial_cached_id.as_ref().borrow().unwrap();
231    id |= (properties.sequence.get() as u64) << properties.node_id_bits;
232    id
233}
234
235fn to_id(properties: &SequenceProperties) -> u64 {
236    if properties.sequence.get() == 0 {
237        cache_partial_id(properties);
238    }
239    to_id_cached(properties)
240}
241
242fn cache_partial_id(properties: &SequenceProperties) {
243    let timestamp_shift_bits = properties.node_id_bits + properties.sequence_bits;
244    let mut id = properties.current_timestamp.borrow().unwrap() << timestamp_shift_bits;
245    id |= properties.node_id as u64;
246    properties.set_partial_cached_id(Some(id).borrow_mut());
247}
248
249pub fn decode_timestamp_micros(id: u64, properties: &SequenceProperties) -> u64 {
250    let id_timestamp_custom_epoch = (id << (properties.unused_bits))
251        >> (properties.node_id_bits + properties.sequence_bits + properties.unused_bits);
252    let timestamp_micros =
253        id_timestamp_custom_epoch * (10_u64).pow(properties.micros_ten_power as u32);
254    properties
255        .custom_epoch
256        .duration_since(properties.custom_epoch)
257        .unwrap_or_else(|_| {panic!("ERROR: Could not calculate difference between timestamp decoded from ID and Unix epoch.")})
258        .checked_add(Duration::from_micros(timestamp_micros))
259        .unwrap_or_else(|| {panic!("ERROR: Could not add the timestamp decoded from ID to the provided custom epoch.")})
260        .as_micros() as u64
261}
262
263pub fn decode_node_id(id: u64, properties: &SequenceProperties) -> u16 {
264    ((id << (properties.unused_bits + properties.timestamp_bits + properties.sequence_bits))
265        >> (properties.sequence_bits + properties.timestamp_bits + properties.unused_bits))
266        as u16
267}
268
269pub fn decode_sequence_id(id: u64, properties: &SequenceProperties) -> u16 {
270    ((id << (properties.unused_bits + properties.timestamp_bits))
271        >> (properties.unused_bits + properties.timestamp_bits + properties.node_id_bits))
272        as u16
273}
274
275#[cfg(test)]
276mod tests {
277    #[test]
278    fn timestamp_from() {
279        // Perform consistency tests for datetime calculation from a custom epoch
280        // First case: Compare system time against custom epoch set to UNIX_EPOCH
281        // Second case: Set CUSTOM_EPOCH to test start time and compare timestamp
282        // calculation against known sleep duration interval
283        use super::*;
284        use std::time::UNIX_EPOCH;
285
286        let time_now = SystemTime::now();
287        let millis_start = time_now
288            .duration_since(UNIX_EPOCH)
289            .expect("ERROR: Failed to get current time as duration from epoch.")
290            .as_millis();
291        sleep(Duration::from_millis(50));
292        // Test UNIX EPOCH
293        let millis_after = timestamp_from_custom_epoch(UNIX_EPOCH, 3).unwrap_or_else(
294            |error| {
295                panic!(
296                    "SequenceGeneratorSystemTimeError: Failed to get timestamp from custom epoch {:?}, difference {:?}",
297                    UNIX_EPOCH, (error).duration()
298                )
299            });
300        // More than expected 50ms. Upper boundary cannot be ascertained as Normal distribution
301        // CPU low-power states and/or older hardware can cause signifficant differences.
302        // (although rather then a Normal distribution, it is instead the case that a Pareto
303        // distribution applies, making it impossible to set high enough value for the test
304        // not to fail on ocassion)
305        let substracted_times = millis_after.checked_sub(millis_start as u64).unwrap();
306        println!("Too small time difference between times calculated\nfrom UNIX_EPOCH using independent functions.\n\nEpoch System Time - Time Difference w/Epoch = {} ms,\nexpected greater or equals than sleep interval 50 ms.\n", substracted_times);
307        assert!(substracted_times >= 50);
308        // If too big upper boundary there could be numerical errors.
309        assert!((millis_after.checked_sub(millis_start as u64).unwrap()) < 90);
310        // Test a CUSTOM EPOCH in tenths of a millisecond
311        let custom_epoch = UNIX_EPOCH
312            .checked_add(Duration::from_millis(millis_start as u64))
313            .expect("ERROR: Failed to create custom epoch.");
314        let tenths_millis_custom_epoch_time = timestamp_from_custom_epoch(custom_epoch, 2).unwrap_or_else(
315            |error| {
316                panic!(
317                    "SequenceGeneratorSystemTimeError: Failed to get current timestamp from custom epoch {:?}, difference {:?}",
318                    UNIX_EPOCH, (error).duration()
319                )
320            });
321        // Wait a bit to prevent Option to call unwrap() on None below
322        // If both timestamps are within small margin substraction of u64
323        // can result in 'panicked at attempt to subtract with overflow'
324        // and checked_sub returns None value
325        sleep(Duration::from_millis(2));
326        // convert elapsed time from microseconds into tenths of a millisecond (0,1ms = 100 mcs)
327        let power_two: u32 = 2;
328        let tenths_millis_elapsed_time = (time_now.elapsed().map_or_else(
329            |error| {
330                panic!(
331                    "SequenceGeneratorSystemTimeError: Failed to get elapsed time, difference {:?}",
332                    (error).duration()
333                )
334            },
335            |duration| duration.as_micros() as u64,
336        )) / (10_u64).pow(power_two);
337        let substracted_times = tenths_millis_elapsed_time
338            .checked_sub(tenths_millis_custom_epoch_time)
339            .unwrap();
340        println!("Too high time difference between calculated time from\nCustom Epoch set at test start and actual elapsed\ntime since the test started.\n\nElapsed Time - Calculated Time Custom Epoch = {} mcs,\nexpected under 100 mcs\n\nPlease note that Pareto distribution applies and it\nis impossible to ensure a high enough difference for\nthe test not to fail on ocassion.\n\nReview only after ensuring repeated failures.\n", substracted_times);
341        // Substract custom epoch result with Rust's own elapsed time
342        // Upper boundary uncertainty set up high at 200mcs more than expected 511mcs as exponential
343        // distribution, CPU low-power states and/or older hardware can cause signifficant differences.
344        assert!(substracted_times < 200);
345    }
346
347    #[test]
348    fn wait_until() {
349        // Case where system clock is readjusted 50ms into the past
350        // Current sequence wouldn't be exhausted but script cools down
351        // until at least matching the previously stored timestamp.
352        use super::*;
353        use std::time::UNIX_EPOCH;
354        let calculated_time_after_50ms: u64 = SystemTime::now()
355            .checked_add(Duration::from_millis(50))
356            .unwrap()
357            .duration_since(UNIX_EPOCH)
358            .expect("ERROR: Failed to get duration from epoch of timestamp 50ms into the future.")
359            .as_millis() as u64;
360        // Function itself serves as an sleep call if correct
361        wait_until_last_timestamp(calculated_time_after_50ms, UNIX_EPOCH, 3, 1500).expect(
362            &format!(
363            "SequenceGeneratorSystemTimeError: Couldn't wait until timestamp '{}' with custom epoch '{:?}'",
364            calculated_time_after_50ms, UNIX_EPOCH
365        ),
366        );
367        // Wait a bit to prevent Option to call unwrap() on None below
368        // If both timestamps are within small margin substraction of u64
369        // can result in 'panicked at attempt to subtract with overflow'
370        // and checked_sub returns None value.
371        // Furthermore: It could also result in useless assert comparing
372        // if an unsigned integer is higher or equal to zero
373        sleep(Duration::from_millis(1));
374        let time_after_50ms: u64 = SystemTime::now()
375            .duration_since(UNIX_EPOCH)
376            .expect("ERROR: Failed to get current time as duration from epoch.")
377            .as_millis() as u64;
378        let substracted_times = time_after_50ms
379            .checked_sub(calculated_time_after_50ms)
380            .unwrap();
381        assert!(substracted_times > 0);
382        println!("Too high time difference while waiting for last timestamp\nafter clock moved backwards\n\nTime Calculated - Actual Time = {} ms, expected under 35 ms\n\nPlease note that Pareto distribution applies and it\nis impossible to ensure a high enough difference for\nthe test not to fail on ocassion.\n\nReview only after ensuring repeated failures.\n", substracted_times);
383        // Assert an upper boundary to how high of a difference there can be.
384        // If implementation is correct, the timestampts should be within few
385        // ms of one another according to a Normal distribution in recent
386        // hardware and normal CPU priority (although rather a Pareto
387        // distribution applies, making it impossible to set a value high
388        // enough for the test not to fail on ocassion)
389        assert!(substracted_times < 35);
390    }
391    #[test]
392    fn wait_next() {
393        // Case where sequence would be exhausted and for that reason
394        // script cools down until at least there exists a difference
395        // between the current system time and the last known timestamp.
396        use super::*;
397        use std::time::UNIX_EPOCH;
398        let calculated_time_after_10ms: u64 = SystemTime::now()
399            .checked_add(Duration::from_millis(10))
400            .expect("ERROR: Failed to 10ms to current timestamp.")
401            .duration_since(UNIX_EPOCH)
402            .expect("ERROR: Failed to get duration from epoch of timestamp 10ms into the future.")
403            .as_millis() as u64;
404        // Function itself serves as an sleep call if correct
405        wait_next_timestamp(calculated_time_after_10ms, UNIX_EPOCH, 3, 1500).unwrap_or_else(|_| {panic!(
406            "SequenceGeneratorSystemTimeError: Couldn't wait until timestamp '{}' with custom epoch '{:?}'",
407            calculated_time_after_10ms, UNIX_EPOCH
408        )});
409        let time_after_11ms: u64 = SystemTime::now()
410            .duration_since(UNIX_EPOCH)
411            .expect("ERROR: Failed to get current time as duration from epoch.")
412            .as_millis() as u64;
413        let substracted_times = time_after_11ms
414            .checked_sub(calculated_time_after_10ms)
415            .unwrap();
416        assert!(substracted_times > 0);
417        println!("Too high time difference while waiting for next timestamp\n\nNext timestamp - Last Timestamp = {} ms, expected under 35 ms\n\nPlease note that Pareto distribution applies and it\nis impossible to ensure a high enough difference for\nthe test not to fail on ocassion.\n\nReview only after ensuring repeated failures.\n", substracted_times);
418        // Assert an upper boundary to how high of a difference there can be.
419        // If implementation is correct, the timestampts should be within few
420        // ms of one another according to a Normal distribution in recent
421        // hardware and normal CPU priority (although rather a Pareto
422        // distribution applies, making it impossible to set a value high
423        // enough for the test not to fail on ocassion)
424        assert!(substracted_times < 35);
425    }
426    #[test]
427    fn gen_id() {
428        use super::*;
429        use rand::Rng;
430        // timestamp with 39 bits
431        let custom_epoch = SystemTime::now();
432        // 2^16 node id (up to 65536)
433        let node_id_bits = 16;
434        // Several unused bits
435        let unused_bits = 7;
436        // 2^2 sequence (up to 4)
437        // To test sequence overflow and wait behaviour, sequence bits unrealistically low
438        let sequence_bits = 2;
439        // in centiseconds (10^4 mcs)
440        let micros_ten_power = 4;
441        let mut rng = rand::rng();
442        // 0..2^16-1
443        let node_id = rng.random_range(0..65535);
444        // if stalled until next millisecond, begin exponential backoff at 1,5 mcs
445        let backoff_cooldown_start_ns = 1_000_000;
446        let last_timestamp = (SystemTime::now()
447            .duration_since(custom_epoch)
448            .expect("ERROR: Failed to get current time as duration from epoch.")
449            .as_millis()
450            / 10) as u64;
451        // Ensure a new fresh second
452        wait_next_timestamp(
453            last_timestamp,
454            custom_epoch,
455            micros_ten_power,
456            backoff_cooldown_start_ns,
457        )
458        .unwrap_or_else(|_| {panic!(
459            "SequenceGeneratorSystemTimeError: Couldn't wait until timestamp '{}' with custom epoch '{:?}'",
460            last_timestamp, custom_epoch
461        )});
462        let mut vector_ids: Vec<u64> = vec![0; 5];
463        let properties = SequenceProperties::new(
464            custom_epoch,
465            node_id_bits,
466            node_id,
467            sequence_bits,
468            micros_ten_power,
469            unused_bits,
470            backoff_cooldown_start_ns,
471        );
472        for element in vector_ids.iter_mut() {
473            *element = generate_id(&properties).unwrap_or_else(
474                |error| {
475                    panic!(
476                        "SequenceGeneratorSystemTimeError: Failed to get timestamp from custom epoch {:?}, difference {:?}",
477                        custom_epoch, (error).duration()
478                    )
479                });
480        }
481        let decoded_timestamp = decode_timestamp_micros(vector_ids[0], &properties);
482        assert!(((decoded_timestamp / 10_000) - (last_timestamp + 1)) < 15);
483        let mut decoded_node_id = decode_node_id(vector_ids[0], &properties);
484        assert_eq!(decoded_node_id, node_id);
485        let mut decoded_seq_id = decode_sequence_id(vector_ids[0], &properties);
486        assert_eq!(decoded_seq_id, 0);
487        for index in 1..5 {
488            decoded_seq_id = decode_sequence_id(vector_ids[index], &properties);
489            assert_eq!(decoded_seq_id, (index as u16) % 4);
490            decoded_node_id = decode_node_id(vector_ids[index], &properties);
491            assert_eq!(decoded_node_id, node_id);
492        }
493        assert!(properties.current_timestamp.borrow().unwrap() - last_timestamp < 15);
494    }
495}