fix: Avoid v12 room ID collisions when creating rooms concurrently

Fixes #2424. v12 rooms created by the same user in the same millisecond got the same room ID, so they ended up as one room.

Reviewed-on: https://forgejo.ellis.link/continuwuation/continuwuity/pulls/2433
Reviewed-by: nex <me@nexy7574.co.uk>
Reviewed-by: Jade Ellis <jade@ellis.link>
This commit is contained in:
Erwan Leboucher authored and Ellis Git committed 2026-11-03 09:52:43 +00:00
1 parent 0e5baa9399
commit 0727d564a2
2 files changed
+21 -4

No files matched your search

+1
View File
@@ -0,0 +1 @@
Fix v12 rooms created by the same user in the same millisecond ending up as the same room. Contributed by @eleboucher.
+20 -4
View File
@@ -1,18 +1,21 @@
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::{
collections::{BTreeMap, BTreeSet, HashSet},
sync::atomic::{AtomicU64, Ordering::SeqCst},
};
use axum::extract::State;
use conduwuit::{
Err, Error, Result, debug, debug_info, err, info,
matrix::{StateKey, pdu::PartialPdu},
trace,
utils::to_canonical_object,
utils::{self, to_canonical_object},
warn,
};
use conduwuit_service::{Services, appservice::RegistrationInfo};
use futures::FutureExt;
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, Int, MilliSecondsSinceUnixEpoch, OwnedRoomAliasId,
OwnedUserId, RoomAliasId, RoomId,
OwnedUserId, RoomAliasId, RoomId, UInt,
api::{
client::room::{
self,
@@ -46,6 +49,8 @@ use serde_json::value::to_raw_value;
use crate::{Ruma, client::invite_helper};
static LAST_CREATE_TS: AtomicU64 = AtomicU64::new(0);
// We use this to keep around extra data in the `creation_content`
// while still parsing and validating keys that we care about.
#[derive(Deserialize, Default)]
@@ -307,6 +312,17 @@ pub(crate) async fn create_room_route(
.map(MilliSecondsSinceUnixEpoch)
};
// v12+ room IDs are the create event's hash, so give each create event a
// unique timestamp to avoid collisions.
let create_timestamp = custom_origin_server_ts.or_else(|| {
room_id.is_none().then(|| {
let now = utils::millis_since_unix_epoch();
let next = |last: u64| now.max(last.saturating_add(1));
let prev = LAST_CREATE_TS.update(SeqCst, SeqCst, next);
MilliSecondsSinceUnixEpoch(UInt::new_saturating(next(prev)))
})
});
let mut create_event_content = to_canonical_object(create_event_content)
.expect("should be able to serialize create event content");
@@ -322,7 +338,7 @@ pub(crate) async fn create_room_route(
event_type: TimelineEventType::RoomCreate,
content: to_raw_value(&create_event_content)?,
state_key: Some(StateKey::new()),
timestamp: custom_origin_server_ts,
timestamp: create_timestamp,
..Default::default()
},
sender_user,