{"record":{"id":"94fc96d3f4e276b9","repo":"microsoft/garnet","slug":"lightepoch","errorCode":null,"errorMessage":"LightEpoch","messagePattern":"LightEpoch","errorType":"exception","errorClass":"ObjectDisposedException","httpStatus":null,"severity":"error","filePath":"libs/client/LightEpoch.cs","lineNumber":632,"sourceCode":"\n            // Table is full, fall back to slow path with waiting\n            ReserveEntryWait(ref entry);\n        }\n\n        /// <summary>\n        /// Slow path for reserving an entry when the table is full.\n        /// Waits on semaphore until an entry becomes available.\n        /// </summary>\n        /// <returns>Reserved entry</returns>\n        [MethodImpl(MethodImplOptions.NoInlining)]\n        void ReserveEntryWait(ref int entry)\n        {\n            int newCount = Interlocked.Increment(ref waiterCount);\n            try\n            {\n                // If the MSB (disposed flag) is set, the epoch is being disposed.\n                if ((newCount & kDisposedFlag) != 0)\n                    throw new ObjectDisposedException(nameof(LightEpoch));\n\n                while (true)\n                {\n                    // Re-check for free slot after incrementing waiterCount. This avoids\n                    // us waiting on the semaphore forever in case we increment waiterCount\n                    // immediately after the epoch releaser sees a zero waiterCount (and\n                    // therefore does not release the semaphore).\n                    if (TryAcquireEntry(ref entry))\n                        return;\n\n                    // No slot available, wait for a signal from Release()\n                    waiterSemaphore.Wait(cts.Token);\n                }\n            }\n            catch (OperationCanceledException)\n            {\n                throw new ObjectDisposedException(nameof(LightEpoch));\n            }","sourceCodeStart":614,"sourceCodeEnd":650,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/client/LightEpoch.cs#L614-L650","documentation":"ReserveEntryWait is the slow path when the epoch table is full: it increments waiterCount and, if the high bit (kDisposedFlag) is set, throws ObjectDisposedException('LightEpoch'). Dispose sets waiterCount's MSB (via CompareExchange to kDisposedFlag from 0) to signal teardown, so any thread entering the slow path during disposal is rejected. This is the use-during-dispose guard for the epoch's entry-reservation path.","triggerScenarios":"A thread tries to reserve an epoch table entry (ReserveEntry → ReserveEntryWait) concurrently with LightEpoch.Dispose(), which sets the kDisposedFlag and thereby makes the incremented waiterCount negative-flagged.","commonSituations":"Concurrent shutdown where worker threads still protect/reclaim via the epoch while Dispose races; a missing drain before disposing the epoch; stress tests tearing down the store while background threads are still active.","solutions":["Drain all threads that use the epoch (ensure no in-flight ReserveEntry) before calling Dispose.","Treat ObjectDisposedException from this path as expected during teardown and stop work gracefully.","Order teardown: signal workers to stop, wait for them, then dispose the epoch."],"exampleFix":"// before\nworkers.Stop();          // signals stop but does not wait\nepoch.Dispose();          // races with workers still reserving entries\n\n// after — wait for full drain before dispose\nawait workers.DrainAsync();\nepoch.Dispose();","handlingStrategy":"try-catch","validationCode":"// Drain threads that use the epoch before disposing it\nawait workers.DrainAsync();\nepoch.Dispose();","typeGuard":null,"tryCatchPattern":"try { epoch.ReserveEntryForThread(); }\ncatch (ObjectDisposedException) { /* epoch is shutting down; stop work */ return; }","preventionTips":["Drain all epoch-using threads before Dispose.","Treat ObjectDisposedException from ReserveEntry as an expected shutdown signal.","Order teardown: stop workers, wait, then dispose the epoch."],"tags":["csharp","object-disposed","epoch","concurrency","light-epoch"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}