Skip to main content

AccountDataLock

Trait AccountDataLock 

Source
pub trait AccountDataLock: AccountDataQueries {
    // Provided methods
    async fn lock_room_account_data(
        &mut self,
        user_id: &UserId,
    ) -> Result<(), DatabaseError> { ... }
    async fn fetch_room_account_data_for_update<C>(
        &mut self,
        user_id: &UserId,
        room_id: &RoomId,
        event_type: &RoomAccountDataEventType,
    ) -> Result<Option<RoomAccountDataEvent<C>>, DatabaseError>
       where C: RoomAccountDataEventContent + DeserializeOwned,
             Self: Sized { ... }
}
Expand description

Extension trait to acquire a per-user advisory lock for account data writes, held for the duration of the current transaction.

Provided Methods§

Source

async fn lock_room_account_data( &mut self, user_id: &UserId, ) -> Result<(), DatabaseError>

Acquire a transaction-scoped, per-user advisory lock for account data writes.

This serializes every writer performing a read-modify-write on a user’s account data (e.g. the tag handlers, which read the m.tag blob, modify it in Rust, and upsert it back). It must be called before the read that a subsequent write depends on, so that the whole read-then-write sequence is atomic with respect to other writers. Without it, two concurrent writers can both read the same blob and the loser’s update is silently overwritten (lost update, see issue #688).

pg_advisory_xact_lock is preferred over SELECT ... FOR UPDATE on the room_account_data row: FOR UPDATE only locks rows that already exist, so two concurrent writers inserting the first row for a (user, room, type) key would still race. The advisory lock also releases automatically on commit or rollback and is re-entrant within the same session.

The two-key (room_account_data_oid, users.nid) form is deliberate — do NOT reduce it to pg_advisory_xact_lock(nid::bigint). A small nid in the bigint form is stored as classid = 0, the space max_safe_seq scans for in-flight stream id values (see 00_locking_sequence.sql). Since account data writes allocate a stream id in the same transaction, a classid = 0 lock would collapse the sync watermark to the user’s nid and stall /sync. Namespacing by table OID keeps the lock out of that scan.

Locking on users.nid serializes all account data writes of one user (across rooms and data types). That is coarser than strictly needed, but account data writes are low-frequency and this keeps the lock key a plain int4 without hashing.

If the user does not exist no lock is taken — handlers only run for authenticated (i.e. existing) users.

Source

async fn fetch_room_account_data_for_update<C>( &mut self, user_id: &UserId, room_id: &RoomId, event_type: &RoomAccountDataEventType, ) -> Result<Option<RoomAccountDataEvent<C>>, DatabaseError>
where C: RoomAccountDataEventContent + DeserializeOwned, Self: Sized,

Retrieve room account data for a user in a specific room, taking the per-user account data lock (Self::lock_room_account_data) first.

Use this instead of fetch_room_account_data whenever the fetched value is modified and written back within the same transaction (read-modify-write), so concurrent writers are serialized and cannot overwrite each other’s updates (#688).

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl AccountDataLock for TransactedDb<'_>

The lock is transaction-scoped (pg_advisory_xact_lock releases on commit or rollback), so it is only implemented for TransactedDb: taking it on a plain connection would release it again right after the statement, providing no protection.

Implementors§