egametang/ET · error · RpcException

Rpc error: request, 注意Actor消息超时,请注意查看是否死锁或者没有reply: {request

Error message

Rpc error: request, 注意Actor消息超时,请注意查看是否死锁或者没有reply: {request}, response: {response}

What it means

Thrown as RpcException when an Actor RPC call's response carries Error == ErrorCode.ERR_MessageTimeout. The Chinese text warns to check for deadlock or a missing reply handler. This means the request was sent but no response was received within the timeout window.

Source

Thrown at Packages/cn.etetet.login/Scripts/Hotfix/Client/Login/ClientSenderComponentSystem.cs:81

        public static void Send(this ClientSenderComponent self, IMessage message)
        {
            A2NetClient_Message a2NetClientMessage = A2NetClient_Message.Create();
            a2NetClientMessage.MessageObject = message;
            self.Root().GetComponent<ProcessInnerSender>().Send(self.FiberInstanceId, a2NetClientMessage);
        }

        public static async ETTask<IResponse> Call(this ClientSenderComponent self, IRequest request, bool needException = true)
        {
            A2NetClient_Request a2NetClientRequest = A2NetClient_Request.Create();
            a2NetClientRequest.MessageObject = request;
            
            using A2NetClient_Response a2NetClientResponse = await self.Root().GetComponent<ProcessInnerSender>().Call(self.FiberInstanceId, a2NetClientRequest) as A2NetClient_Response;
            IResponse response = a2NetClientResponse.MessageObject;
            
            if (response.Error == ErrorCode.ERR_MessageTimeout)
            {
                throw new RpcException(response.Error, $"Rpc error: request, 注意Actor消息超时,请注意查看是否死锁或者没有reply: {request}, response: {response}");
            }

            if (needException && ErrorCode.IsRpcNeedThrowException(response.Error))
            {
                throw new RpcException(response.Error, $"Rpc error: {request}, response: {response}");
            }
            return response;
        }

    }
}

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Check that the handler for the request type actually sends a reply (response) — trace the handler from the request type's dispatch.
  2. Look for deadlocks: circular awaits between fibers, or a fiber blocked on a synchronous call that prevents it from processing the reply.
  3. Increase the RPC timeout if the operation legitimately takes longer than the default window.
  4. Check server logs for the target fiber crashing or being disposed mid-request.

Example fix

// before: handler processes request but doesn't reply
public static void OnMyRequest(MyRequest req)
{
    DoWork(req);
    // missing reply
}
// after: always reply
public static async ETTask OnMyRequest(Session session, MyRequest req, MyResponse rsp, Action reply)
{
    DoWork(req);
    reply();
}
Defensive patterns

Strategy: try-catch

Try / catch

try { IResponse resp = await self.Call(request); } catch (RpcException e) when (e.Error == ErrorCode.ERR_MessageTimeout) { Log.Error($"RPC timeout for {request.GetType().Name}: {e.Message}"); /* retry or fallback */ }

Prevention

When it happens

Trigger: ClientSenderComponent.Call sends a request via ProcessInnerSender to a target fiber; if the handler on the receiving side never replies (no Send(response) call), or if there is a deadlock preventing the reply, the timeout fires.

Common situations: The message handler on the server side processes the request but forgets to reply. A deadlock between fibers prevents the reply from being sent. The timeout is too short for the operation's actual latency. The target fiber crashed or was disposed before replying.

Related errors


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