diff --git a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java index cbc191101..0cc6f8946 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FloodgateSkinUploader.java @@ -28,7 +28,18 @@ package org.geysermc.geyser.skin; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; +import java.net.ConnectException; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLException; import lombok.Getter; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.geysermc.floodgate.pluginmessage.PluginMessageChannels; import org.geysermc.floodgate.util.WebsocketEventType; import org.geysermc.geyser.Constants; @@ -40,23 +51,23 @@ import org.geysermc.geyser.util.PluginMessageUtils; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; -import javax.net.ssl.SSLException; -import java.net.ConnectException; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - public final class FloodgateSkinUploader { - private final List skinQueue = new ArrayList<>(); + private static final int MAX_QUEUED_ENTRIES = 500; private final GeyserLogger logger; private final WebSocketClient client; private volatile boolean closed; + /** + * Queue skins in case the global api is temporarily unavailable, so that players will have their skin when the + * global api comes back online. + * However, we only start queueing skins if the websocket has been opened before, which is why this is nullable. + * Some servers block access to external sites such as our global api, in which case the skin upload queue would + * grow without the skins having a chance to be actually uploaded. + * We'll lose player skins if the server started while the global api was offline, but that's worth the trade-off. + */ + private @MonotonicNonNull Deque skinQueue = null; + @Getter private int id; @Getter private String verifyCode; @Getter private int subscribersCount; @@ -68,10 +79,19 @@ public final class FloodgateSkinUploader { public void onOpen(ServerHandshake handshake) { setConnectionLostTimeout(11); - Iterator queueIterator = skinQueue.iterator(); - while (isOpen() && queueIterator.hasNext()) { - send(queueIterator.next()); - queueIterator.remove(); + boolean hasSkinQueue = skinQueue != null; + + if (!hasSkinQueue) { + skinQueue = new LinkedList<>(); + return; + } + + synchronized (skinQueue) { + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } } } @@ -210,7 +230,16 @@ public final class FloodgateSkinUploader { client.send(jsonString); return; } - skinQueue.add(jsonString); + + if (skinQueue != null) { + synchronized (skinQueue) { + // Only keep the most recent skins if we hit the limit, as it's more likely that they're still online + if (skinQueue.size() >= MAX_QUEUED_ENTRIES) { + skinQueue.removeFirst(); + } + skinQueue.addLast(jsonString); + } + } } private void reconnectLater(GeyserImpl geyser) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 596ee1a68..035da3f1b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,9 +13,9 @@ guava = "29.0-jre" gson = "2.3.1" # Provided by Spigot 1.8.8 TODO bump to 2.8.1 or similar (Spigot 1.16.5 version) after Merge gson-runtime = "2.10.1" websocket = "1.5.1" -protocol-connection = "3.0.0.Beta11-20251210.195537-15" -protocol-common = "3.0.0.Beta11-20251210.195537-14" -protocol-codec = "3.0.0.Beta11-20251210.195537-15" +protocol-connection = "3.0.0.Beta11-20251212.150341-16" +protocol-common = "3.0.0.Beta11-20251212.150341-15" +protocol-codec = "3.0.0.Beta11-20251212.150341-16" raknet = "1.0.0.CR3-20251208.214317-23" minecraftauth = "5.0.0" mcprotocollib = "1.21.11-20251210.192611-9"