From 6d18e8e9b6b707d392d574077f6889736ba610e0 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Mon, 3 Apr 2023 18:41:09 -0700 Subject: [PATCH] Make uses of SimpleDateFormat thread-safe Turns out, the utility is not thread-safe to use for whatever reason. So, we need to use ThreadLocal to create instances per thread. --- patches/server/0005-Threaded-Regions.patch | 142 ++++++++++++++++++- patches/server/0008-Max-pending-logins.patch | 2 +- 2 files changed, 136 insertions(+), 8 deletions(-) diff --git a/patches/server/0005-Threaded-Regions.patch b/patches/server/0005-Threaded-Regions.patch index 94f8a37..ccbec54 100644 --- a/patches/server/0005-Threaded-Regions.patch +++ b/patches/server/0005-Threaded-Regions.patch @@ -11011,6 +11011,37 @@ index 0000000000000000000000000000000000000000..cf9b66afc1762dbe2c625f09f9e804ca + } + } +} +diff --git a/src/main/java/net/minecraft/advancements/CriterionProgress.java b/src/main/java/net/minecraft/advancements/CriterionProgress.java +index f40d6eaa6ebbd775cd3feb41546423fe4cbf2b22..e43c95e3be6cb41eab0a1cecbf154350e70b7b79 100644 +--- a/src/main/java/net/minecraft/advancements/CriterionProgress.java ++++ b/src/main/java/net/minecraft/advancements/CriterionProgress.java +@@ -12,7 +12,7 @@ import javax.annotation.Nullable; + import net.minecraft.network.FriendlyByteBuf; + + public class CriterionProgress { +- private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); ++ private static final ThreadLocal DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)); // Folia - region threading - SDF is not thread-safe + @Nullable + private Date obtained; + +@@ -43,7 +43,7 @@ public class CriterionProgress { + } + + public JsonElement serializeToJson() { +- return (JsonElement)(this.obtained != null ? new JsonPrimitive(DATE_FORMAT.format(this.obtained)) : JsonNull.INSTANCE); ++ return (JsonElement)(this.obtained != null ? new JsonPrimitive(DATE_FORMAT.get().format(this.obtained)) : JsonNull.INSTANCE); // Folia - region threading - SDF is not thread-safe + } + + public static CriterionProgress fromNetwork(FriendlyByteBuf buf) { +@@ -56,7 +56,7 @@ public class CriterionProgress { + CriterionProgress criterionProgress = new CriterionProgress(); + + try { +- criterionProgress.obtained = DATE_FORMAT.parse(datetime); ++ criterionProgress.obtained = DATE_FORMAT.get().parse(datetime); // Folia - region threading - SDF is not thread-safe + return criterionProgress; + } catch (ParseException var3) { + throw new JsonSyntaxException("Invalid datetime: " + datetime, var3); diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java index 7b6b51392b123d34382233adcf4c3d4867bdaa32..4c793605566f1755c99496c00dc1ef4f38c4ac7d 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -17282,12 +17313,81 @@ index 2ff578e4a953ffcf5176815ba8e3f06f73499989..2e96377d628b3a07fb565020074d665f private State() {} } +diff --git a/src/main/java/net/minecraft/server/players/BanListEntry.java b/src/main/java/net/minecraft/server/players/BanListEntry.java +index 8f19876c20ce9cce574ebbec386a26ab5c807e18..4627c7bd326ca42c9476ec17867dba2ad4191b86 100644 +--- a/src/main/java/net/minecraft/server/players/BanListEntry.java ++++ b/src/main/java/net/minecraft/server/players/BanListEntry.java +@@ -10,7 +10,7 @@ import net.minecraft.network.chat.Component; + + public abstract class BanListEntry extends StoredUserEntry { + +- public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); ++ public static final ThreadLocal DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)); // Folia - region threading - SDF is not thread-safe + public static final String EXPIRES_NEVER = "forever"; + protected final Date created; + protected final String source; +@@ -32,7 +32,7 @@ public abstract class BanListEntry extends StoredUserEntry { + Date date; + + try { +- date = json.has("created") ? BanListEntry.DATE_FORMAT.parse(json.get("created").getAsString()) : new Date(); ++ date = json.has("created") ? BanListEntry.DATE_FORMAT.get().parse(json.get("created").getAsString()) : new Date(); // Folia - region threading - SDF is not thread-safe + } catch (ParseException parseexception) { + date = new Date(); + } +@@ -43,7 +43,7 @@ public abstract class BanListEntry extends StoredUserEntry { + Date date1; + + try { +- date1 = json.has("expires") ? BanListEntry.DATE_FORMAT.parse(json.get("expires").getAsString()) : null; ++ date1 = json.has("expires") ? BanListEntry.DATE_FORMAT.get().parse(json.get("expires").getAsString()) : null; // Folia - region threading - SDF is not thread-safe + } catch (ParseException parseexception1) { + date1 = null; + } +@@ -78,9 +78,9 @@ public abstract class BanListEntry extends StoredUserEntry { + + @Override + protected void serialize(JsonObject json) { +- json.addProperty("created", BanListEntry.DATE_FORMAT.format(this.created)); ++ json.addProperty("created", BanListEntry.DATE_FORMAT.get().format(this.created)); // Folia - region threading - SDF is not thread-safe + json.addProperty("source", this.source); +- json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires)); ++ json.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.get().format(this.expires)); // Folia - region threading - SDF is not thread-safe + json.addProperty("reason", this.reason); + } + +@@ -89,7 +89,7 @@ public abstract class BanListEntry extends StoredUserEntry { + Date expires = null; + + try { +- expires = jsonobject.has("expires") ? BanListEntry.DATE_FORMAT.parse(jsonobject.get("expires").getAsString()) : null; ++ expires = jsonobject.has("expires") ? BanListEntry.DATE_FORMAT.get().parse(jsonobject.get("expires").getAsString()) : null; // Folia - region threading - SDF is not thread-safe + } catch (ParseException ex) { + // Guess we don't have a date + } +diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +index 7edd4b88eb0476f0630630bc4681e859bd145b2b..f3586a5c5b5d4cae817aa7c15fc0c2fc4cd6dc09 100644 +--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java ++++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java +@@ -517,7 +517,7 @@ public class OldUsersConverter { + Date date1; + + try { +- date1 = BanListEntry.DATE_FORMAT.parse(dateString); ++ date1 = BanListEntry.DATE_FORMAT.get().parse(dateString); // Folia - region threading - SDF is not thread-safe + } catch (ParseException parseexception) { + date1 = fallback; + } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0dbe182fbae5ce5ba182176eb5d5e3f1897e77f2..0bdd6f4bea2b3fa7fef2c0a739af37f9c21ff5cd 100644 +index 0dbe182fbae5ce5ba182176eb5d5e3f1897e77f2..b9bd88fa6536e17cccec8a4f482b9b272be9568f 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -139,7 +139,7 @@ public abstract class PlayerList { - private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); +@@ -136,10 +136,10 @@ public abstract class PlayerList { + public static final Component CHAT_FILTERED_FULL = Component.translatable("chat.filtered_full"); + private static final Logger LOGGER = LogUtils.getLogger(); + private static final int SEND_PLAYER_INFO_INTERVAL = 600; +- private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z"); ++ private static final ThreadLocal BAN_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z")); // Folia - region threading - SDF is not thread-safe private final MinecraftServer server; public final List players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety - private final Map playersByUUID = Maps.newHashMap(); @@ -17505,7 +17605,24 @@ index 0dbe182fbae5ce5ba182176eb5d5e3f1897e77f2..0bdd6f4bea2b3fa7fef2c0a739af37f9 } // Instead of kicking then returning, we need to store the kick reason -@@ -724,7 +785,7 @@ public abstract class PlayerList { +@@ -704,7 +765,7 @@ public abstract class PlayerList { + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason()); + if (gameprofilebanentry.getExpires() != null) { +- ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires()))); ++ ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.get().format(gameprofilebanentry.getExpires()))); // Folia - region threading - SDF is not thread-safe + } + + // return chatmessage; +@@ -717,14 +778,14 @@ public abstract class PlayerList { + + ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipbanentry.getReason()); + if (ipbanentry.getExpires() != null) { +- ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned_ip.expiration", PlayerList.BAN_DATE_FORMAT.format(ipbanentry.getExpires()))); ++ ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned_ip.expiration", PlayerList.BAN_DATE_FORMAT.get().format(ipbanentry.getExpires()))); // Folia - region threading - SDF is not thread-safe + } + + // return chatmessage; event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; @@ -20407,9 +20524,18 @@ index c6d2f764efa9b8bec730bbe757d480e365b25ccc..af9313a3b3aaa0af4f2a2f4fb2424dc3 } diff --git a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java -index 888936385196a178ab8b730fd5e4fff4a5466428..7f09f2864c73c8e144475f2168e4a9d9b8aa4642 100644 +index 888936385196a178ab8b730fd5e4fff4a5466428..a49621dcc5915c86bf0327a4903706817c7b8ff4 100644 --- a/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +++ b/src/main/java/net/minecraft/world/level/BaseCommandBlock.java +@@ -20,7 +20,7 @@ import net.minecraft.world.phys.Vec3; + + public abstract class BaseCommandBlock implements CommandSource { + +- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss"); ++ private static final ThreadLocal TIME_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss")); // Folia - region threading - SDF is not thread-safe + private static final Component DEFAULT_NAME = Component.literal("@"); + private long lastExecution = -1L; + private boolean updateLastExecution = true; @@ -111,6 +111,7 @@ public abstract class BaseCommandBlock implements CommandSource { } @@ -20418,7 +20544,7 @@ index 888936385196a178ab8b730fd5e4fff4a5466428..7f09f2864c73c8e144475f2168e4a9d9 if (!world.isClientSide && world.getGameTime() != this.lastExecution) { if ("Searge".equalsIgnoreCase(this.command)) { this.lastOutput = Component.literal("#itzlipofutzli"); -@@ -169,10 +170,12 @@ public abstract class BaseCommandBlock implements CommandSource { +@@ -169,11 +170,13 @@ public abstract class BaseCommandBlock implements CommandSource { } @@ -20428,10 +20554,12 @@ index 888936385196a178ab8b730fd5e4fff4a5466428..7f09f2864c73c8e144475f2168e4a9d9 public void sendSystemMessage(Component message) { if (this.trackOutput) { - org.spigotmc.AsyncCatcher.catchOp("sendSystemMessage to a command block"); // Paper +- SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; + this.threadCheck(); // Folia - SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT; ++ SimpleDateFormat simpledateformat = BaseCommandBlock.TIME_FORMAT.get(); // Folia - region threading - SDF is not thread-safe Date date = new Date(); + this.lastOutput = Component.literal("[" + simpledateformat.format(date) + "] ").append(message); diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java index 3b959f42d958bf0f426853aee56753d6c455fcdb..b1a6a66ed02706c1adc36dcedfa415f5a24a25a0 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java diff --git a/patches/server/0008-Max-pending-logins.patch b/patches/server/0008-Max-pending-logins.patch index 3ca1599..e11cb30 100644 --- a/patches/server/0008-Max-pending-logins.patch +++ b/patches/server/0008-Max-pending-logins.patch @@ -19,7 +19,7 @@ index 2e96377d628b3a07fb565020074d665f594f32e8..75b1877f8c3e4da3183437f327ef3376 } // Folia - region threading - remove delayed accept diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0bdd6f4bea2b3fa7fef2c0a739af37f9c21ff5cd..44c6ff04b7310f3b59a7fa4c600e3221cbec7bb8 100644 +index b9bd88fa6536e17cccec8a4f482b9b272be9568f..5f13a2f03d7a448d86550d90f105edc5dcde1194 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -177,6 +177,17 @@ public abstract class PlayerList {