1use crate::store_utils::{DEFAULT_TIMEOUT, get_with_timeout, put_with_timeout};
11use anyhow::Result;
12use bytes::Bytes;
13use object_store::path::Path;
14use object_store::{ObjectStore, PutMode, PutOptions, UpdateVersion};
15use serde::{Deserialize, Serialize};
16use std::sync::Arc;
17use tokio::sync::Mutex;
18use uni_common::core::id::{Eid, Vid};
19
20#[derive(Serialize, Deserialize, Default, Clone)]
22struct CounterManifest {
23 next_vid_batch: u64,
25 next_eid_batch: u64,
27}
28
29struct AllocatorState {
31 manifest: CounterManifest,
32 manifest_version: Option<String>, current_vid: u64,
34 current_eid: u64,
35}
36
37pub struct IdAllocator {
44 store: Arc<dyn ObjectStore>,
45 path: Path,
46 state: Mutex<AllocatorState>,
47 batch_size: u64,
48}
49
50impl IdAllocator {
51 pub async fn new(store: Arc<dyn ObjectStore>, path: Path, batch_size: u64) -> Result<Self> {
53 let (manifest, version) = match get_with_timeout(&store, &path, DEFAULT_TIMEOUT).await {
54 Ok(get_result) => {
55 let version = get_result.meta.e_tag.clone();
56 let bytes = get_result.bytes().await?;
57 let manifest: CounterManifest = serde_json::from_slice(&bytes)?;
58 (manifest, version)
59 }
60 Err(e) if crate::store_utils::is_not_found(&e) => (CounterManifest::default(), None),
66 Err(e) => return Err(e),
67 };
68
69 let current_vid = manifest.next_vid_batch;
71 let current_eid = manifest.next_eid_batch;
72
73 Ok(Self {
74 store,
75 path,
76 state: Mutex::new(AllocatorState {
77 manifest,
78 manifest_version: version,
79 current_vid,
80 current_eid,
81 }),
82 batch_size,
83 })
84 }
85
86 pub async fn allocate_vid(&self) -> Result<Vid> {
90 let mut state = self.state.lock().await;
91
92 if state.current_vid >= state.manifest.next_vid_batch {
94 let reserved = state
99 .current_vid
100 .checked_add(self.batch_size)
101 .ok_or_else(|| anyhow::anyhow!("VID space exhausted"))?;
102 let prev = state.manifest.next_vid_batch;
103 state.manifest.next_vid_batch = reserved;
104 if let Err(e) = self.persist_manifest(&mut state).await {
105 state.manifest.next_vid_batch = prev;
110 return Err(e);
111 }
112 }
113
114 let vid = Vid::new(state.current_vid);
115 state.current_vid += 1;
116 Ok(vid)
117 }
118
119 pub async fn allocate_vids(&self, count: usize) -> Result<Vec<Vid>> {
121 let mut state = self.state.lock().await;
122 let needed = count as u64;
123
124 let want = state
126 .current_vid
127 .checked_add(needed)
128 .ok_or_else(|| anyhow::anyhow!("VID space exhausted"))?;
129 if want > state.manifest.next_vid_batch {
130 let reserved = want
132 .checked_add(self.batch_size)
133 .ok_or_else(|| anyhow::anyhow!("VID space exhausted"))?;
134 let prev = state.manifest.next_vid_batch;
135 state.manifest.next_vid_batch = reserved;
136 if let Err(e) = self.persist_manifest(&mut state).await {
137 state.manifest.next_vid_batch = prev;
139 return Err(e);
140 }
141 }
142
143 let vids: Vec<Vid> = (0..count)
144 .map(|i| Vid::new(state.current_vid + i as u64))
145 .collect();
146 state.current_vid += needed;
147 Ok(vids)
148 }
149
150 pub async fn allocate_eid(&self) -> Result<Eid> {
154 let mut state = self.state.lock().await;
155
156 if state.current_eid >= state.manifest.next_eid_batch {
158 let reserved = state
160 .current_eid
161 .checked_add(self.batch_size)
162 .ok_or_else(|| anyhow::anyhow!("EID space exhausted"))?;
163 let prev = state.manifest.next_eid_batch;
164 state.manifest.next_eid_batch = reserved;
165 if let Err(e) = self.persist_manifest(&mut state).await {
166 state.manifest.next_eid_batch = prev;
168 return Err(e);
169 }
170 }
171
172 let eid = Eid::new(state.current_eid);
173 state.current_eid += 1;
174 Ok(eid)
175 }
176
177 pub async fn allocate_eids(&self, count: usize) -> Result<Vec<Eid>> {
179 let mut state = self.state.lock().await;
180 let needed = count as u64;
181
182 let want = state
184 .current_eid
185 .checked_add(needed)
186 .ok_or_else(|| anyhow::anyhow!("EID space exhausted"))?;
187 if want > state.manifest.next_eid_batch {
188 let reserved = want
190 .checked_add(self.batch_size)
191 .ok_or_else(|| anyhow::anyhow!("EID space exhausted"))?;
192 let prev = state.manifest.next_eid_batch;
193 state.manifest.next_eid_batch = reserved;
194 if let Err(e) = self.persist_manifest(&mut state).await {
195 state.manifest.next_eid_batch = prev;
197 return Err(e);
198 }
199 }
200
201 let eids: Vec<Eid> = (0..count)
202 .map(|i| Eid::new(state.current_eid + i as u64))
203 .collect();
204 state.current_eid += needed;
205 Ok(eids)
206 }
207
208 pub async fn current_vid(&self) -> u64 {
210 self.state.lock().await.current_vid
211 }
212
213 pub async fn current_eid(&self) -> u64 {
215 self.state.lock().await.current_eid
216 }
217
218 pub async fn current_hwm(&self) -> (u64, u64) {
227 let state = self.state.lock().await;
228 (state.current_vid, state.current_eid)
229 }
230
231 pub async fn in_memory_seeded(vid_hwm: u64, eid_hwm: u64, batch_size: u64) -> Result<Self> {
242 let store: Arc<dyn ObjectStore> = Arc::new(object_store::memory::InMemory::new());
243 let path = Path::from("scratch_id_allocator.json");
244 let manifest = CounterManifest {
245 next_vid_batch: vid_hwm,
246 next_eid_batch: eid_hwm,
247 };
248 let bytes = Bytes::from(serde_json::to_vec(&manifest)?);
249 put_with_timeout(&store, &path, bytes, DEFAULT_TIMEOUT).await?;
250 Self::new(store, path, batch_size).await
251 }
252
253 pub async fn checkpoint(&self) -> Result<()> {
271 let mut state = self.state.lock().await;
272 if state.manifest.next_vid_batch < state.current_vid {
275 state.manifest.next_vid_batch = state.current_vid;
276 }
277 if state.manifest.next_eid_batch < state.current_eid {
278 state.manifest.next_eid_batch = state.current_eid;
279 }
280 self.persist_manifest(&mut state).await
281 }
282
283 async fn persist_manifest(&self, state: &mut AllocatorState) -> Result<()> {
285 let json = serde_json::to_vec_pretty(&state.manifest)?;
286 let bytes = Bytes::from(json);
287
288 let put_result = if let Some(version) = &state.manifest_version {
291 let opts: PutOptions = PutMode::Update(UpdateVersion {
292 e_tag: Some(version.clone()),
293 version: None,
294 })
295 .into();
296 match tokio::time::timeout(
297 DEFAULT_TIMEOUT,
298 self.store.put_opts(&self.path, bytes.clone().into(), opts),
299 )
300 .await
301 {
302 Ok(Ok(result)) => result,
303 Ok(Err(e))
304 if e.to_string().contains("not yet implemented")
305 || e.to_string().contains("not supported") =>
306 {
307 put_with_timeout(&self.store, &self.path, bytes, DEFAULT_TIMEOUT).await?
309 }
310 Ok(Err(e)) => return Err(e.into()),
311 Err(_) => {
312 return Err(anyhow::anyhow!(
313 "Object store put_opts timed out after {:?}",
314 DEFAULT_TIMEOUT
315 ));
316 }
317 }
318 } else {
319 let opts: PutOptions = PutMode::Create.into();
321 match tokio::time::timeout(
322 DEFAULT_TIMEOUT,
323 self.store.put_opts(&self.path, bytes.clone().into(), opts),
324 )
325 .await
326 {
327 Ok(Ok(result)) => result,
328 Ok(Err(object_store::Error::AlreadyExists { .. })) => {
329 put_with_timeout(&self.store, &self.path, bytes, DEFAULT_TIMEOUT).await?
331 }
332 Ok(Err(e)) if e.to_string().contains("not yet implemented") => {
333 put_with_timeout(&self.store, &self.path, bytes, DEFAULT_TIMEOUT).await?
334 }
335 Ok(Err(e)) => return Err(e.into()),
336 Err(_) => {
337 return Err(anyhow::anyhow!(
338 "Object store put_opts timed out after {:?}",
339 DEFAULT_TIMEOUT
340 ));
341 }
342 }
343 };
344
345 state.manifest_version = put_result.e_tag;
346 Ok(())
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353 use object_store::memory::InMemory;
354
355 #[tokio::test]
356 async fn test_allocate_vid() {
357 let store = Arc::new(InMemory::new());
358 let path = Path::from("id_counters.json");
359 let allocator = IdAllocator::new(store, path, 100).await.unwrap();
360
361 let vid1 = allocator.allocate_vid().await.unwrap();
362 let vid2 = allocator.allocate_vid().await.unwrap();
363 let vid3 = allocator.allocate_vid().await.unwrap();
364
365 assert_eq!(vid1.as_u64(), 0);
366 assert_eq!(vid2.as_u64(), 1);
367 assert_eq!(vid3.as_u64(), 2);
368 }
369
370 #[tokio::test]
371 async fn test_allocate_eid() {
372 let store = Arc::new(InMemory::new());
373 let path = Path::from("id_counters.json");
374 let allocator = IdAllocator::new(store, path, 100).await.unwrap();
375
376 let eid1 = allocator.allocate_eid().await.unwrap();
377 let eid2 = allocator.allocate_eid().await.unwrap();
378
379 assert_eq!(eid1.as_u64(), 0);
380 assert_eq!(eid2.as_u64(), 1);
381 }
382
383 #[tokio::test]
384 async fn test_allocate_many() {
385 let store = Arc::new(InMemory::new());
386 let path = Path::from("id_counters.json");
387 let allocator = IdAllocator::new(store, path, 100).await.unwrap();
388
389 let vids = allocator.allocate_vids(5).await.unwrap();
390 assert_eq!(vids.len(), 5);
391 for (i, vid) in vids.iter().enumerate() {
392 assert_eq!(vid.as_u64(), i as u64);
393 }
394
395 let next = allocator.allocate_vid().await.unwrap();
397 assert_eq!(next.as_u64(), 5);
398 }
399
400 #[tokio::test]
401 async fn test_persistence() {
402 let store = Arc::new(InMemory::new());
403 let path = Path::from("id_counters.json");
404
405 {
407 let allocator = IdAllocator::new(store.clone(), path.clone(), 10)
408 .await
409 .unwrap();
410 for _ in 0..15 {
411 allocator.allocate_vid().await.unwrap();
412 }
413 }
414
415 {
417 let allocator = IdAllocator::new(store, path, 10).await.unwrap();
418 let vid = allocator.allocate_vid().await.unwrap();
421 assert_eq!(vid.as_u64(), 20);
422 }
423 }
424}