{"record":{"id":"fd6c051f7899baca","repo":"egametang/ET","slug":"kchannel-connected-but-kcp-is-zero","errorCode":null,"errorMessage":"kchannel connected but kcp is zero!","messagePattern":"kchannel connected but kcp is zero!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"Packages/cn.etetet.core/Scripts/Core/Share/Network/KChannel.cs","lineNumber":408,"sourceCode":"\t\t\t\t\tthis.kcp.Send(memoryStream.GetBuffer().AsSpan((int)memoryStream.Position + alreadySendCount, sendCount));\n\t\t\t\t\t\n\t\t\t\t\talreadySendCount += sendCount;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.Service.AddToUpdate(0, this.Id);\n\t\t}\n\t\t\n\t\tpublic void Send(MemoryBuffer memoryBuffer)\n\t\t{\n\t\t\tif (!this.IsConnected)\n\t\t\t{\n\t\t\t\tthis.waitSendMessages.Enqueue(memoryBuffer);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.kcp == null)\n\t\t\t{\n\t\t\t\tthrow new Exception(\"kchannel connected but kcp is zero!\");\n\t\t\t}\n\t\t\t\n\t\t\t// 检查等待发送的消息，如果超出最大等待大小，应该断开连接\n\t\t\tint n = (int)this.kcp.WaitSendCount;\n\t\t\tint maxWaitSize = 0;\n\t\t\tswitch (this.Service.ServiceType)\n\t\t\t{\n\t\t\t\tcase ServiceType.Inner:\n\t\t\t\t\tmaxWaitSize = Kcp.InnerMaxWaitSize;\n\t\t\t\t\tbreak;\n\t\t\t\tcase ServiceType.Outer:\n\t\t\t\t\tmaxWaitSize = Kcp.OuterMaxWaitSize;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tthrow new ArgumentOutOfRangeException();\n\t\t\t}\n\t\t\tif (n > maxWaitSize)\n\t\t\t{","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.core/Scripts/Core/Share/Network/KChannel.cs#L390-L426","documentation":"KChannel.Send throws when IsConnected is true but the kcp instance is null. This is an internal invariant violation: the connected state and the kcp lifecycle are out of sync, meaning the channel was disposed or never fully created while its flag says connected.","triggerScenarios":"Calling Send after the kcp was disposed (e.g., on OnError/timeout path) but before IsConnected was cleared, or a connect handshake that set the flag without finishing kcp allocation.","commonSituations":"Disconnect/error race, double-dispose, or a version regression in the connect state machine.","solutions":["Do not Send on a channel that is being torn down; gate sends on both IsConnected and kcp != null.","Ensure OnError/Dispose clears IsConnected before nulling kcp.","Report as a framework bug if it reproduces with a clean connect/disconnect cycle."],"exampleFix":"// before\npublic void Send(MemoryBuffer mb) {\n    if (!IsConnected) { waitSendMessages.Enqueue(mb); return; }\n    if (kcp == null) throw new Exception(...);\n// after\npublic void Send(MemoryBuffer mb) {\n    if (!IsConnected || kcp == null) { waitSendMessages.Enqueue(mb); return; }","handlingStrategy":"type-guard","validationCode":"null","typeGuard":"public bool CanSend => IsConnected && kcp != null;","tryCatchPattern":"null","preventionTips":["Gate sends on kcp != null, not only IsConnected.","Clear IsConnected before nulling kcp in the dispose/error path.","Treat this throw as a framework bug to file if it reproduces."],"tags":["network","kcp","state-machine","csharp"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}