egametang/ET · error · RpcException

ERR_ActorLocationSenderTimeout2

ERR_ActorLocationSenderTimeout2

Error message

{message}

What it means

In SendInner, the cached ActorId was default, so a LocationProxyComponent.Get was issued to resolve the target. After that async location lookup returned, the MessageLocationSender was re-acquired from EntityRef and found null — the sender was disposed during the location Get round-trip. The message can no longer be delivered through this sender.

Source

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

            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}");
                    }

                    messageLocationSender.ActorId = actorId;
                }
                
                messageLocationSender = messageLocationSenderRef;
                messageLocationSender.LastSendOrRecvTime = messageLocationSender.GetSingleton<TimeInfo>().ServerNow();
                root = rootRef;
                root.GetComponent<MessageSender>().Send(messageLocationSender.ActorId, message);
            }
        }

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Catch RpcException with ERR_ActorLocationSenderTimeout2 and re-send — GetOrCreate will make a new sender.
  2. Use SendAsync to await and handle disposal errors at the call site.
  3. Reduce location server latency to narrow the disposal window.
  4. Keep the sender's ActorId populated (send frequently) to avoid triggering location lookups.
Defensive patterns

Strategy: try-catch

Try / catch

try
{
    await senderOneType.SendAsync(entityId, message);
}
catch (RpcException e) when (e.Error == ErrorCode.ERR_ActorLocationSenderTimeout2)
{
    // Sender disposed during location lookup — retry recreates it
    await senderOneType.SendAsync(entityId, message);
}

Prevention

When it happens

Trigger: MessageLocationSenderOneType.Send(entityId, message) where messageLocationSender.ActorId is default, triggering a location lookup, and during that async lookup the sender is disposed by the Check timer or explicit Remove.

Common situations: The Check timer fires during the location server round-trip. Location server is slow, widening the disposal window. Another coroutine removes the sender (e.g., player reconnect) during the lookup.

Related errors


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