1use crate::{RandSource, Result, SmolSleep, TimeSource, UlidGenerator, UlidId};
2use core::future::Future;
3
4pub trait UlidGeneratorAsyncSmolExt<ID, T, R>
13where
14 ID: UlidId,
15 T: TimeSource<ID::Ty>,
16 R: RandSource<ID::Ty>,
17{
18 type Err;
19 fn try_next_id_async(&self) -> impl Future<Output = Result<ID, Self::Err>>;
33}
34
35impl<G, ID, T, R> UlidGeneratorAsyncSmolExt<ID, T, R> for G
36where
37 G: UlidGenerator<ID, T, R>,
38 ID: UlidId,
39 T: TimeSource<ID::Ty>,
40 R: RandSource<ID::Ty>,
41{
42 type Err = G::Err;
43
44 #[allow(clippy::future_not_send)]
45 fn try_next_id_async(&self) -> impl Future<Output = Result<ID, Self::Err>> {
46 <Self as crate::UlidGeneratorAsyncExt<ID, T, R>>::try_next_id_async::<SmolSleep>(self)
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::{
54 LockMonoUlidGenerator, MonotonicClock, RandSource, Result, SleepProvider, SmolYield,
55 ThreadRandom, TimeSource, UlidGenerator, UlidId, ULID,
56 };
57 use futures::future::try_join_all;
58 use smol::Task;
59 use std::collections::HashSet;
60 use std::vec::Vec;
61
62 const TOTAL_IDS: usize = 4096;
63 const NUM_GENERATORS: u64 = 8;
64 const IDS_PER_GENERATOR: usize = TOTAL_IDS * 8;
65
66 #[test]
68 fn generates_many_unique_ids_basic_smol_sleep() {
69 smol::block_on(async {
70 test_many_ulid_unique_ids_explicit::<ULID, _, _, _, SmolSleep>(
71 LockMonoUlidGenerator::new,
72 MonotonicClock::default,
73 ThreadRandom::default,
74 )
75 .await
76 .unwrap();
77 });
78 }
79 #[test]
80 fn generates_many_unique_ids_basic_smol_yield() {
81 smol::block_on(async {
82 test_many_ulid_unique_ids_explicit::<ULID, _, _, _, SmolYield>(
83 LockMonoUlidGenerator::new,
84 MonotonicClock::default,
85 ThreadRandom::default,
86 )
87 .await
88 .unwrap();
89 });
90 }
91 #[test]
92 fn generates_many_unique_ids_basic_smol_convience() {
93 smol::block_on(async {
94 test_many_ulid_unique_ids_convenience::<ULID, _, _, _>(
95 LockMonoUlidGenerator::new,
96 MonotonicClock::default,
97 ThreadRandom::default,
98 )
99 .await
100 .unwrap();
101 });
102 }
103
104 async fn test_many_ulid_unique_ids_explicit<ID, G, T, R, S>(
106 generator_fn: impl Fn(T, R) -> G,
107 clock_factory: impl Fn() -> T,
108 rand_factory: impl Fn() -> R,
109 ) -> Result<()>
110 where
111 G: UlidGenerator<ID, T, R> + Send + Sync + 'static,
112 ID: UlidId + Send + 'static,
113 T: TimeSource<ID::Ty> + Clone + Send,
114 R: RandSource<ID::Ty> + Clone + Send,
115 S: SleepProvider,
116 {
117 let clock = clock_factory();
118 let rand = rand_factory();
119 let generators: Vec<_> = (0..NUM_GENERATORS)
120 .map(|_| generator_fn(clock.clone(), rand.clone()))
121 .collect();
122
123 let tasks: Vec<Task<Result<Vec<ID>>>> = generators
125 .into_iter()
126 .map(|g| {
127 smol::spawn(async move {
128 let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
129 for _ in 0..IDS_PER_GENERATOR {
130 let id = crate::UlidGeneratorAsyncExt::try_next_id_async::<S>(&g)
131 .await
132 .unwrap();
133 ids.push(id);
134 }
135 Ok(ids)
136 })
137 })
138 .collect();
139
140 validate_unique_ulid_ids(tasks).await
141 }
142
143 async fn test_many_ulid_unique_ids_convenience<ID, G, T, R>(
145 generator_fn: impl Fn(T, R) -> G,
146 clock_factory: impl Fn() -> T,
147 rand_factory: impl Fn() -> R,
148 ) -> Result<()>
149 where
150 G: UlidGenerator<ID, T, R> + Send + Sync + 'static,
151 G::Err: Send + Sync + 'static,
152 ID: UlidId + Send + 'static,
153 T: TimeSource<ID::Ty> + Clone + Send,
154 R: RandSource<ID::Ty> + Clone + Send,
155 {
156 let clock = clock_factory();
157 let rand = rand_factory();
158 let generators: Vec<_> = (0..NUM_GENERATORS)
159 .map(|_| generator_fn(clock.clone(), rand.clone()))
160 .collect();
161
162 let tasks: Vec<Task<Result<Vec<ID>>>> = generators
164 .into_iter()
165 .map(|g| {
166 smol::spawn(async move {
167 let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
168 for _ in 0..IDS_PER_GENERATOR {
169 let id = g.try_next_id_async().await.unwrap();
172 ids.push(id);
173 }
174 Ok(ids)
175 })
176 })
177 .collect();
178
179 validate_unique_ulid_ids(tasks).await
180 }
181
182 async fn validate_unique_ulid_ids(tasks: Vec<Task<Result<Vec<impl UlidId>>>>) -> Result<()> {
184 let all_ids: Vec<_> = try_join_all(tasks).await?.into_iter().flatten().collect();
185
186 #[allow(clippy::cast_possible_truncation)]
187 let expected_total = NUM_GENERATORS as usize * IDS_PER_GENERATOR;
188 assert_eq!(
189 all_ids.len(),
190 expected_total,
191 "Expected {} IDs but got {}",
192 expected_total,
193 all_ids.len()
194 );
195
196 let mut seen = HashSet::with_capacity(all_ids.len());
197 for id in &all_ids {
198 assert!(seen.insert(id), "Duplicate ID found: {id:?}");
199 }
200
201 Ok(())
202 }
203}