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.