Remove player entities and connected state stranded after an abrupt disconnect - #1385
Open
GeekOfWires wants to merge 3 commits into
Open
Remove player entities and connected state stranded after an abrupt disconnect#1385GeekOfWires wants to merge 3 commits into
GeekOfWires wants to merge 3 commits into
Conversation
A PersistenceMonitor removes a player's world entity via PerformLogout, which keyed the lookup solely off `inZone` -- the zone last reported through an Update heartbeat. On an abrupt disconnect (a crash during or after a zone transfer, or right after a respawn before an Update landed) `inZone` is stale: the live avatar or corpse actually resides in a different zone. The name lookups then find nothing, execution falls through to the "user stalled / caught between zone transfer" no-op, and the entity -- along with its GUID registration and zone population membership -- is stranded in the world until the server restarts. This is the mechanism behind stuck player entities after crashes: a corpse left on the ground, a player standing in a sanctuary respawn tube, or occasionally a body on a continent. Before cleaning up, if the player is not present in `inZone`, search all zones for wherever they actually are and re-point `inZone` there, so the existing four-case logout logic (in-vehicle / standing / released / absent) runs against the correct zone. When the player is found nowhere (genuinely gone), behavior is unchanged and it falls through to the existing no-op. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ister failures Two follow-on gaps in PerformLogout beyond the stale-zone lookup: A body-less connected player -- released, corpse decayed, sitting at the deployment map when the client dropped -- has no player body in any zone, so even the all-zones search finds nothing and execution reaches the "user stalled" no-op. But LivePlayerList.Remove and the squad/galaxy/ population departures live only inside AvatarLogout, so the account stays listed as online (in /who, the portal, squad, galaxy) until a restart, with no visible entity. The no-op branch now resolves the avatar from LivePlayerList by name and, if still present, runs AvatarLogout to clear that residual connected state. The GUID unregister tasks (unregisterPlayer, unregisterObject for the locker) were fire-and-forget; a failure silently leaked the GUID and left the ObjectDelete incomplete until restart. Their futures are now observed and failures logged at error level so the condition is visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reword the comments added by this branch so they describe the behaviour of the code as it now stands, rather than contrasting it with the code it replaced. A reader arriving at these files has no view of the previous implementation, so that framing carries no meaning for them; the before-and-after reasoning belongs in the commit messages, where it already is. No behavioural change: comments only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Addressed the review note about comments describing the change rather than the current behaviour. Pushed as Small footprint here: 1 file / 9 lines, both blocks in
The failure modes being guarded are still documented, since those remain useful to a reader; only the contrast with the prior implementation was dropped. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Players sometimes remain present in the game world, or keep counting as online, after they are no longer connected — most often following a client crash. It shows up as a corpse left on the ground, a player standing in a sanctuary respawn tube, occasionally a body on a continent, or as a "connected" player with no visible entity at all. These persist until the server restarts.
The cause is in how a
PersistenceMonitortears a player down inPerformLogout(AccountPersistenceService.scala). This PR fixes two independent gaps there and adds visibility to a third.1. Logout looked only in the last-recorded zone
PerformLogoutlocated the player viainZone.Players/inZone.AllPlayers, whereinZoneis the zone last reported through anUpdateheartbeat. On an abrupt disconnect this can be stale — a crash during/after a zone transfer, or right after a respawn before the nextUpdatelanded — so the live avatar/corpse actually resides in a different zone. The name lookups then find nothing, execution falls through to the "user stalled / caught between zone transfer" no-op, and the entity (with its GUID registration and zone population membership) is stranded until restart.Now, if the player isn't present in
inZone, all zones are searched andinZoneis re-pointed to wherever they actually are, so the existing four-case logout logic (in-vehicle / standing / released / absent) runs against the correct zone.2. A body-less connected player was never cleared from the online registries
A player who released, whose corpse then decayed, and who was sitting at the deployment map when the client dropped, has no player body in any zone — so even the all-zones search above finds nothing and reaches the same no-op. But
LivePlayerList.Removeand the squad/galaxy/population departures live only insideAvatarLogout, so the account stays listed as online (in/who, the PSFPortal, squad, galaxy) indefinitely, with no visible entity — a "phantom connected".The no-op branch now resolves the avatar from
LivePlayerListby name and, if it is still present, runsAvatarLogoutto clear that residual connected state.3. GUID unregister failures were silent
The
GUIDTask.unregisterPlayer/unregisterObject(locker)tasks were fire-and-forget; a failure silently leaked the GUID and left theObjectDeleteincomplete until restart. Their futures are now observed and failures logged aterrorlevel so the condition is visible.Each corrective branch also logs a
warnwhen it triggers (stale recorded zone, or a phantom-connected cleanup), making these conditions observable in the wild.Scope
Deliberately limited to
PerformLogoutand its helpers. Related hardening left for follow-ups:AccountPersistenceService.Logoutfrom the socket-death path so cleanup fires promptly on disconnect rather than after the ~60s inactivity timer;context.watch/TerminatedinAccountPersistenceServiceso a monitor that dies abnormally still prunes theaccountsmap;avatar.lockerin its own zone when a transfer split the body and locker across zones.Testing
sbt Compile/compilesucceeds.