Skip to content

Commit 89eabe2

Browse files
authored
Merge pull request #4 from matrix-org/quenting/ulid
2 parents 677d42a + 60f4746 commit 89eabe2

File tree

2 files changed

+58
-11
lines changed

2 files changed

+58
-11
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tokio = { version = "1.21.2", features = ["sync", "time"] }
2121
tower = { version = "0.4.13", features = ["util"] }
2222
tower-http = { version = "0.3.4", features = ["cors", "limit", "set-header"] }
2323
tracing = "0.1.37"
24-
uuid = { version = "1.1.2", features = ["v4", "fast-rng", "serde"] }
24+
ulid = { version = "1.0.0", features = ["serde"] }
2525

2626
[dev-dependencies]
2727
hyper = "0.14.20"

src/lib.rs

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ 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,
4949
set_header::SetResponseHeaderLayer,
5050
};
51-
use uuid::Uuid;
51+
use ulid::Ulid;
5252

5353
struct Session {
5454
hash: [u8; 32],
@@ -114,23 +114,34 @@ impl Session {
114114
#[derive(Clone, Default)]
115115
struct Sessions {
116116
// TODO: is that global lock alright?
117-
inner: Arc<RwLock<HashMap<Uuid, Session>>>,
117+
inner: Arc<RwLock<HashMap<Ulid, Session>>>,
118118
ttl: Duration,
119+
generator: Arc<Mutex<ulid::Generator>>,
119120
}
120121

121122
impl Sessions {
122-
async fn insert(self, id: Uuid, session: Session, ttl: Duration) {
123+
async fn insert(self, id: Ulid, session: Session, ttl: Duration) {
123124
self.inner.write().await.insert(id, session);
124125
// TODO: cancel this task when an item gets deleted
125126
tokio::task::spawn(async move {
126127
tokio::time::sleep(ttl).await;
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 {
133-
type Target = RwLock<HashMap<Uuid, Session>>;
144+
type Target = RwLock<HashMap<Ulid, Session>>;
134145

135146
fn deref(&self) -> &Self::Target {
136147
&self.inner
@@ -159,8 +170,9 @@ async fn new_session(
159170
payload: Bytes,
160171
) -> impl IntoResponse {
161172
let ttl = sessions.ttl;
162-
// TODO: should we use something else? Check for colisions?
163-
let id = Uuid::new_v4();
173+
174+
let id = sessions.generate_id().await;
175+
164176
let content_type =
165177
content_type.map_or(mime::APPLICATION_OCTET_STREAM, |TypedHeader(c)| c.into());
166178
let session = Session::new(payload, content_type, ttl);
@@ -172,7 +184,7 @@ async fn new_session(
172184
(StatusCode::CREATED, headers, additional_headers)
173185
}
174186

175-
async fn delete_session(State(sessions): State<Sessions>, Path(id): Path<Uuid>) -> StatusCode {
187+
async fn delete_session(State(sessions): State<Sessions>, Path(id): Path<Ulid>) -> StatusCode {
176188
if sessions.write().await.remove(&id).is_some() {
177189
StatusCode::NO_CONTENT
178190
} else {
@@ -182,7 +194,7 @@ async fn delete_session(State(sessions): State<Sessions>, Path(id): Path<Uuid>)
182194

183195
async fn update_session(
184196
State(sessions): State<Sessions>,
185-
Path(id): Path<Uuid>,
197+
Path(id): Path<Ulid>,
186198
content_type: Option<TypedHeader<ContentType>>,
187199
if_match: Option<TypedHeader<IfMatch>>,
188200
payload: Bytes,
@@ -206,7 +218,7 @@ async fn update_session(
206218

207219
async fn get_session(
208220
State(sessions): State<Sessions>,
209-
Path(id): Path<Uuid>,
221+
Path(id): Path<Ulid>,
210222
if_none_match: Option<TypedHeader<IfNoneMatch>>,
211223
) -> Response {
212224
let sessions = sessions.read().await;
@@ -241,6 +253,7 @@ where
241253
let sessions = Sessions {
242254
inner: Arc::default(),
243255
ttl,
256+
generator: Arc::default(),
244257
};
245258

246259
let state = AppState::new(sessions);
@@ -383,6 +396,40 @@ mod tests {
383396
assert_eq!(response.status(), StatusCode::NOT_FOUND);
384397
}
385398

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+
386433
#[tokio::test]
387434
async fn test_post_max_bytes() {
388435
let ttl = Duration::from_secs(60);

0 commit comments

Comments
 (0)