ModernUO/Projects/Server/Serialization
Kamron Batman 95b225aef7
fix(saves): close a deadlock race in the worker pause handshake
Execute() signaled _stopEvent before clearing _pause and before checking the
exit flag. Wake/Sleep/Exit all run on the owning thread, so the moment
_stopEvent is set the owner can start another pause cycle - Exit() does
exactly that. Two interleavings in that window are fatal:

- The worker's late "_pause = false" clobbers the new cycle's pause request,
  so the worker consumes the wake and spins forever waiting for a pause that
  never reads true while the owner blocks in Sleep().
- The worker's late exit check observes the _exit flag written by an Exit()
  whose Sleep() is still pending, and returns without ever signaling it.

Either way the run hangs silently. The ordering predates this branch, but
production callers never sat inside the window (one Wake/Sleep pair per
save, minutes apart; Exit only long after the last handshake). The tests
added on this branch are the first callers that run pause cycles
back-to-back, and a saturated CI runner hit the window: this is what
stalled the CentOS job for 2h41m. The race is not rare under that pattern -
a churn test reproduces the deadlock in 4 out of 4 runs locally.

Fix: sample the exit condition and clear _pause (volatile) BEFORE signaling
_stopEvent. A post-signal Exit() is then always serviced by one more
wake/drain/signal round, and its pause request can no longer be clobbered.
Drain-before-exit semantics on Core.Closing are unchanged.

Adds a watchdog churn regression test (2000 wake/sleep/exit lifecycles) that
fails instead of hanging if the race is reintroduced: red 4/4 deadlocks
without the fix, green 4/4 at ~550ms with it. Server.Tests 789/789 and
UOContent.Tests 509/509 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 22:38:49 -07:00
..
Attributes fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
AdhocPersistence.cs perf(saves): delete SerializedTypes.db write path and runtime type tracking 2026-07-16 22:38:49 -07:00
BinaryFileReader.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
BufferReader.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
BufferWriter.cs perf(saves): delete SerializedTypes.db write path and runtime type tracking 2026-07-16 22:38:49 -07:00
FileBufferWriter.cs perf(saves): delete SerializedTypes.db write path and runtime type tracking 2026-07-16 22:38:49 -07:00
GenericEntityPersistence.cs docs(saves): note the failure mode if the type-table invariant guard fires 2026-07-16 22:38:49 -07:00
GenericPersistence.cs perf(saves): delete SerializedTypes.db write path and runtime type tracking 2026-07-16 22:38:49 -07:00
IGenericReader.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
IGenericSerializable.cs perf(saves): drop the 9-byte per-entity placement state; write snapshots from worker segment logs 2026-07-16 22:38:47 -07:00
IGenericWriter.cs Removes MemoryMapFileWriter 2026-07-16 22:38:47 -07:00
ISerializable.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
ISerializableExtensions.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00
Persistence.cs perf(saves): delete SerializedTypes.db write path and runtime type tracking 2026-07-16 22:38:49 -07:00
SerializationChunkSource.cs perf(saves): drop the 9-byte per-entity placement state; write snapshots from worker segment logs 2026-07-16 22:38:47 -07:00
SerializationExtensions.cs feat: Refactor Poison system, implement Darkglow & Parasitic effects (#2385) 2026-03-21 21:27:22 -07:00
SerializationThreadWorker.cs fix(saves): close a deadlock race in the worker pause handshake 2026-07-16 22:38:49 -07:00
ShadowDictionaryEntries.cs perf(saves): workers iterate entity dictionaries directly; main thread joins the drain 2026-07-16 22:38:46 -07:00
UnmanagedDataReader.cs fix: Bumps deps. Updates copyrights (#2353) 2026-03-05 19:36:54 -08:00