Skip to content

Fix shutdown crash when flushing actions for unregistered players - #390

Open
luozaixuan wants to merge 1 commit into
QuiltServerTools:backport/1.20.1from
luozaixuan:backport/1.20.1
Open

Fix shutdown crash when flushing actions for unregistered players#390
luozaixuan wants to merge 1 commit into
QuiltServerTools:backport/1.20.1from
luozaixuan:backport/1.20.1

Conversation

@luozaixuan

Copy link
Copy Markdown

Ledger (1.3.18, 1.20.1 Fabric) crashed 100% of the time on server stop when the action queue had a backlog (tens of thousands to hundreds of thousands of entries): flushing inserted into the players table and hit SQLITE_CONSTRAINT_NOTNULL (players.player_name is NOT NULL with no default), the error was mistaken for a transient failure and retried 3 times with backoff, stalling shutdown for ~5 minutes before timing out and losing the whole backlog.

Root cause: insertActions called getOrCreatePlayerId(it.id); that helper reused getOrCreateObjectId, which is designed for single-key lookup tables (Sources/Worlds/ObjectResourceLocations) and, on a miss, inserts only the unique key column. The Players table has a second mandatory column, player_name, so the generated INSERT omitted it and violated the constraint. Player rows are normally created asynchronously by onJoin's logPlayer, so any action produced before that write landed (or by entities/Create fake players with no registered join) hit a missing player row when shutdown force-drained the queue.

Fix: getOrCreatePlayerId now takes the full GameProfile and, on a cache/DB miss, inserts a complete row including player_name (falling back to an unknown_ + uuid-prefix name within the 16-char limit when the profile has no name), keeping both playerKeys and playernameKeys caches in sync.

No schema or migration changes are needed: the NOT NULL constraint guarantees no dirty rows exist in existing databases.

Ledger (1.3.18, 1.20.1 Fabric) crashed 100% of the time on server stop
when the action queue had a backlog (tens of thousands to hundreds of
thousands of entries): flushing inserted into the players table and hit
SQLITE_CONSTRAINT_NOTNULL (players.player_name is NOT NULL with no
default), the error was mistaken for a transient failure and retried 3
times with backoff, stalling shutdown for ~5 minutes before timing out
and losing the whole backlog.

Root cause: insertActions called getOrCreatePlayerId(it.id); that helper
reused getOrCreateObjectId, which is designed for single-key lookup
tables (Sources/Worlds/ObjectResourceLocations) and, on a miss, inserts
only the unique key column. The Players table has a second mandatory
column, player_name, so the generated INSERT omitted it and violated the
constraint. Player rows are normally created asynchronously by onJoin's
logPlayer, so any action produced before that write landed (or by
entities/Create fake players with no registered join) hit a missing
player row when shutdown force-drained the queue.

Fix: getOrCreatePlayerId now takes the full GameProfile and, on a
cache/DB miss, inserts a complete row including player_name (falling
back to an unknown_ + uuid-prefix name within the 16-char limit when the
profile has no name), keeping both playerKeys and playernameKeys caches
in sync.

No schema or migration changes are needed: the NOT NULL constraint
guarantees no dirty rows exist in existing databases.
@luozaixuan
luozaixuan requested a review from a team as a code owner August 19, 2026 06:27
@luozaixuan

Copy link
Copy Markdown
Author

this fix only for 1.20.1-backport,because Ledger with Create will have this error

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant