{"record":{"id":"f35538a3c80a5841","repo":"egametang/ET","slug":"tchannel-dispose","errorCode":null,"errorMessage":"TChannel已经被Dispose, 不能发送消息","messagePattern":"TChannel已经被Dispose, 不能发送消息","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"Packages/cn.etetet.core/Scripts/Core/Share/Network/TChannel.cs","lineNumber":97,"sourceCode":"\n\t\t\tLog.Info($\"channel dispose: {this.Id} {this.RemoteAddress} {this.Error}\");\n\t\t\t\n\t\t\tlong id = this.Id;\n\t\t\tthis.Id = 0;\n\t\t\tthis.Service.Remove(id);\n\t\t\tthis.socket.Close();\n\t\t\tthis.innArgs.Dispose();\n\t\t\tthis.outArgs.Dispose();\n\t\t\tthis.innArgs = null;\n\t\t\tthis.outArgs = null;\n\t\t\tthis.socket = null;\n\t\t}\n\n\t\tpublic void Send(MemoryBuffer stream)\n\t\t{\n\t\t\tif (this.IsDisposed)\n\t\t\t{\n\t\t\t\tthrow new Exception(\"TChannel已经被Dispose, 不能发送消息\");\n\t\t\t}\n\t\t\t\n\t\t\tswitch (this.Service.ServiceType)\n\t\t\t{\n\t\t\t\tcase ServiceType.Inner:\n\t\t\t\t{\n\t\t\t\t\tint messageSize = (int) (stream.Length - stream.Position);\n\t\t\t\t\tif (messageSize > ushort.MaxValue * 16)\n\t\t\t\t\t{\n\t\t\t\t\t\tthrow new Exception($\"send packet too large: {stream.Length} {stream.Position}\");\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.sendCache.WriteTo(0, messageSize);\n\t\t\t\t\tthis.sendBuffer.Write(this.sendCache, 0, PacketParser.InnerPacketSizeLength);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase ServiceType.Outer:\n\t\t\t\t{","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.core/Scripts/Core/Share/Network/TChannel.cs#L79-L115","documentation":"TChannel.Send checks IsDisposed before queuing and throws if the channel's socket has been closed. This guards against use-after-free: someone is trying to send on a channel that is already torn down.","triggerScenarios":"Application code holding a channel reference after disconnect/error and calling Send, or a send queued during shutdown racing with Dispose.","commonSituations":"Session not notified of channel removal, reconnect logic reusing a stale channel, or shutdown ordering where Send runs after Dispose.","solutions":["Null/clear channel references when the channel is disposed or errors out.","Check IsDisposed at the call site before sending.","Drive sends only through the session lifecycle that owns the channel."],"exampleFix":"// before\nchannel.Send(memoryBuffer);\n// after\nif (channel == null || channel.IsDisposed) return;\nchannel.Send(memoryBuffer);","handlingStrategy":"type-guard","validationCode":"null","typeGuard":"public static bool CanSend(TChannel c) => c != null && !c.IsDisposed;","tryCatchPattern":"null","preventionTips":["Null out session channel references on remove/error.","Check IsDisposed before Send at the session layer.","Never enqueue work for a channel past its lifecycle end."],"tags":["network","tcp","lifecycle","csharp"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}