Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,6 @@ public void onGeyserEnable() {
// Event must be fired after CommandRegistry has subscribed its listener.
// Also, the subscription for the Permissions class is created when Geyser is initialized.
cloud.fireRegisterPermissionsEvent();
} else {
// This isn't ideal - but geyserLogger#start won't ever finish, leading to a reloading deadlock
geyser.setReloading(false);
}

if (gui != null) {
Expand All @@ -191,7 +188,10 @@ public void onGeyserEnable() {

geyserPingPassthrough = GeyserLegacyPingPassthrough.init(geyser);

geyserLogger.start();
if (!reloading) {
// Only start logger once; no need to re-start it on reload
geyserLogger.start();
}
}

@Override
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/org/geysermc/geyser/GeyserImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ private void startInstance() {
GeyserLogger logger = bootstrap.getGeyserLogger();
GeyserConfig config = bootstrap.config();

ScoreboardUpdater.init();
ScoreboardUpdater.init(this);

SkinProvider.registerCacheImageTask(this);

Expand Down Expand Up @@ -668,6 +668,7 @@ private void startInstance() {
newsHandler.handleNews(null, NewsItemAction.ON_SERVER_STARTED);

if (isReloading) {
isReloading = false;
this.eventBus.fire(new GeyserPostReloadEvent(this.extensionManager, this.eventBus));
} else {
this.eventBus.fire(new GeyserPostInitializeEvent(this.extensionManager, this.eventBus));
Expand Down Expand Up @@ -743,6 +744,7 @@ public void disable() {
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.kick.done"));
}

runIfNonNull(metrics, MetricsBase::shutdown);
runIfNonNull(scheduledThread, ScheduledExecutorService::shutdown);
runIfNonNull(geyserServer, GeyserServer::shutdown);
runIfNonNull(skinUploader, FloodgateSkinUploader::close);
Expand Down Expand Up @@ -778,8 +780,6 @@ public void reloadGeyser() {

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

isReloading = false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.util.VarInts;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.util.JsonUtils;

Expand All @@ -47,7 +50,7 @@
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

public class GeyserLegacyPingPassthrough extends Thread implements IGeyserPingPassthrough, Runnable {
public class GeyserLegacyPingPassthrough extends Thread implements IGeyserPingPassthrough, Runnable, EventRegistrar {
private static final byte[] HAPROXY_BINARY_PREFIX = new byte[]{13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10};

private final GeyserImpl geyser;
Expand All @@ -73,12 +76,23 @@ public GeyserLegacyPingPassthrough(GeyserImpl geyser, int interval) {
GeyserLegacyPingPassthrough pingPassthrough = new GeyserLegacyPingPassthrough(geyser, interval);
pingPassthrough.setName("Geyser LegacyPingPassthrough Thread");
pingPassthrough.setDaemon(true);
pingPassthrough.start();
if (geyser.isReloading()) {
geyser.eventBus().subscribe(pingPassthrough, GeyserPostReloadEvent.class, (ignored) -> pingPassthrough.start());
} else {
geyser.eventBus().subscribe(pingPassthrough, GeyserPostInitializeEvent.class, (ignored) -> pingPassthrough.start());
}
Comment on lines +79 to +83
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.
return pingPassthrough;
}
return null;
}

@Override
public void start() {
// Already started, no need to keep the subscription
geyser.eventBus().unregisterAll(this);
super.start();
}

@Override
public GeyserPingInfo getPingInformation(InetSocketAddress inetSocketAddress) {
return pingInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,49 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.configuration.GeyserConfig;
import org.geysermc.geyser.api.event.EventRegistrar;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent;
import org.geysermc.geyser.api.event.lifecycle.GeyserPostReloadEvent;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.WorldCache;
import org.geysermc.geyser.text.GeyserLocale;

import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;

public final class ScoreboardUpdater extends Thread {
public static final int FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD;
public static final int SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD = 250;
public final class ScoreboardUpdater extends Thread implements EventRegistrar {

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.
private final boolean debugEnabled;

private static final boolean DEBUG_ENABLED;
private final GeyserImpl geyser;
private long lastUpdate = System.currentTimeMillis();
private long lastPacketsPerSecondUpdate = System.currentTimeMillis();

static {
GeyserConfig config = GeyserImpl.getInstance().config();
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD = Math.min(config.advanced().scoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
DEBUG_ENABLED = config.debugMode();
public ScoreboardUpdater(GeyserImpl geyser) {
this.geyser = geyser;
firstScorePacketsPerSecondThreshold = Math.min(geyser.config().advanced().scoreboardPacketThreshold(), SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD);
this.debugEnabled = geyser.config().debugMode();
}

private final GeyserImpl geyser = GeyserImpl.getInstance();

private long lastUpdate = System.currentTimeMillis();
private long lastPacketsPerSecondUpdate = System.currentTimeMillis();
public static void init(GeyserImpl geyser) {
ScoreboardUpdater updater = new ScoreboardUpdater(geyser);
updater.setName("Geyser Scoreboard Updater Thread");
updater.setDaemon(true);
if (geyser.isReloading()) {
geyser.eventBus().subscribe(updater, GeyserPostReloadEvent.class, (ignored) -> updater.start());
} else {
geyser.eventBus().subscribe(updater, GeyserPostInitializeEvent.class, (ignored) -> updater.start());
}
}

public static void init() {
new ScoreboardUpdater().start();
@Override
public void start() {
geyser.eventBus().unregisterAll(this);
super.start();
}

@Override
Expand Down Expand Up @@ -86,8 +99,8 @@ public void run() {
scoreboardSession.pendingPacketsPerSecond.set(0);

// just making sure that all updates are pushed before giving up control
if (oldPps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD &&
newPps < FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (oldPps >= firstScorePacketsPerSecondThreshold &&
newPps < firstScorePacketsPerSecondThreshold) {
session.getWorldCache().getScoreboard().onUpdate();
}
}
Expand All @@ -101,7 +114,7 @@ public void run() {
ScoreboardSession scoreboardSession = worldCache.getScoreboardSession();

int pps = scoreboardSession.getPacketsPerSecond();
if (pps >= FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps >= firstScorePacketsPerSecondThreshold) {
boolean reachedSecondThreshold = pps >= SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD;

int millisBetweenUpdates = reachedSecondThreshold ?
Expand All @@ -112,10 +125,10 @@ public void run() {
worldCache.getScoreboard().onUpdate();
scoreboardSession.lastUpdate = currentTime;

if (DEBUG_ENABLED && (currentTime - scoreboardSession.lastLog >= 60000)) { // one minute
if (debugEnabled && (currentTime - scoreboardSession.lastLog >= 60000)) { // one minute
int threshold = reachedSecondThreshold ?
SECOND_SCORE_PACKETS_PER_SECOND_THRESHOLD :
FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD;
firstScorePacketsPerSecondThreshold;

geyser.getLogger().info(
GeyserLocale.getLocaleStringLog("geyser.scoreboard.updater.threshold_reached.log", session.bedrockUsername(), threshold, pps) +
Expand All @@ -129,7 +142,7 @@ public void run() {
}
}

if (DEBUG_ENABLED) {
if (debugEnabled) {
long timeSpent = System.currentTimeMillis() - currentTime;
if (timeSpent > 0) {
geyser.getLogger().info(String.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void translate(GeyserSession session, ClientboundResetScorePacket packet)

// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher than the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps < ScoreboardUpdater.firstScorePacketsPerSecondThreshold) {
scoreboard.onUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void translate(GeyserSession session, ClientboundSetDisplayObjectivePacke

// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher than the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps < ScoreboardUpdater.firstScorePacketsPerSecondThreshold) {
scoreboard.onUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void translate(GeyserSession session, ClientboundSetObjectivePacket packe

// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher than the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps < ScoreboardUpdater.firstScorePacketsPerSecondThreshold) {
scoreboard.onUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void translate(GeyserSession session, ClientboundSetPlayerTeamPacket pack

// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher than the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps < ScoreboardUpdater.firstScorePacketsPerSecondThreshold) {
scoreboard.onUpdate();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void translate(GeyserSession session, ClientboundSetScorePacket packet) {

// ScoreboardUpdater will handle it for us if the packets per second
// (for score and team packets) is higher than the first threshold
if (pps < ScoreboardUpdater.FIRST_SCORE_PACKETS_PER_SECOND_THRESHOLD) {
if (pps < ScoreboardUpdater.firstScorePacketsPerSecondThreshold) {
scoreboard.onUpdate();
}
}
Expand Down
Loading