{"record":{"id":"7071e7dea8e21a50","repo":"microsoft/garnet","slug":"throttle-count-is-negative","errorCode":null,"errorMessage":"Throttle count is negative","messagePattern":"Throttle count is negative","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"libs/client/ClientTcpNetworkSender.cs","lineNumber":47,"sourceCode":"            : base(socket, networkBufferSettings, networkPool, networkSendThrottleMax)\n        {\n            this.callback = callback;\n            this.reusableSaea = new SimpleObjectPool<SocketAsyncEventArgs>(() =>\n            {\n                var s = new SocketAsyncEventArgs();\n                s.Completed += SeaaBuffer_Completed;\n                return s;\n            });\n        }\n\n        /// <inheritdoc />\n        public override void SendResponse(byte[] buffer, int offset, int count, object context)\n        {\n            var cnt = Interlocked.Increment(ref throttleCount);\n            if (cnt < 0)\n            {\n                Interlocked.Decrement(ref throttleCount);\n                throw new Exception(\"Throttle count is negative\");\n            }\n            if (cnt > ThrottleMax)\n                throttle.Wait();\n\n            var s = reusableSaea.Checkout();\n            s.SetBuffer(buffer, offset, count);\n            s.UserToken = context;\n            try\n            {\n                if (!socket.SendAsync(s))\n                    SeaaBuffer_Completed(null, s);\n            }\n            catch\n            {\n                reusableSaea.Return(s);\n                if (Interlocked.Decrement(ref throttleCount) >= ThrottleMax)\n                    throttle.Release();\n                // Rethrow exception as session is not usable","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/client/ClientTcpNetworkSender.cs#L29-L65","documentation":"ClientTcpNetworkSender.SendResponse increments a throttleCount via Interlocked.Increment and throws if the result is negative. Because throttleCount is only ever incremented on send and decremented on completion, a negative value means the 32-bit counter overflowed past int.MaxValue — i.e. more than ~2.1 billion outstanding sends were counted without the matching decrements, or the counter was corrupted. This is a self-consistency check indicating a serious accounting bug or a leaked/never-completed send path, not normal backpressure (that uses the separate throttle semaphore when cnt > ThrottleMax).","triggerScenarios":"An extremely long-lived ClientTcpNetworkSender accumulating sends until throttleCount wraps to negative; or a path that increments throttleCount without a matching decrement on completion (a send-completion callback never firing, so the decrement in DisposeNetworkSender/SeaaBuffer_Completed is skipped).","commonSituations":"Long-running connections that never recycle the network sender under very high throughput; a bug where SocketAsyncEventArgs completion is lost; concurrent dispose racing with SendResponse corrupting the counter.","solutions":["Treat this as a defect: recycle/reconnect the ClientTcpNetworkSender periodically before the counter can overflow.","Ensure every SendResponse path has a guaranteed decrement (success, synchronous completion, exception, and dispose paths).","Report to the Garnet maintainers with a reproduction — a negative count implies an unmatched increment/decrement bug."],"exampleFix":"// before — single long-lived sender under sustained load\n// (counter eventually overflows after >2.1B sends)\n\n// after — recycle the network sender/connection on a send-count budget\nif (Interlocked.Read(ref sendsSinceReconnect) > SendBudget)\n    await client.ReconnectAsync();","handlingStrategy":"fallback","validationCode":"// No caller pre-check; recycle the network sender on a send-count budget to avoid overflow\nif (Interlocked.Read(ref sendsSinceReconnect) > SendBudget)\n    await client.ReconnectAsync();","typeGuard":null,"tryCatchPattern":"try { networkSender.SendResponse(buf, off, count, ctx); }\ncatch (Exception ex) when (ex.Message.Contains(\"Throttle count is negative\")) { await client.ReconnectAsync(); /* retry on fresh sender */ }","preventionTips":["Periodically recycle long-lived network senders before throttleCount can overflow.","Report a negative-count occurrence to maintainers — it implies an unmatched increment/decrement.","Verify every send path guarantees a decrement on completion and on error."],"tags":["csharp","throttle","concurrency","overflow","network-sender"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}