{"record":{"id":"92f66b0505ded054","repo":"egametang/ET","slug":"location-lock-failed-key-key-actorid-actorid","errorCode":null,"errorMessage":"location lock failed key: {key} actorId: {actorId} error: {response.Message}","messagePattern":"location lock failed key: (.+?) actorId: (.+?) error: (.+?)","errorType":"exception","errorClass":"RpcException","httpStatus":null,"severity":"error","filePath":"Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationProxyComponentSystem.cs","lineNumber":235,"sourceCode":"                    $\"location add failed key: {key} actorId: {actorId} error: {response.Message}\");\n            }\n        }\n\n        public static async ETTask<long> LockWithToken(this LocationProxyComponent self, int type, long key, ActorId actorId,\n            int time = 60000)\n        {\n            Log.Info($\"location proxy lock {key}, {actorId} {self.GetSingleton<TimeInfo>().ServerNow()}\");\n\n            ObjectLockRequest request = ObjectLockRequest.Create();\n            request.Type = type;\n            request.Key = key;\n            request.ActorId = actorId;\n            request.Time = time;\n\n            ObjectLockResponse response = (ObjectLockResponse)await self.CallPrimaryWithRetry(key, request);\n            if (response.Error != ErrorCode.ERR_Success)\n            {\n                throw new RpcException(response.Error,\n                    $\"location lock failed key: {key} actorId: {actorId} error: {response.Message}\");\n            }\n\n            return response.LockToken;\n        }\n\n        [Obsolete(\"Use LockWithToken and pass the returned token to UnLock.\", true)]\n        public static async ETTask Lock(this LocationProxyComponent self, int type, long key, ActorId actorId,\n            int time = 60000)\n        {\n            await self.LockWithToken(type, key, actorId, time);\n        }\n\n        [Obsolete(\"Use UnLock overload with lockToken.\", true)]\n        public static ETTask UnLock(this LocationProxyComponent self, int type, long key, ActorId oldActorId,\n            ActorId newActorId)\n        {\n            throw new RpcException(ErrorCode.ERR_LocationLockTokenMismatch,","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationProxyComponentSystem.cs#L217-L253","documentation":"Thrown by LocationProxyComponent.LockWithToken when the RPC response from the primary Location scene returns a non-success error code. This wraps the server-side error with proxy-level context. The most common underlying cause is ERR_LocationAlreadyLocked (another actor holds the lock) or ERR_LocationLockOwnerMismatch (a different actor owns the route entry).","triggerScenarios":"LocationProxyComponent.LockWithToken(type, key, actorId, time) calls CallPrimaryWithRetry, gets response.Error != ERR_Success. LockWithToken does NOT internally retry on ERR_LocationAlreadyLocked (that is not in the retry-eligible set), so a lock conflict surfaces immediately as this exception.","commonSituations":"Another actor already holds the lock (ERR_LocationAlreadyLocked); the route entry is owned by a different actor (ERR_LocationLockOwnerMismatch); primary was unavailable after all retries (ERR_LocationPrimaryUnavailable).","solutions":["Catch RpcException and inspect e.Error: ERR_LocationAlreadyLocked requires waiting for the lock to release or expire before retrying.","For ERR_LocationLockOwnerMismatch, ensure the actorId matches the current route owner before locking.","Implement a retry loop with backoff at the application level for transient lock conflicts.","For ERR_LocationPrimaryUnavailable after exhaustion, verify Location scene topology."],"exampleFix":"// before -- unhandled lock\nlong token = await locationProxy.LockWithToken(type, key, actorId, 60000);\n\n// after -- retry on lock conflict with backoff\nlong token = 0;\nfor (int i = 0; i < maxRetries; i++)\n{\n    try\n    {\n        token = await locationProxy.LockWithToken(type, key, actorId, 60000);\n        break;\n    }\n    catch (RpcException e) when (e.Error == ErrorCode.ERR_LocationAlreadyLocked && i < maxRetries - 1)\n    {\n        await root.TimerComponent.WaitAsync(100 * (i + 1));\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try\n{\n    token = await locationProxy.LockWithToken(type, key, actorId, 60000);\n}\ncatch (RpcException e) when (e.Error == ErrorCode.ERR_LocationAlreadyLocked)\n{\n    // Retry with backoff or fail gracefully\n    Log.Warning($\"Lock conflict on key {key}: {e.Message}\");\n}\ncatch (RpcException e) when (e.Error == ErrorCode.ERR_LocationLockOwnerMismatch)\n{\n    Log.Warning($\"Lock owner mismatch on key {key}: {e.Message}\");\n}","preventionTips":["Catch RpcException from LockWithToken and handle by error code -- AlreadyLocked and OwnerMismatch are the most common.","For contention scenarios, implement a bounded retry loop with backoff at the application level.","Always store the returned token and pass it to the matching UnLock."],"tags":["location","proxy","rpc","wrapper","lock"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}