ferroid/runtime/
tokio_snowflake.rs

1use crate::{Result, Snowflake, SnowflakeGenerator, TimeSource, TokioSleep};
2
3/// Extension trait for asynchronously generating Snowflake IDs using the
4/// [`tokio`](https://docs.rs/tokio) async runtime.
5///
6/// This trait provides a convenience method for using a [`SleepProvider`]
7/// backed by the `tokio` runtime, allowing you to call `.try_next_id_async()`
8/// without specifying the sleep strategy manually.
9///
10/// [`SleepProvider`]: crate::SleepProvider
11pub trait SnowflakeGeneratorAsyncTokioExt<ID, T>
12where
13    ID: Snowflake,
14    T: TimeSource<ID::Ty>,
15{
16    /// Returns a future that resolves to the next available Snowflake ID using
17    /// the [`TokioSleep`] provider.
18    ///
19    /// Internally delegates to
20    /// [`SnowflakeGeneratorAsyncExt::try_next_id_async`] method with
21    /// [`TokioSleep`] as the sleep strategy.
22    ///
23    /// # Errors
24    ///
25    /// This future may return an error if the underlying generator does.
26    ///
27    /// [`SnowflakeGeneratorAsyncExt::try_next_id_async`]:
28    ///     crate::SnowflakeGeneratorAsyncExt::try_next_id_async
29    fn try_next_id_async(&self) -> impl Future<Output = Result<ID>>;
30}
31
32impl<G, ID, T> SnowflakeGeneratorAsyncTokioExt<ID, T> for G
33where
34    G: SnowflakeGenerator<ID, T>,
35    ID: Snowflake,
36    T: TimeSource<ID::Ty>,
37{
38    fn try_next_id_async(&self) -> impl Future<Output = Result<ID>> {
39        <Self as crate::SnowflakeGeneratorAsyncExt<ID, T>>::try_next_id_async::<TokioSleep>(self)
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::{
47        AtomicSnowflakeGenerator, LockSnowflakeGenerator, MonotonicClock, Result, SleepProvider,
48        Snowflake, SnowflakeGenerator, SnowflakeTwitterId, TimeSource, TokioYield,
49    };
50    use core::fmt;
51    use futures::future::try_join_all;
52    use std::collections::HashSet;
53
54    const TOTAL_IDS: usize = 4096;
55    const NUM_GENERATORS: u64 = 8;
56    const IDS_PER_GENERATOR: usize = TOTAL_IDS * 8; // Enough to simulate at least 8 Pending cycles
57
58    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
59    async fn generates_many_unique_ids_lock_sleep() -> Result<()> {
60        test_many_snow_unique_ids_explicit::<SnowflakeTwitterId, _, _, TokioSleep>(
61            LockSnowflakeGenerator::new,
62            MonotonicClock::default,
63        )
64        .await?;
65        Ok(())
66    }
67    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
68    async fn generates_many_unique_ids_lock_yield() -> Result<()> {
69        test_many_snow_unique_ids_explicit::<SnowflakeTwitterId, _, _, TokioYield>(
70            LockSnowflakeGenerator::new,
71            MonotonicClock::default,
72        )
73        .await?;
74        Ok(())
75    }
76    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
77    async fn generates_many_unique_ids_lock_convenience() -> Result<()> {
78        test_many_snow_unique_ids_convenience::<SnowflakeTwitterId, _, _>(
79            LockSnowflakeGenerator::new,
80            MonotonicClock::default,
81        )
82        .await?;
83        Ok(())
84    }
85
86    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
87    async fn generates_many_unique_ids_atomic_sleep() -> Result<()> {
88        test_many_snow_unique_ids_explicit::<SnowflakeTwitterId, _, _, TokioSleep>(
89            AtomicSnowflakeGenerator::new,
90            MonotonicClock::default,
91        )
92        .await?;
93        Ok(())
94    }
95    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
96    async fn generates_many_unique_ids_atomic_yield() -> Result<()> {
97        test_many_snow_unique_ids_explicit::<SnowflakeTwitterId, _, _, TokioYield>(
98            AtomicSnowflakeGenerator::new,
99            MonotonicClock::default,
100        )
101        .await?;
102        Ok(())
103    }
104    #[tokio::test(flavor = "multi_thread", worker_threads = 8)]
105    async fn generates_many_unique_ids_atomic_convenience() -> Result<()> {
106        test_many_snow_unique_ids_convenience::<SnowflakeTwitterId, _, _>(
107            AtomicSnowflakeGenerator::new,
108            MonotonicClock::default,
109        )
110        .await?;
111        Ok(())
112    }
113
114    // Helper function for explicit SleepProvider testing
115    async fn test_many_snow_unique_ids_explicit<ID, G, T, S>(
116        generator_fn: impl Fn(u64, T) -> G,
117        clock_factory: impl Fn() -> T,
118    ) -> Result<()>
119    where
120        G: SnowflakeGenerator<ID, T> + Send + Sync + 'static,
121        ID: Snowflake + fmt::Debug + Send + 'static,
122        T: TimeSource<ID::Ty> + Clone + Send,
123        S: SleepProvider,
124    {
125        let clock = clock_factory();
126        let generators: Vec<_> = (0..NUM_GENERATORS)
127            .map(|machine_id| generator_fn(machine_id, clock.clone()))
128            .collect();
129
130        // Test explicit SleepProvider syntax
131        let tasks: Vec<tokio::task::JoinHandle<Result<_>>> = generators
132            .into_iter()
133            .map(|g| {
134                tokio::spawn(async move {
135                    let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
136                    for _ in 0..IDS_PER_GENERATOR {
137                        let id =
138                            crate::SnowflakeGeneratorAsyncExt::try_next_id_async::<S>(&g).await?;
139                        ids.push(id);
140                    }
141                    Ok(ids)
142                })
143            })
144            .collect();
145
146        validate_unique_snow_ids(tasks).await
147    }
148
149    // Helper function for convenience extension trait testing
150    async fn test_many_snow_unique_ids_convenience<ID, G, T>(
151        generator_fn: impl Fn(u64, T) -> G,
152        clock_factory: impl Fn() -> T,
153    ) -> Result<()>
154    where
155        G: SnowflakeGenerator<ID, T> + Send + Sync + 'static,
156        ID: Snowflake + fmt::Debug + Send + 'static,
157        T: TimeSource<ID::Ty> + Clone + Send,
158    {
159        let clock = clock_factory();
160        let generators: Vec<_> = (0..NUM_GENERATORS)
161            .map(|machine_id| generator_fn(machine_id, clock.clone()))
162            .collect();
163
164        // Test convenience extension trait syntax (uses TokioSleep by default)
165        let tasks: Vec<tokio::task::JoinHandle<Result<_>>> = generators
166            .into_iter()
167            .map(|g| {
168                tokio::spawn(async move {
169                    let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
170                    for _ in 0..IDS_PER_GENERATOR {
171                        // This uses the convenience method - no explicit
172                        // SleepProvider type!
173                        let id = g.try_next_id_async().await?;
174                        ids.push(id);
175                    }
176                    Ok(ids)
177                })
178            })
179            .collect();
180
181        validate_unique_snow_ids(tasks).await
182    }
183
184    // Helper to validate uniqueness - shared between test approaches
185    async fn validate_unique_snow_ids(
186        tasks: Vec<tokio::task::JoinHandle<Result<Vec<impl Snowflake + fmt::Debug>>>>,
187    ) -> Result<()> {
188        let all_ids: Vec<_> = try_join_all(tasks)
189            .await
190            .unwrap()
191            .into_iter()
192            .flat_map(Result::unwrap)
193            .collect();
194
195        let expected_total = NUM_GENERATORS as usize * IDS_PER_GENERATOR;
196        assert_eq!(
197            all_ids.len(),
198            expected_total,
199            "Expected {} IDs but got {}",
200            expected_total,
201            all_ids.len()
202        );
203
204        let mut seen = HashSet::with_capacity(all_ids.len());
205        for id in &all_ids {
206            assert!(seen.insert(id), "Duplicate ID found: {id:?}");
207        }
208
209        Ok(())
210    }
211}