Skip to content

Fix: Issues related to Geyser's reload command#6179

Open
onebeastchris wants to merge 1 commit intoGeyserMC:masterfrom
onebeastchris:reload-issues
Open

Fix: Issues related to Geyser's reload command#6179
onebeastchris wants to merge 1 commit intoGeyserMC:masterfrom
onebeastchris:reload-issues

Conversation

@onebeastchris
Copy link
Member

@onebeastchris onebeastchris commented Feb 11, 2026

  • ScoreboardUpdater and GeyserLegacyPingPassthrough should be started after the reload finishes
  • Metrics should be shut down to avoid thread leaking
  • Don't start multiple console readers (standalone-specific)

This would resolve #6169 and allow re-configuring the scoreboard update threshold with a reload.

- ScoreboardUpdater and GeyserLegacyPingPassthrough should be started after the reload finishes
- Metrics should be shut down to avoid thread leaking
Copilot AI review requested due to automatic review settings February 11, 2026 14:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ScoreboardUpdater startup until after GeyserPostInitializeEvent / 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.

Comment on lines +79 to +83
if (geyser.isReloading()) {
geyser.eventBus().subscribe(pingPassthrough, GeyserPostReloadEvent.class, (ignored) -> pingPassthrough.start());
} else {
geyser.eventBus().subscribe(pingPassthrough, GeyserPostInitializeEvent.class, (ignored) -> pingPassthrough.start());
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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();

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
public static int firstScorePacketsPerSecondThreshold;
public static volatile int firstScorePacketsPerSecondThreshold;

Copilot uses AI. Check for mistakes.
Comment on lines 777 to 783
public void reloadGeyser() {
isReloading = true;
this.eventBus.fire(new GeyserPreReloadEvent(this.extensionManager, this.eventBus));

bootstrap.onGeyserDisable();
bootstrap.onGeyserEnable();

isReloading = false;
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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.

After the geyser reload command, the scoreboards are not visible.

1 participant