egametang/ET · error · RpcException

ERR_LocationAlreadyLocked

ERR_LocationAlreadyLocked

Error message

location add rejected by lock type: {locationType} key: {key} actorId: {currentState.ActorId} lockToken: {currentState.LockToken}

What it means

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.

Source

Thrown at Packages/cn.etetet.actorlocation/Scripts/Hotfix/Server/LocationComponentSystem.cs:275

                if (self == null)
                {
                    return;
                }

                locationInfo ??= self.CreateInfo(key);
                Dictionary<int, LocationTypeState> snapshot = CreateStatesSnapshot(locationInfo);

                if (TryGetRouteState(locationInfo, locationType, out LocationTypeState currentState)
                    && self.IsExpiredLock(currentState))
                {
                    currentState.LockToken = default;
                    currentState.LockExpireTime = default;
                    locationInfo.TypeStates[locationType] = currentState;
                }

                if (TryGetRouteState(locationInfo, locationType, out currentState) && IsLocked(currentState))
                {
                    throw new RpcException(ErrorCode.ERR_LocationAlreadyLocked,
                        $"location add rejected by lock type: {locationType} key: {key} actorId: {currentState.ActorId} lockToken: {currentState.LockToken}");
                }

                bool oldExists = TryGetRouteState(locationInfo, locationType, out LocationTypeState oldState);
                locationInfo.TypeStates[locationType] = new LocationTypeState { ActorId = actorId };

                try
                {
                    await self.SaveInfoToDB(locationInfo);
                }
                catch
                {
                    self = selfRef;
                    if (self != null)
                    {
                        self.RestoreStates(key, snapshot);
                    }

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Search registration/update logs for entries with empty SceneName or default ActorId reaching the proxy (see FormatServiceInfo output).
  2. Ensure all ServiceInfo insertion goes through AddOrUpdateLocalService, whose null/empty/default guard at line 536 must not be bypassed.
  3. Verify scenes that dispose emit a remove notification (OnServiceChangeRemoveService) so stale EntityRefs are cleaned via RemoveLocalService.
  4. Clear the proxy's local cache (ClearLocalServices) and let it re-resolve from the master if corruption is suspected.

Example fix

// before
orderedServices.Add(serviceInfo); // ValidateServiceInfo throws on bad entry

// after
if (serviceInfo == null || string.IsNullOrWhiteSpace(serviceInfo.SceneName) || serviceInfo.ActorId == default)
{
    Log.Error($"skip invalid local cache entry sceneType: {sceneTypeName}");
    continue;
}
orderedServices.Add(serviceInfo);
Defensive patterns

Strategy: validation

Validate before calling

foreach (ServiceInfo si in proxy.GetBySceneType(sceneType))
{
    if (si == null || string.IsNullOrWhiteSpace(si.SceneName) || si.ActorId == default)
    {
        Log.Error($"invalid cached ServiceInfo detected for sceneType {sceneType}");
    }
}

Try / catch

try
{
    ActorId id = proxy.ResolveServiceActorIdByRouteKeyOnce(sceneType, sceneTypeName, routeKey);
}
catch (RpcException ex) when (ex.Error == ErrorCode.ERR_ServiceDiscoveryOperationFailed)
{
    // local cache corruption; clear and re-resolve
    proxy.ClearLocalServices(true);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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