{"record":{"id":"a86f8a53aca8e5eb","repo":"egametang/ET","slug":"err-messagetimeout","errorCode":"ERR_MessageTimeout","errorMessage":"{message}","messagePattern":"\\{message\\}","errorType":"error_code","errorClass":"RpcException","httpStatus":null,"severity":"error","filePath":"Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/MessageLocationSenderComponentSystem.cs","lineNumber":116,"sourceCode":"        public static async ETTask SendAsync(this MessageLocationSenderOneType self, long entityId, IMessage message)\n        {\n            await self.SendInner(entityId, message);\n        }\n        \n        private static async ETTask SendInner(this MessageLocationSenderOneType self, long entityId, IMessage message)\n        {\n            EntityRef<MessageLocationSenderOneType> selfRef = self;\n            MessageLocationSender messageLocationSender = self.GetOrCreate(entityId);\n            EntityRef<MessageLocationSender> messageLocationSenderRef = messageLocationSender;\n            Scene root = self.Root();\n            EntityRef<Scene> rootRef = root;\n            \n            using (await root.CoroutineLockComponent.Wait(CoroutineLockType.MessageLocationSender, entityId))\n            {\n                messageLocationSender = messageLocationSenderRef;\n                if (messageLocationSender == null)\n                {\n                    throw new RpcException(ErrorCode.ERR_MessageTimeout, $\"{message}\");\n                }\n                \n                if (messageLocationSender.ActorId == default)\n                {\n                    root = rootRef;\n                    self = selfRef;\n                    ActorId actorId = await root.GetComponent<LocationProxyComponent>().Get((int)self.Id, messageLocationSender.Id);\n                    messageLocationSender = messageLocationSenderRef;\n                    if (messageLocationSender == null)\n                    {\n                        throw new RpcException(ErrorCode.ERR_ActorLocationSenderTimeout2, $\"{message}\");\n                    }\n\n                    if (actorId == default)\n                    {\n                        self = selfRef;\n                        self?.Remove(entityId);\n                        throw new RpcException(ErrorCode.ERR_NotFoundActor, $\"{message}\");","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/MessageLocationSenderComponentSystem.cs#L98-L134","documentation":"In SendInner (fire-and-forget IMessage path), after awaiting the CoroutineLock for the entityId, the MessageLocationSender was found null via the EntityRef. This means the sender child entity was disposed during the await on the coroutine lock — either by the 10s Check timer (which removes senders idle > TIMEOUT_TIME), by explicit Remove(), or by parent entity disposal.","triggerScenarios":"MessageLocationSenderOneType.Send(entityId, message) where the MessageLocationSender child is removed (Check timer expiry or explicit Remove) during the await on CoroutineLockType.MessageLocationSender for that entityId.","commonSituations":"The entity was idle long enough for TIMEOUT_TIME to elapse. Another coroutine explicitly removed the sender (disconnect/reconnect to a different Gate). High contention on the same entityId's coroutine lock causing long waits that exceed the sender's lifetime.","solutions":["Catch RpcException with ERR_MessageTimeout and re-send the message — GetOrCreate will recreate a fresh sender.","Use SendAsync instead of Send if you need to await and handle the error at the call site.","Reduce coroutine lock contention on the same entityId to shorten the lock-wait window.","Ensure the entity is actively communicating so the sender doesn't time out."],"exampleFix":"// before — fire-and-forget, error is swallowed\nsenderOneType.Send(entityId, message);\n\n// after — await and retry on timeout\ntry\n{\n    await senderOneType.SendAsync(entityId, message);\n}\ncatch (RpcException e) when (e.Error == ErrorCode.ERR_MessageTimeout)\n{\n    await senderOneType.SendAsync(entityId, message); // GetOrCreate makes a new sender\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// For fire-and-forget Send, use SendAsync and retry on timeout:\ntry\n{\n    await senderOneType.SendAsync(entityId, message);\n}\ncatch (RpcException e) when (e.Error == ErrorCode.ERR_MessageTimeout)\n{\n    // Sender was disposed during lock wait — recreate and retry\n    await senderOneType.SendAsync(entityId, message);\n}","preventionTips":["Use SendAsync instead of fire-and-forget Send when you need error recovery.","Reduce coroutine lock contention on the same entityId.","Keep the entity actively communicating so the sender doesn't time out.","Increase TIMEOUT_TIME if idle gaps are expected."],"tags":["message-routing","entity-disposed","timeout","fire-and-forget","coroutine-lock"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}