egametang/ET · error · RpcException

ERR_MessageTimeout

ERR_MessageTimeout

Error message

{message}

What it means

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.

Source

Thrown at Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/MessageLocationSenderComponentSystem.cs:116

        public static async ETTask SendAsync(this MessageLocationSenderOneType self, long entityId, IMessage message)
        {
            await self.SendInner(entityId, message);
        }
        
        private static async ETTask SendInner(this MessageLocationSenderOneType self, long entityId, IMessage message)
        {
            EntityRef<MessageLocationSenderOneType> selfRef = self;
            MessageLocationSender messageLocationSender = self.GetOrCreate(entityId);
            EntityRef<MessageLocationSender> messageLocationSenderRef = messageLocationSender;
            Scene root = self.Root();
            EntityRef<Scene> rootRef = root;
            
            using (await root.CoroutineLockComponent.Wait(CoroutineLockType.MessageLocationSender, entityId))
            {
                messageLocationSender = messageLocationSenderRef;
                if (messageLocationSender == null)
                {
                    throw new RpcException(ErrorCode.ERR_MessageTimeout, $"{message}");
                }
                
                if (messageLocationSender.ActorId == default)
                {
                    root = rootRef;
                    self = selfRef;
                    ActorId actorId = await root.GetComponent<LocationProxyComponent>().Get((int)self.Id, messageLocationSender.Id);
                    messageLocationSender = messageLocationSenderRef;
                    if (messageLocationSender == null)
                    {
                        throw new RpcException(ErrorCode.ERR_ActorLocationSenderTimeout2, $"{message}");
                    }

                    if (actorId == default)
                    {
                        self = selfRef;
                        self?.Remove(entityId);
                        throw new RpcException(ErrorCode.ERR_NotFoundActor, $"{message}");

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Catch RpcException with ERR_MessageTimeout and re-send the message — GetOrCreate will recreate a fresh sender.
  2. Use SendAsync instead of Send if you need to await and handle the error at the call site.
  3. Reduce coroutine lock contention on the same entityId to shorten the lock-wait window.
  4. Ensure the entity is actively communicating so the sender doesn't time out.

Example fix

// before — fire-and-forget, error is swallowed
senderOneType.Send(entityId, message);

// after — await and retry on timeout
try
{
    await senderOneType.SendAsync(entityId, message);
}
catch (RpcException e) when (e.Error == ErrorCode.ERR_MessageTimeout)
{
    await senderOneType.SendAsync(entityId, message); // GetOrCreate makes a new sender
}
Defensive patterns

Strategy: try-catch

Try / catch

// For fire-and-forget Send, use SendAsync and retry on timeout:
try
{
    await senderOneType.SendAsync(entityId, message);
}
catch (RpcException e) when (e.Error == ErrorCode.ERR_MessageTimeout)
{
    // Sender was disposed during lock wait — recreate and retry
    await senderOneType.SendAsync(entityId, message);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/a86f8a53aca8e5eb. Report an issue: GitHub.