fix(presence): release the session when a login never arrives#25
Merged
Conversation
Presence is created at PreLoginEvent and removed at DisconnectEvent, and that pair has a hole: Velocity only fires DisconnectEvent for connections that finished logging in. A client that dies in between — an expired Mojang session, a crash, a pulled cable — leaves its row behind with nothing to remove it, and every retry is refused with "you are already online" until the 90s TTL reaps it. Seen in a live workspace: the session was created at 17:50:20, the connection dropped in the same second, and the next two attempts were kicked at 17:50:57 and 17:51:50 — the player was locked out of their own account for 90 seconds after doing nothing wrong. The proxy that created the row now watches it: no player behind that id after a grace period, no session. PostLogin hands ownership to the disconnect path, so a player who joined normally is never touched, and the pending marker is consumed on release so a reconnect in the meantime cannot be logged out by a late timer. A duplicate login that pre-login denied never becomes pending, which is what keeps this from kicking the player who is legitimately online. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015GE1tqMhu6W7g6Ck5fv8h9
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.
Who decides that a player is online
Velocity's connection list is the source of truth. plugin-player registers the session at
PreLoginEvent, heartbeats every online player fromproxy.allPlayers, and deletes the row atDisconnectEvent; service-player expires anything unheartbeated aftergrounds.player.sessions.ttl(90s) with a reaper every 30s.The pair pre-login → disconnect has a hole: Velocity only fires
DisconnectEventfor connections that finished logging in. A client that dies in between — expired Mojang session, crash, pulled cable — leaves its row behind with nothing to remove it.Live evidence from a workspace proxy:
Locked out of their own account for 90 seconds, then healed by the TTL. The reaper is doing its job; the session simply should never have outlived the connection.
Change
The proxy that created the row watches it. After a grace period (15s — long enough for auth, config phase and the first server connect), if no
Playerexists for that id, the session is released.PostLoginEventhands ownership to the disconnect path, so a player who joined normally is never touched.The decision is a pure function (
shouldReleaseAbandonedLogin) so it is tested without mocking Velocity.Verified
./gradlew buildgreen🤖 Generated with Claude Code
https://claude.ai/code/session_015GE1tqMhu6W7g6Ck5fv8h9