Struct TimestampIdGenerator

pub struct TimestampIdGenerator;
Expand description

Generator for creating TimestampId values with monotonicity guarantees.

TimestampIdGenerator is a stateless utility that encapsulates the clock access and monotonicity logic required to safely generate time-ordered identifiers. It ensures that generated timestamps are strictly increasing, even in scenarios where:

  • The system clock hasn’t advanced between calls
  • The system clock moves backward (clock skew)
  • Multiple IDs are generated in rapid succession

§Design Philosophy

This generator follows the principle of separating concerns:

This separation enables:

  • Easy testing of TimestampId without mocking time
  • Clear understanding of when system clock is accessed
  • Explicit control over monotonicity guarantees

§Monotonicity Guarantees

When a previous timestamp is provided, the generator ensures the new timestamp is strictly greater than the previous one. If the current time hasn’t advanced past the previous timestamp, the generator adds 1 millisecond to the previous value.

This guarantees total ordering of events:

T1 < T2 < T3 < ... < Tn

Even if generated at the same instant or during clock skew.

§Thread Safety and Synchronization

Important: While the generator itself is stateless and thread-safe, maintaining monotonicity across multiple threads requires external synchronization. The typical pattern is:

  1. Acquire a per-patient lock
  2. Read the most recent timestamp ID for that patient
  3. Generate the next ID using generate(Some(previous))
  4. Write the new ID
  5. Release the lock

This ensures that even with concurrent writes to different patients, each patient’s timeline remains strictly ordered.

§Millisecond Precision

The generator uses millisecond precision (3 decimal places). This means:

  • Maximum theoretical throughput: 1,000 events per second per patient
  • In practice, with locking overhead: ~100-500 events per second
  • For higher throughput scenarios, consider batching or sequence numbers

§Clock Skew Handling

If the system clock moves backward, the generator detects this by comparing against the previous timestamp and advances from the previous value instead of using the (earlier) current time. This prevents:

  • Timestamp collisions
  • Out-of-order events
  • Audit log inconsistencies

§Stateless Design

The generator is stateless - it doesn’t store any previous timestamps internally. This means:

  • No memory overhead per patient
  • No cleanup required
  • Caller controls what “previous” means (from database, cache, etc.)
  • Easy to use in distributed systems

§Performance Characteristics

  • Time complexity: O(1) - constant time for all operations
  • Memory: Zero-cost abstraction - compiles to a simple function call
  • System calls: One call to Utc::now() per invocation

§When to Use

Use this generator when you need:

  • Time-ordered event logging
  • Audit trails with strict ordering
  • Patient record versioning
  • Any scenario requiring monotonically increasing timestamps

§When Not to Use

Don’t use this generator for:

  • Random identifiers (use Uuid::new_v4() directly)
  • High-frequency events requiring sub-millisecond precision
  • Scenarios where logical clocks (Lamport/Vector clocks) are more appropriate

§See Also

  • TimestampId - The value object produced by this generator
  • Uuid - For the UUID component

Implementations§

§

impl TimestampIdGenerator

pub fn generate(previous: Option<&str>) -> Result<TimestampId, UuidError>

Generates a new TimestampId with optional monotonicity relative to a previous ID.

This is the primary method for creating time-ordered identifiers in the VPR system. It combines the current UTC time with a freshly generated UUID to create a globally unique, time-ordered identifier.

§Monotonicity Behavior
  • If previous is None: Uses the current system time (Utc::now())
  • If previous is Some(id): Ensures the new timestamp is strictly greater than the previous one, advancing by 1ms if necessary
§Arguments
  • previous - Optional string representation of the previous TimestampId. Must be in valid format if provided: YYYYMMDDTHHMMSS.mmmZ-<uuid>
§Returns
  • Ok(TimestampId) - A new identifier with guaranteed monotonicity
  • Err(UuidError) - If the previous string is malformed
§Errors

Returns [UuidError::InvalidInput] if previous is provided but cannot be parsed. This typically indicates:

  • Invalid timestamp format
  • Malformed UUID
  • Missing required components
§Monotonicity Algorithm
now = current_time()
if previous exists and now <= previous.timestamp:
    new_timestamp = previous.timestamp + 1ms
else:
    new_timestamp = now

This ensures strictly increasing timestamps even during:

  • Rapid successive calls
  • System clock adjustments
  • NTP corrections
§Thread Safety

This method is thread-safe but does not provide atomicity guarantees across multiple calls. For maintaining monotonicity across threads, wrap calls in a per-patient mutex or other synchronization primitive.

§Performance
  • Single system call to Utc::now()
  • Single UUID generation
  • Optional parsing if previous is provided
  • Total time: ~1-5 microseconds (depending on system)

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more