{"record":{"id":"c9fa954a3ba35d9a","repo":"egametang/ET","slug":"err-locationalreadylocked","errorCode":"ERR_LocationAlreadyLocked","errorMessage":"location add rejected by lock type: {locationType} key: {key} actorId: {currentState.ActorId} lockToken: {currentState.LockToken}","messagePattern":"location add rejected by lock type: (.+?) key: (.+?) actorId: (.+?) lockToken: (.+?)","errorType":"exception","errorClass":"RpcException","httpStatus":null,"severity":"error","filePath":"Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationComponentSystem.cs","lineNumber":275,"sourceCode":"                if (self == null)\n                {\n                    return;\n                }\n\n                locationInfo ??= self.CreateInfo(key);\n                Dictionary<int, LocationTypeState> snapshot = CreateStatesSnapshot(locationInfo);\n\n                if (TryGetRouteState(locationInfo, locationType, out LocationTypeState currentState)\n                    && self.IsExpiredLock(currentState))\n                {\n                    currentState.LockToken = default;\n                    currentState.LockExpireTime = default;\n                    locationInfo.TypeStates[locationType] = currentState;\n                }\n\n                if (TryGetRouteState(locationInfo, locationType, out currentState) && IsLocked(currentState))\n                {\n                    throw new RpcException(ErrorCode.ERR_LocationAlreadyLocked,\n                        $\"location add rejected by lock type: {locationType} key: {key} actorId: {currentState.ActorId} lockToken: {currentState.LockToken}\");\n                }\n\n                bool oldExists = TryGetRouteState(locationInfo, locationType, out LocationTypeState oldState);\n                locationInfo.TypeStates[locationType] = new LocationTypeState { ActorId = actorId };\n\n                try\n                {\n                    await self.SaveInfoToDB(locationInfo);\n                }\n                catch\n                {\n                    self = selfRef;\n                    if (self != null)\n                    {\n                        self.RestoreStates(key, snapshot);\n                    }\n","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/egametang/ET/blob/5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f/Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationComponentSystem.cs#L257-L293","documentation":"Thrown by ValidateServiceInfo when a ServiceInfo entry in the proxy's local cache is null, has a blank SceneName, or has a default ActorId. It guards SelectServiceActorIdByRouteKey against corrupted cache entries that would break the Sort/CompareServiceInfo or produce a route to a dead ActorId. Wrapped as RpcException(ERR_ServiceDiscoveryOperationFailed), which is NOT in the ERR_ServiceNotFound retry set, so it surfaces immediately.","triggerScenarios":"The SceneNameServices cache contains a malformed ServiceInfo: a stale EntityRef<ServiceInfo> whose target was disposed (resolves to null), a ServiceInfo whose SceneName was cleared, or one whose ActorId is default. AddOrUpdateLocalService filters these out at line 536, so a hit means that filter was bypassed or an EntityRef went stale after insertion.","commonSituations":"Target scene disposed without sending a deregister notification, leaving a dangling EntityRef; concurrent mutation of SceneNameServices racing with iteration; partial deserialization after a reload; a code path adding ServiceInfo directly to SceneNameServices instead of through AddOrUpdateLocalService.","solutions":["Search registration/update logs for entries with empty SceneName or default ActorId reaching the proxy (see FormatServiceInfo output).","Ensure all ServiceInfo insertion goes through AddOrUpdateLocalService, whose null/empty/default guard at line 536 must not be bypassed.","Verify scenes that dispose emit a remove notification (OnServiceChangeRemoveService) so stale EntityRefs are cleaned via RemoveLocalService.","Clear the proxy's local cache (ClearLocalServices) and let it re-resolve from the master if corruption is suspected."],"exampleFix":"// before\norderedServices.Add(serviceInfo); // ValidateServiceInfo throws on bad entry\n\n// after\nif (serviceInfo == null || string.IsNullOrWhiteSpace(serviceInfo.SceneName) || serviceInfo.ActorId == default)\n{\n    Log.Error($\"skip invalid local cache entry sceneType: {sceneTypeName}\");\n    continue;\n}\norderedServices.Add(serviceInfo);","handlingStrategy":"validation","validationCode":"foreach (ServiceInfo si in proxy.GetBySceneType(sceneType))\n{\n    if (si == null || string.IsNullOrWhiteSpace(si.SceneName) || si.ActorId == default)\n    {\n        Log.Error($\"invalid cached ServiceInfo detected for sceneType {sceneType}\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    ActorId id = proxy.ResolveServiceActorIdByRouteKeyOnce(sceneType, sceneTypeName, routeKey);\n}\ncatch (RpcException ex) when (ex.Error == ErrorCode.ERR_ServiceDiscoveryOperationFailed)\n{\n    // local cache corruption; clear and re-resolve\n    proxy.ClearLocalServices(true);\n}","preventionTips":["Route all ServiceInfo insertion through AddOrUpdateLocalService so the null/empty/default guard is enforced.","Ensure disposing scenes emit deregister notifications so stale EntityRef<ServiceInfo> entries are removed.","Periodically validate the SceneNameServices cache for dangling EntityRefs in dev/test."],"tags":["servicediscovery","cache-corruption","routing","entityref"],"backgroundTag":null,"analyzedSha":"5cab01f7a8bee5f49f4781eebe9e2b1c6d7ebe0f","analyzedAt":"2026-08-13T21:10:40.377Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}