From 52950362ff0b35157d6de977d4d9e7e7fb404739 Mon Sep 17 00:00:00 2001 From: nolt Date: Mon, 27 Jul 2026 11:33:47 +0200 Subject: [PATCH] Document the cross-player persistence lock-ordering invariant The packet handler funnel now holds each player's persistence lock for the whole handler. Acquiring a second player's lock from inside a handler is therefore a lock-ordering hazard; note the invariant on the guard method so a future cross-player save cannot silently open an AB-BA cycle. Documentation only, no behavioural change. --- src/GameLogic/Player.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/GameLogic/Player.cs b/src/GameLogic/Player.cs index a7f316e..6e55ec8 100644 --- a/src/GameLogic/Player.cs +++ b/src/GameLogic/Player.cs @@ -1917,6 +1917,14 @@ public class Player : AsyncDisposable, IBucketMapObserver, IAttackable, IAttacke /// save fails too, so the whole session is lost on relog. Serializing the packet handler funnel /// and the save against each other closes that window. The lock is re-entrant per asynchronous /// flow, so an inline save inside an already-serialized handler does not deadlock. + /// + /// Invariant: never acquire another player's persistence lock (via their + /// or ) from inside a + /// packet handler, which already holds this player's lock, unless a global lock order is enforced. + /// Today only the trade accept does a cross-player save, and it cannot form a cycle because a trade + /// has a single accepting side (so the A-then-B acquisition order has no concurrent B-then-A + /// counterpart). A second cross-player caller with the opposite order could deadlock. + /// /// /// The result type of the operation. /// The operation to run exclusively.