ferroid/generator/ulid/
lock_mono.rs

1use crate::{
2    rand::RandSource, Error, IdGenStatus, Mutex, Result, TimeSource, UlidGenerator, UlidId,
3};
4use alloc::sync::Arc;
5use core::cmp::Ordering;
6#[cfg(feature = "tracing")]
7use tracing::instrument;
8
9/// A lock-based *monotonic* ULID-style ID generator suitable for multi-threaded
10/// environments.
11///
12/// This generator wraps the Ulid state in an [`Arc<Mutex<_>>`], allowing safe
13/// shared use across threads.
14///
15/// ## Features
16/// - ✅ Thread-safe
17/// - ✅ Probabilistically unique (no coordination required)
18/// - ✅ Time-ordered (monotonically increasing per millisecond)
19///
20/// ## Recommended When
21/// - You're in a multi-threaded environment
22/// - You need require monotonically increasing IDs (ID generated within the
23///   same millisecond increment a sequence counter)
24/// - Your target doesn't support atomics.
25///
26/// ## See Also
27/// - [`BasicUlidGenerator`]
28/// - [`BasicMonoUlidGenerator`]
29/// - [`AtomicMonoUlidGenerator`]
30///
31/// [`BasicUlidGenerator`]: crate::BasicUlidGenerator
32/// [`BasicMonoUlidGenerator`]: crate::BasicMonoUlidGenerator
33/// [`AtomicMonoUlidGenerator`]: crate::AtomicMonoUlidGenerator
34pub struct LockMonoUlidGenerator<ID, T, R>
35where
36    ID: UlidId,
37    T: TimeSource<ID::Ty>,
38    R: RandSource<ID::Ty>,
39{
40    #[cfg(feature = "cache-padded")]
41    state: Arc<crossbeam_utils::CachePadded<Mutex<ID>>>,
42    #[cfg(not(feature = "cache-padded"))]
43    state: Arc<Mutex<ID>>,
44    time: T,
45    rng: R,
46}
47
48impl<ID, T, R> LockMonoUlidGenerator<ID, T, R>
49where
50    ID: UlidId,
51    T: TimeSource<ID::Ty>,
52    R: RandSource<ID::Ty>,
53{
54    /// Creates a new [`LockMonoUlidGenerator`] with the provided time source and
55    /// RNG.
56    ///
57    /// # Parameters
58    /// - `time`: A [`TimeSource`] used to retrieve the current timestamp
59    /// - `rng`: A [`RandSource`] used to generate random bits
60    ///
61    /// # Returns
62    /// A ready-to-use ULID generator suitable for producing unique, sortable
63    /// IDs.
64    ///
65    /// # Example
66    /// ```
67    /// use ferroid::{LockMonoUlidGenerator, IdGenStatus, ULID, MonotonicClock, ThreadRandom};
68    ///
69    /// let generator = LockMonoUlidGenerator::new(MonotonicClock::default(), ThreadRandom::default());
70    ///
71    /// let id: ULID = loop {
72    ///     match generator.next_id() {
73    ///         IdGenStatus::Ready { id } => break id,
74    ///         IdGenStatus::Pending { .. } => core::hint::spin_loop(),
75    ///     }
76    /// };
77    /// ```
78    ///
79    /// [`TimeSource`]: crate::TimeSource
80    /// [`RandSource`]: crate::RandSource
81    pub fn new(time: T, rng: R) -> Self {
82        Self::from_components(ID::ZERO, ID::ZERO, time, rng)
83    }
84
85    /// Creates a new ID generator from explicit component values.
86    ///
87    /// This constructor is primarily useful for advanced use cases such as
88    /// restoring state from persistent storage or controlling the starting
89    /// point of the generator manually.
90    ///
91    /// # Parameters
92    /// - `timestamp`: The initial timestamp component (usually in milliseconds)
93    /// - `machine_id`: The machine or worker identifier
94    /// - `sequence`: The initial sequence number
95    /// - `time`: A [`TimeSource`] implementation used to fetch the current
96    ///   time
97    ///
98    /// # Returns
99    /// A new generator instance preloaded with the given state.
100    ///
101    /// # ⚠️ Note
102    /// In typical use cases, you should prefer [`Self::new`] to let the
103    /// generator initialize itself from the current time.
104    pub fn from_components(timestamp: ID::Ty, random: ID::Ty, time: T, rng: R) -> Self {
105        let id = ID::from_components(timestamp, random);
106        Self {
107            #[cfg(feature = "cache-padded")]
108            state: Arc::new(crossbeam_utils::CachePadded::new(Mutex::new(id))),
109            #[cfg(not(feature = "cache-padded"))]
110            state: Arc::new(Mutex::new(id)),
111            time,
112            rng,
113        }
114    }
115
116    /// Generates a new ULID.
117    ///
118    /// Internally calls [`Self::try_next_id`] and unwraps the result. This
119    /// method will panic on error, so prefer the fallible version if you want
120    /// explicit control over error handling.
121    ///
122    /// # Panics
123    /// Panics if the lock is poisoned. For explicitly fallible behavior, use
124    /// [`Self::try_next_id`] instead.
125    ///
126    /// # Example
127    /// ```
128    /// use ferroid::{LockMonoUlidGenerator, IdGenStatus, ULID, MonotonicClock, ThreadRandom};
129    ///
130    /// let generator = LockMonoUlidGenerator::new(MonotonicClock::default(), ThreadRandom::default());
131    ///
132    /// let id: ULID = loop {
133    ///     match generator.next_id() {
134    ///         IdGenStatus::Ready { id } => break id,
135    ///         IdGenStatus::Pending { .. } => std::thread::yield_now(),
136    ///     }
137    /// };
138    /// ```
139    pub fn next_id(&self) -> IdGenStatus<ID> {
140        self.try_next_id().unwrap()
141    }
142
143    /// Attempts to generate a new ULID with fallible error handling.
144    ///
145    /// Combines the current timestamp with a freshly generated random value to
146    /// produce a unique identifier. Returns [`IdGenStatus::Ready`] on success.
147    ///
148    /// # Returns
149    /// - `Ok(IdGenStatus::Ready { id })`: A new ID is available
150    /// - `Ok(IdGenStatus::Pending { yield_for })`: The time to wait (in
151    ///   milliseconds) before trying again
152    /// - `Err(e)`: the lock was poisoned
153    ///
154    /// # Errors
155    /// - Returns an error if the underlying lock has been poisoned.
156    ///
157    /// # Example
158    /// ```
159    /// use ferroid::{LockMonoUlidGenerator, IdGenStatus, ULID, ToU64, MonotonicClock, ThreadRandom};
160    ///
161    /// let generator = LockMonoUlidGenerator::new(MonotonicClock::default(), ThreadRandom::default());
162    ///
163    /// // Attempt to generate a new ID
164    /// let id: ULID = loop {
165    ///     match generator.try_next_id() {
166    ///         Ok(IdGenStatus::Ready { id }) => break id,
167    ///         Ok(IdGenStatus::Pending { yield_for }) => {
168    ///             std::thread::sleep(core::time::Duration::from_millis(yield_for.to_u64()));
169    ///         }
170    ///         Err(e) => panic!("Generator error: {}", e),
171    ///     }
172    /// };
173    /// ```
174    #[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self)))]
175    pub fn try_next_id(&self) -> Result<IdGenStatus<ID>, Error<core::convert::Infallible>> {
176        let now = self.time.current_millis();
177
178        #[cfg(feature = "parking-lot")]
179        let mut id = self.state.lock();
180        #[cfg(not(feature = "parking-lot"))]
181        let mut id = self.state.lock()?;
182
183        let current_ts = id.timestamp();
184
185        match now.cmp(&current_ts) {
186            Ordering::Equal => {
187                if id.has_random_room() {
188                    *id = id.increment_random();
189                    Ok(IdGenStatus::Ready { id: *id })
190                } else {
191                    Ok(IdGenStatus::Pending { yield_for: ID::ONE })
192                }
193            }
194            Ordering::Greater => {
195                let rand = self.rng.rand();
196                *id = id.rollover_to_timestamp(now, rand);
197                Ok(IdGenStatus::Ready { id: *id })
198            }
199            Ordering::Less => Ok(Self::cold_clock_behind(now, current_ts)),
200        }
201    }
202
203    #[cold]
204    #[inline(never)]
205    fn cold_clock_behind(now: ID::Ty, current_ts: ID::Ty) -> IdGenStatus<ID> {
206        let yield_for = current_ts - now;
207        debug_assert!(yield_for >= ID::ZERO);
208        IdGenStatus::Pending { yield_for }
209    }
210}
211
212impl<ID, T, R> UlidGenerator<ID, T, R> for LockMonoUlidGenerator<ID, T, R>
213where
214    ID: UlidId,
215    T: TimeSource<ID::Ty>,
216    R: RandSource<ID::Ty>,
217{
218    type Err = Error;
219
220    fn new(time: T, rng: R) -> Self {
221        Self::new(time, rng)
222    }
223
224    fn next_id(&self) -> IdGenStatus<ID> {
225        self.next_id()
226    }
227
228    fn try_next_id(&self) -> Result<IdGenStatus<ID>, Self::Err> {
229        self.try_next_id()
230    }
231}