Fix: Issues related to Geyser's reload command#6179
Fix: Issues related to Geyser's reload command#6179onebeastchris wants to merge 1 commit intoGeyserMC:masterfrom
Conversation
- ScoreboardUpdater and GeyserLegacyPingPassthrough should be started after the reload finishes - Metrics should be shut down to avoid thread leaking
There was a problem hiding this comment.
Pull request overview
This PR adjusts Geyser’s reload lifecycle so background services (notably scoreboard updating and legacy ping passthrough) start only after reload completion, and ensures metrics are shut down during disable to prevent thread leaks. It targets reload-related scoreboard breakage (Issue #6169) and makes the scoreboard packet threshold reconfigurable via reload.
Changes:
- Make scoreboard packet threshold reloadable by replacing the static-final threshold constant with a runtime-updated value.
- Delay
ScoreboardUpdaterstartup until afterGeyserPostInitializeEvent/GeyserPostReloadEvent. - Shut down metrics on disable to avoid leaking threads; adjust standalone bootstrap reload behavior around logger startup.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetScoreTranslator.java | Use the new reloadable scoreboard threshold value. |
| core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetPlayerTeamTranslator.java | Use the new reloadable scoreboard threshold value. |
| core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetObjectiveTranslator.java | Use the new reloadable scoreboard threshold value. |
| core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaSetDisplayObjectiveTranslator.java | Use the new reloadable scoreboard threshold value. |
| core/src/main/java/org/geysermc/geyser/translator/protocol/java/scoreboard/JavaResetScorePacket.java | Use the new reloadable scoreboard threshold value. |
| core/src/main/java/org/geysermc/geyser/scoreboard/ScoreboardUpdater.java | Defer updater thread start until post-init/post-reload; make threshold configurable across reloads. |
| core/src/main/java/org/geysermc/geyser/ping/GeyserLegacyPingPassthrough.java | Attempt to defer legacy ping passthrough thread start until post-init/post-reload. |
| core/src/main/java/org/geysermc/geyser/GeyserImpl.java | Start ScoreboardUpdater with instance context; fire post-reload after clearing isReloading; shut down metrics on disable. |
| bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneBootstrap.java | Avoid restarting the standalone logger on reload; adjust reload flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (geyser.isReloading()) { | ||
| geyser.eventBus().subscribe(pingPassthrough, GeyserPostReloadEvent.class, (ignored) -> pingPassthrough.start()); | ||
| } else { | ||
| geyser.eventBus().subscribe(pingPassthrough, GeyserPostInitializeEvent.class, (ignored) -> pingPassthrough.start()); | ||
| } |
There was a problem hiding this comment.
GeyserLegacyPingPassthrough.init(...) now only starts the thread via GeyserPostInitializeEvent/GeyserPostReloadEvent subscriptions. However, every bootstrap calls GeyserLegacyPingPassthrough.init(geyser) after GeyserImpl.start(), and those lifecycle events are fired inside startInstance() before start() returns. This means the event has already fired by the time you subscribe, so the ping passthrough thread will never start (both on initial enable and reload). Consider starting the thread immediately here (and only delay if you can guarantee subscription happens before the event is fired), or move initialization earlier in the startup sequence.
| if (geyser.isReloading()) { | |
| geyser.eventBus().subscribe(pingPassthrough, GeyserPostReloadEvent.class, (ignored) -> pingPassthrough.start()); | |
| } else { | |
| geyser.eventBus().subscribe(pingPassthrough, GeyserPostInitializeEvent.class, (ignored) -> pingPassthrough.start()); | |
| } | |
| // Start immediately instead of relying on lifecycle events that may have already fired | |
| pingPassthrough.start(); |
| private static final int FIRST_MILLIS_BETWEEN_UPDATES = 250; // 4 updates per second | ||
| private static final int SECOND_MILLIS_BETWEEN_UPDATES = 1000; // 1 update per second | ||
| public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250; | ||
| public static int firstScorePacketsPerSecondThreshold; |
There was a problem hiding this comment.
firstScorePacketsPerSecondThreshold is written during (re)initialization and read from multiple threads (packet translators + ScoreboardUpdater thread). As a plain static int, updates aren’t guaranteed to be visible across threads after a reload under the Java memory model. Mark this field volatile (or use an AtomicInteger / accessor with proper synchronization) so reload-time threshold changes are reliably observed.
| public static int firstScorePacketsPerSecondThreshold; | |
| public static volatile int firstScorePacketsPerSecondThreshold; |
| public void reloadGeyser() { | ||
| isReloading = true; | ||
| this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus)); | ||
|
|
||
| bootstrap.onGeyserDisable(); | ||
| bootstrap.onGeyserEnable(); | ||
|
|
||
| isReloading = false; | ||
| } |
There was a problem hiding this comment.
isReloading is no longer reset in reloadGeyser(); it’s now only set back to false in startInstance() when the reload completes successfully. If bootstrap.onGeyserEnable() returns early (e.g., config failed to load/validate on reload), startInstance() won’t run and isReloading will remain true indefinitely, which will stop components that gate on isReloading (e.g., ScoreboardUpdater/ping threads). Consider resetting isReloading in a finally block in reloadGeyser(), or otherwise ensuring failure paths clear the flag.
This would resolve #6169 and allow re-configuring the scoreboard update threshold with a reload.