Repository navigation
Description
What happened?
Our production server crashed 4 times in 16.5 hours on 38435 (2026-11-07 03:39 to 20:03 UTC), within the first 35 hours after we deployed it. All four crashes are inside the same function: the body of the per-client lambda passed to tbb::parallel_for_each in ServerGameState::Tick (ServerGameState.cpp L1409), which is citizen-server-state-fivesv.dll+0x1a240 (the function that references playerEnteredScope / playerLeftScope / onPlayerDropped). Two crashed on a TBB worker and two on UV loop: svSync. There are no script frames on any stack.
| Time (UTC) | Players | Uptime | Thread | Fault | Dump | |
|---|---|---|---|---|---|---|
| A | 2026-11-07 03:39 | 331 | 3h10m | TBB worker | 0xC0000374, double free (heap_failure_block_not_busy) |
c0c2d142-6f8c-4d7a-8159-12b3060d8e23 |
| B | 2026-11-07 05:01 | 417 | 1h22m | svSync | AV, object_pool<SyncCommandList> free-queue pop |
01333ecd-efd0-450c-9633-d7170d4f48b9 |
| C | 2026-11-07 18:06 | ~560 | 1h29m | TBB worker | AV, object_pool<SyncCommand> free-queue pop |
01ce17fe-055b-4c5e-82de-084ba26852a2 |
| D | 2026-11-07 20:03 | ~165 | 0h28m | svSync | AV, shared_reference<SyncCommandList>::reset() releasing its control block |
bdf34df9-2b23-4f8e-b7d6-75afc3a793dc |
I don't have PDBs. I matched functions by disassembly against the source at 5c3065c. The struct offsets below line up with object_pool.h, shared_reference.h and detached_queue.h. Please confirm with symbols.
A: double free under frameStates.erase
ntdll!RtlFreeHeap (fail-fast, HEAP_FAILURE_INFORMATION FailureType 8 = block not busy)
ucrtbase!free+0x208b
citizen-server-state-fivesv.dll+0x35fe0
citizen-server-state-fivesv.dll+0x4971c
citizen-server-state-fivesv.dll+0x1c79b <- per-client lambda
citizen-server-state-fivesv.dll+0x4eaf0 <- TBB task
I matched the free to clientDataUnlocked->frameStates.erase(firstFrameState); (L1993-L2012). The freed block was already free.
B and C: corrupted m_tail in a thread-local bucket_entry::free_queue
Both crashes fault on the same instruction, mov rax,[rdx], which is m_tail->next in detached_mpsc_queue::pop_until_empty (L112-L113). They are two instantiations of the same code: +0xed70 and +0xee50. Each one is reached from the thread-local bucket_proxy → bucket_entry::allocate() (TLS slots +0x238 and +0x250).
-
B (
+0xed87←+0x41c40← lambda+0x1a322): the pool ofshared_reference<sync::SyncCommandList, &syncPool>::Construct(L1440-L1441).m_tailpointed to unmapped memory. -
C (
+0xee67←+0x48c7e←+0x1cb3b← lambda+0x1c605). The function at+0x1cac0builds aFixedFunction<…,128>(invoker pointers at +0x80/+0x88) and pushes the key at +0x90. That isSyncCommandList::EnqueueCommand→syncPool.construct→commands.push(ServerGameState.h L1181-L1184), so this is theSyncCommandpool.From the full dump, the
bucket_entryat0x21b692dab80holds:m_head(+0x00) =0x21c4d3f7440m_tail(+0x80) =0x00d0021c4d827f68, non-canonicalm_stub.next(+0x88) =0x21c4d1ea1a0
The low 48 bits of
m_taildo not point to afree_queue_key. Entries in that pool instance are 0xA8 bytes with the key at +0xA0, and0x21c4d827f68falls inside the callback storage of aSyncCommandentry. So a node'snextwas overwritten while the node was on the free queue: a write after free, or the same entry freed twice.
D: shared_reference<SyncCommandList>::reset() with a corrupted control block
citizen-server-state-fivesv.dll+0x3288b cmp byte ptr [rdx+110h],0 ; rdx = bucket = nullptr
citizen-server-state-fivesv.dll+0x1c95b per-client lambda, end of scope (destructor of `scl`)
+0x32810 is shared_reference::reset() (L97-L113) for SyncCommandList; the value's object_entry::pool is at value+0x18 = sizeof(SyncCommandList).
- Both counters reached zero, and
Pool->destruct(value)succeeded. control_blocks.destruct(block)then readblock'sobject_entry::pool→pool_instance::bucket→bucket->expired(object_pool.h L284,expiredat bucket+0x110). The bucket pointer was null.
The full dump shows what went wrong. Control-block entries are 24 bytes (control_block 8, pool 8, free_queue_key 8):
block-0x18: 00000000'00000000 pool=00000184'b9e73048 key=00000184'b9e7c4e8
block : 00000000'00000000 pool=00000183'b9e73048 key=00000184'b9ec5400 <- ours
block+0x18: 00000000'00000000 pool=00000184'b9e73048 key=00000000'00000000
0x184b9e73048 is a real pool_instance: bucket 0x183c4b30580, object_count 0xaaaa = 1 MiB / 24. Our block's pool is exactly 0x1'0000'0000 lower. Something did a 32-bit decrement on the upper half of block->pool (block+0xC). That matches dec_weak() called through a control-block pointer of block+8, or dec_ref() through block+0xC. In other words, a reference was released through a stale or corrupted control-block pointer. That last part is my inference.
Common pattern (inference, not confirmed)
All four crashes are use-after-free or double-free signatures in memory that the per-client lambda allocates or frees: frameStates, SyncCommandList, SyncCommand and control_blocks. The lambda carries the upstream comment at L1415:
// #TODO: imagine if this mutates state alongside but after OnDrop clears it. WHAT COULD GO WRONG?At this population players join and drop every few seconds. A lifetime race between the per-client tick and client drop/cleanup is my best guess.
Expected result
FXServer keeps running; no memory corruption in the OneSync per-client tick.
Reproduction steps
Observed 4 times on production in 16.5 hours. I cannot trigger it on demand.
- Windows FXServer 38435, OneSync with
sv_maxclients 2000(big mode),onesync_population false,sv_enforceGameBuild 2944, default NAK sync (onesync_automaticResendoff). - 165-560 players connected and playing normally, with constant joins and drops.
- The crash comes at a random time: after 28 minutes to 3h10m of uptime.
Our anticheat logs of game events and state bag writes in the 60 s before each crash show normal traffic: no floods, no oversized values, nothing unusual on entity bags. In the minute before each crash, scripts did only ordinary things: joins, quits, timeouts, and one DropPlayer ban 16 s before A.
Importancy
Crash
Area(s)
FXServer, OneSync
Specific version(s)
Server 38435 windows (master 5c3065c), game build 2944
Additional information
-
Environment: Windows Server 2022 (20348), 32 logical CPUs, 128 GB RAM, txAdmin 8.1.2. Memory was healthy before each crash: working set under 4 GB, system commit far below the 143 GB limit, and no hitch warnings beforehand.
-
Previous artifact: feature/network-improvements-clean 37264. Its first crash came after ~9 days (a different heap corruption, in the ENet free path on svNetwork). On 38435 we had these 4 crashes in 16.5 hours.
-
Profiler: a resource ran
profiler record/ stop on svMain every ~20 s during A and B, but it was not running at all during C and D. NosaveJSONthread was alive in any of these dumps. -
Minidumps:
fxserver-38435-onesync-crashes-sanitized-minidumps.zip(all 4) is attached in the first comment below. Sanitized because they are public:- Kept intact: thread contexts (GPRs, rip/rsp), thread stacks (return addresses), the module list with CodeView records, and the exception records.
- Removed: all captured memory outside thread stacks is zeroed, and printable strings of 6 or more characters are zeroed inside stacks, vector/x87 register areas and the function-table stream.
The raw minidumps (4-5 MB) and full dumps (3-8 GB) contain server secrets and player data. I can share them privately with Cfx staff.
-
Possibly related: [Urgent] No Viable Production Artifact After b25770 Degradation - Sync Network Hitches / Mass Connection Timeouts #4244.
Activity
yw1gara commentedon Nov 8, 2026
fxserver-38435-onesync-crashes-sanitized-minidumps.zip
yw1gara commentedon Nov 8, 2026
Fifth crash (E), same lambda, same path as A
337edd0b-eb04-4ace-9880-b1e00695034aThat makes 5 OneSync crashes in 22 hours on 38435.
+0xe990isweak_reference::reset():valueat +0,blockat +8, thendec_weak()=lock xadd [block+4], -1. So this is the destruction ofClientEntityState::syncedEntities(pair<uint16_t, ClientEntityData>, 40 bytes,SyncEntityWeakPtrat element+8). It fails on element 10 of 16.The vector in the full dump: begin
0x20f38b98eb0, end0x20f38b99130, keys sorted (…0x420, 0x422, 0x448, 0x44a, 0x44b, 0x50e, 0x50f, 0x530…). The contents are intact except for one pattern: in exactly two 64-byte lines of the vector, byte 6 (bits 48-55) of every qword has been overwritten. Every other line is clean.The low 48 bits still hold the original values:
0x0000020f32abb250is a plausible control block, and the frame indices look right. Crash C'sm_tail(0x00d0021c4d827f68) has the same signature, a valid-looking heap pointer with garbage only in byte 6. Something writes into memory owned by these per-client sync structures. A and E (double free / corrupted vector in the sameframeStates.erase) point at the sameClientEntityState. Without symbols I can't tell which code writes those bytes.Nothing unusual happened before E: normal joins and quits, one timeout, and our entity cleanup (paced
DeleteEntity) had run about 14 minutes earlier. 38435 is still both recommended and latest, and master has no newer commits.The sanitized minidump for E is attached below. It was sanitized the same way as the others.
[-]Server (Windows) 38435: memory corruption in the parallel per-client sync tick (4 crashes in 16 h: double free, corrupted object_pool free lists, corrupted control block)[/-][+]Server (Windows) 38435: memory corruption in the parallel per-client sync tick (5 crashes in 22 h: double free, corrupted object_pool free lists, corrupted control block)[/+]Gogsi commentedon Nov 8, 2026
I would try reinstalling the server artifacts, in case of file corruption, and running a memory test to check if the RAM on the server is good. There have been no other reports of such crashes and 5 in a day is highly unusual. Especially when you also had corruption issues with the network-improvements-clean artifact.
Looking into the first crash, I don't see any possibility of a race condition around GameStateClientData, every write or erase is behind the selfMutex, and the clientRef cannot be deleted by a client drop while processing that client in
ServerGameState::Tick'stbb::parallel_for_each, because the shared pointer holds a reference on the client. This to me implies some larger corruption than just the state data. It's also worth noting that none of this code has changed recently, beside simple changes in bd84727 and d34c31a both of which have been used by many large servers.