Skip to content

Commit 60f4746

Browse files
committed
Use the ULID monotonic generator
1 parent 93fc233 commit 60f4746

File tree

1 file changed

+50
-2
lines changed

1 file changed

+50
-2
lines changed

src/lib.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use headers::{
4242
};
4343
use mime::Mime;
4444
use sha2::Digest;
45-
use tokio::sync::RwLock;
45+
use tokio::sync::{Mutex, RwLock};
4646
use tower_http::{
4747
cors::{Any, CorsLayer},
4848
limit::RequestBodyLimitLayer,
@@ -116,6 +116,7 @@ struct Sessions {
116116
// TODO: is that global lock alright?
117117
inner: Arc<RwLock<HashMap<Ulid, Session>>>,
118118
ttl: Duration,
119+
generator: Arc<Mutex<ulid::Generator>>,
119120
}
120121

121122
impl Sessions {
@@ -127,6 +128,16 @@ impl Sessions {
127128
self.inner.write().await.remove(&id);
128129
});
129130
}
131+
132+
async fn generate_id(&self) -> Ulid {
133+
self.generator
134+
.lock()
135+
.await
136+
.generate()
137+
// This would panic the thread if too many IDs (more than 2^40) are generated on the same
138+
// millisecond, which is very unlikely
139+
.expect("Failed to generate random ID")
140+
}
130141
}
131142

132143
impl Deref for Sessions {
@@ -159,7 +170,9 @@ async fn new_session(
159170
payload: Bytes,
160171
) -> impl IntoResponse {
161172
let ttl = sessions.ttl;
162-
let id = Ulid::new();
173+
174+
let id = sessions.generate_id().await;
175+
163176
let content_type =
164177
content_type.map_or(mime::APPLICATION_OCTET_STREAM, |TypedHeader(c)| c.into());
165178
let session = Session::new(payload, content_type, ttl);
@@ -240,6 +253,7 @@ where
240253
let sessions = Sessions {
241254
inner: Arc::default(),
242255
ttl,
256+
generator: Arc::default(),
243257
};
244258

245259
let state = AppState::new(sessions);
@@ -382,6 +396,40 @@ mod tests {
382396
assert_eq!(response.status(), StatusCode::NOT_FOUND);
383397
}
384398

399+
#[tokio::test]
400+
async fn test_monotonically_increasing() {
401+
let ttl = Duration::from_secs(60);
402+
let app = router("/", ttl, 4096);
403+
404+
// Prepare a thousand requests
405+
let mut requests = Vec::with_capacity(1000);
406+
for _ in 0..requests.capacity() {
407+
requests.push(
408+
app.clone()
409+
.oneshot(Request::post("/").body(String::new()).unwrap()),
410+
);
411+
}
412+
413+
// Run them all in order
414+
let mut responses = Vec::with_capacity(requests.len());
415+
for fut in requests {
416+
responses.push(fut.await);
417+
}
418+
419+
// Get the location out of them
420+
let ids: Vec<_> = responses
421+
.iter()
422+
.map(|res| {
423+
let res = res.as_ref().unwrap();
424+
assert_eq!(res.status(), StatusCode::CREATED);
425+
res.headers().get(LOCATION).unwrap().to_str().unwrap()
426+
})
427+
.collect();
428+
429+
// Check that all the IDs are monotonically increasing
430+
assert!(ids.windows(2).all(|loc| loc[0] < loc[1]));
431+
}
432+
385433
#[tokio::test]
386434
async fn test_post_max_bytes() {
387435
let ttl = Duration::from_secs(60);

0 commit comments

Comments
 (0)