egametang/ET · error · RpcException
location unlock failed key: {key} oldActorId: {oldActorId} n
Error message
location unlock failed key: {key} oldActorId: {oldActorId} newActorId: {newActorId} lockToken: {lockToken} error: {response.Message} What it means
The valid token-based UnLock overload sent an ObjectUnLockRequest to the primary location server via CallPrimaryWithRetry (which already retries on primary failover up to locationRequestRetryTimes). The server returned a non-success error, so an RpcException is thrown carrying the server's error code and message. Common server-side codes include ERR_LocationLockTokenMismatch (wrong token), ERR_LocationLockOwnerMismatch (oldActorId doesn't match the lock owner), and ERR_LocationLockNotFound (lock expired or was never acquired).
Source
Thrown at Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationProxyComponentSystem.cs:273
}
public static async ETTask UnLock(this LocationProxyComponent self, int type, long key, ActorId oldActorId,
ActorId newActorId, long lockToken)
{
Log.Info(
$"location proxy unlock {key}, {newActorId} lockToken: {lockToken} {self.GetSingleton<TimeInfo>().ServerNow()}");
ObjectUnLockRequest request = ObjectUnLockRequest.Create();
request.Type = type;
request.Key = key;
request.OldActorId = oldActorId;
request.NewActorId = newActorId;
request.LockToken = lockToken;
ObjectUnLockResponse response = (ObjectUnLockResponse)await self.CallPrimaryWithRetry(key, request);
if (response.Error != ErrorCode.ERR_Success)
{
throw new RpcException(response.Error,
$"location unlock failed key: {key} oldActorId: {oldActorId} newActorId: {newActorId} lockToken: {lockToken} error: {response.Message}");
}
}
public static async ETTask UnLockWithRetry(this LocationProxyComponent self, int type, long key, ActorId oldActorId,
ActorId newActorId, long lockToken)
{
EntityRef<LocationProxyComponent> selfRef = self;
int retryCount = 0;
while (true)
{
self = selfRef;
if (self == null)
{
throw new Exception("location proxy disposed");
}
tryView on GitHub (pinned to 5cab01f7a8)
Solutions
- Use UnLockWithRetry instead of UnLock — it catches LockNotFound, OwnerMismatch, and TokenMismatch, then compensates by re-querying location to verify the actor already landed at newActorId.
- Increase the lock timeout parameter in LockWithToken if the locked operation is long-running.
- Ensure the lockToken returned by LockWithToken is correctly stored and propagated to the eventual unlock call without loss or mutation.
- Catch RpcException and inspect e.Error to distinguish recoverable (lock already gone) from genuine failures.
Example fix
// before long token = await proxy.LockWithToken(type, key, actorId); // ... work ... await proxy.UnLock(type, key, oldActorId, newActorId, token); // may throw on token mismatch // after long token = await proxy.LockWithToken(type, key, actorId); // ... work ... await proxy.UnLockWithRetry(type, key, oldActorId, newActorId, token); // compensates automatically
Defensive patterns
Strategy: try-catch
Try / catch
// Option A: use UnLockWithRetry (handles LockNotFound, OwnerMismatch, TokenMismatch)
await proxy.UnLockWithRetry(type, key, oldActorId, newActorId, lockToken);
// Option B: manual catch for specific error codes
try
{
await proxy.UnLock(type, key, oldActorId, newActorId, lockToken);
}
catch (RpcException e) when (
e.Error == ErrorCode.ERR_LocationLockTokenMismatch
|| e.Error == ErrorCode.ERR_LocationLockOwnerMismatch
|| e.Error == ErrorCode.ERR_LocationLockNotFound)
{
// Lock already expired or was released — verify the actor landed correctly
ActorId current = await proxy.Get(type, key);
if (current == newActorId)
{
Log.Warning($"unlock failed but actor already at target: {key}");
return; // acceptable
}
throw; // genuine failure
} Prevention
- Prefer UnLockWithRetry over UnLock — it compensates for token/owner/not-found automatically.
- Increase lock timeout in LockWithToken for long operations.
- Propagate the lockToken faithfully from lock to unlock without mutation.
- Log the lockToken at lock time to aid debugging if unlock fails.
When it happens
Trigger: UnLock(type, key, oldActorId, newActorId, lockToken) where the lockToken doesn't match the server's stored token, oldActorId differs from the lock owner, or the lock already expired (default 60s timeout) and was garbage-collected by the server.
Common situations: The lock's 60s timeout elapsed before unlock was called. Concurrent unlock attempts where one succeeds and invalidates the token. Actor migration where oldActorId changed between lock and unlock. Process crash after lock but before unlock, leaving a stale lock that expires.
Related errors
- location get failed key: {key} error: {response.Error} messa
- location db manager not found scene: {root.Name}
- location add failed key: {key} actorId: {actorId} error: {re
- location lock failed key: {key} actorId: {actorId} error: {r
- ERR_LocationLockTokenMismatch
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/2d0a38a9b4d6d43a.
Report an issue: GitHub.