From 4d2a55c60f220260b077e0568ce45ce69f7eff9a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 14:39:51 -0400 Subject: [PATCH] Identify and patch MegaMek's single-game-per-JVM blocker IsolationCheck shows two games in one JVM are properly isolated at the data layer: distinct Entity, Game, Board and Player objects, and two Servers bind and coexist. Entity IDs overlap across games, which is harmless since each Game indexes its own. The transport layer is not isolated. Server's constructor ends with 'serverInstance = this', and AbstractGameManager routes every outbound packet through the static Server.getServerInstance(). Constructing a second Server therefore redirects the first game's packets to the second game's connections. Observed symptom: games run fine until game 2 starts, then every game wedges with 'invalid movement/deployment packet' and all threads go idle. Patch gives AbstractGameManager a reference to its owning Server, set from the Server constructor, falling back to the static when unset so existing single game paths are unchanged. Two files, ~20 lines. MegaMek.jar is sealed, so patched classes cannot shadow it via the classpath; build-patched-jar.sh recompiles and injects them into a copy of the jar. Co-Authored-By: Claude Opus 5 --- .gitignore | 2 + harness/build.sh | 2 +- harness/run.sh | 4 +- harness/src/bench/IsolationCheck.java | 17 + harness/src/spike/TextRenderer.java | 206 +++ harness/src/spike/WatchGame.java | 185 ++ patches/build-patched-jar.sh | 37 + .../megamek/server/AbstractGameManager.java | 316 ++++ patches/src/megamek/server/Server.java | 1556 +++++++++++++++++ results/patched8.csv | 31 + results/staggered8.csv | 210 +++ results/stall-jstack.txt | 735 ++++++++ 12 files changed, 3299 insertions(+), 2 deletions(-) create mode 100644 harness/src/spike/TextRenderer.java create mode 100644 harness/src/spike/WatchGame.java create mode 100755 patches/build-patched-jar.sh create mode 100644 patches/src/megamek/server/AbstractGameManager.java create mode 100644 patches/src/megamek/server/Server.java create mode 100644 results/patched8.csv create mode 100644 results/stall-jstack.txt diff --git a/.gitignore b/.gitignore index 4c0f0e8..d0ae912 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ harness/lib/ # Runtime noise logs/ *.log +MegaMek-patched.jar +patches/out/ diff --git a/harness/build.sh b/harness/build.sh index c9e531b..0183868 100755 --- a/harness/build.sh +++ b/harness/build.sh @@ -22,6 +22,6 @@ mkdir -p "$OUT" -classpath "$CP" \ -d "$OUT" \ -Xlint:-options \ - "$ROOT"/harness/src/bench/*.java + "$ROOT"/harness/src/bench/*.java "$ROOT"/harness/src/spike/*.java echo "built -> $OUT" diff --git a/harness/run.sh b/harness/run.sh index bf4cf48..dfda643 100755 --- a/harness/run.sh +++ b/harness/run.sh @@ -13,7 +13,9 @@ MM_HOME="${MM_HOME:-$ROOT/MegaMek-0.51.00}" JAVA_HOME="${JAVA_HOME:-$ROOT/jdk-21.0.12+8}" HEAP="${HEAP:-6g}" -CP="$ROOT/harness/out:$MM_HOME/MegaMek.jar" +# MM_JAR=../MegaMek-patched.jar selects the multi-tenancy-patched build. +MM_JAR="${MM_JAR:-$MM_HOME/MegaMek.jar}" +CP="$ROOT/harness/out:$MM_JAR" for jar in "$MM_HOME"/lib/*.jar; do CP="$CP:$jar"; done cd "$MM_HOME" diff --git a/harness/src/bench/IsolationCheck.java b/harness/src/bench/IsolationCheck.java index 9d55e20..c89424c 100644 --- a/harness/src/bench/IsolationCheck.java +++ b/harness/src/bench/IsolationCheck.java @@ -102,6 +102,23 @@ public final class IsolationCheck { System.out.printf(" server B game == game B: %s%n", serverB.getGame() == gameB); System.out.printf(" servers see different games: %s%n", serverA.getGame() != serverB.getGame()); + // 5. The blocker. Server's constructor ends with `serverInstance = this`, and + // AbstractGameManager.send() routes every outbound packet through + // Server.getServerInstance(). So the moment a second Server exists, the first + // game's manager starts transmitting to the second game's connections. + // Isolation of the *data* (checks 1-3) is real; isolation of the *transport* + // is not. + Server current = Server.getServerInstance(); + System.out.println(); + System.out.printf("[5] Server.getServerInstance() == serverB (newest): %s%n", current == serverB); + System.out.printf(" Server.getServerInstance() == serverA (first): %s%n", current == serverA); + System.out.printf(" -> game A's manager would send packets to %s%n", + current == serverB ? "SERVER B ** WRONG GAME **" : "server A"); + System.out.printf(" verdict: %s%n", + current == serverA + ? "ok" + : "FAIL - concurrent games in one JVM corrupt each other's transport"); + serverA.die(); serverB.die(); System.exit(0); diff --git a/harness/src/spike/TextRenderer.java b/harness/src/spike/TextRenderer.java new file mode 100644 index 0000000..fe71593 --- /dev/null +++ b/harness/src/spike/TextRenderer.java @@ -0,0 +1,206 @@ +package spike; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import megamek.common.Player; +import megamek.common.board.Board; +import megamek.common.board.Coords; +import megamek.common.game.Game; +import megamek.common.units.Entity; +import megamek.common.units.Terrains; + +/** + * Renders live game state as text. + * + *

This is a deliberate stand-in for the eventual web client's data path. A GIF of the + * board is not enough to play from - it shows terrain and unit positions but none of the + * information the Swing client puts in its side panels: per-location armor, heat, whether + * a unit has already moved, what the last round's dice actually did. Everything rendered + * here comes off the client's own {@link Game} object, which is exactly what a browser + * client would be fed over a websocket. + * + *

Rendered from a viewing player's perspective so that double-blind filtering, when it + * matters, is honoured by construction rather than bolted on. + */ +public final class TextRenderer { + + private TextRenderer() { + } + + /** MegaMek displays coordinates 1-indexed as a 4-digit column/row pair. */ + private static String coordLabel(Coords c) { + if (c == null) { + return "----"; + } + return String.format("%02d%02d", c.getX() + 1, c.getY() + 1); + } + + private static final String[] FACING = { "N", "NE", "SE", "S", "SW", "NW" }; + + private static String facing(int f) { + return (f >= 0 && f < FACING.length) ? FACING[f] : "?"; + } + + /** One character per hex, picking the most tactically relevant feature present. */ + private static char terrainChar(Board board, int x, int y) { + var hex = board.getHex(x, y); + if (hex == null) { + return ' '; + } + if (hex.containsTerrain(Terrains.BUILDING)) { + return '#'; + } + if (hex.containsTerrain(Terrains.WATER)) { + return '~'; + } + if (hex.containsTerrain(Terrains.WOODS)) { + return hex.terrainLevel(Terrains.WOODS) >= 2 ? 'A' : '^'; + } + if (hex.containsTerrain(Terrains.JUNGLE)) { + return 'J'; + } + if (hex.containsTerrain(Terrains.RUBBLE)) { + return ':'; + } + if (hex.containsTerrain(Terrains.ROUGH)) { + return '%'; + } + if (hex.containsTerrain(Terrains.SWAMP)) { + return 's'; + } + if (hex.containsTerrain(Terrains.ROAD)) { + return '='; + } + return '.'; + } + + public static String render(Game game, Player viewer) { + StringBuilder sb = new StringBuilder(8192); + + sb.append("=== ROUND ").append(game.getCurrentRound()) + .append(" | PHASE ").append(game.getPhase()); + if (viewer != null) { + sb.append(" | VIEW ").append(viewer.getName()); + } + sb.append(" ===\n\n"); + + List entities = new ArrayList<>(game.getEntitiesVector()); + entities.sort(Comparator.comparingInt(Entity::getOwnerId).thenComparingInt(Entity::getId)); + + sb.append(renderBoard(game, entities)); + sb.append('\n'); + sb.append(renderUnits(game, entities)); + + return sb.toString(); + } + + private static String renderBoard(Game game, List entities) { + Board board = game.getBoard(0); + if (board == null) { + return "(no board)\n"; + } + int width = board.getWidth(); + int height = board.getHeight(); + + // Units are overlaid as digits keyed to the unit table below, so the map and + // the stat block can be read together without guessing which blip is which. + char[][] overlay = new char[height][width]; + for (int i = 0; i < entities.size(); i++) { + Entity e = entities.get(i); + Coords pos = e.getPosition(); + if (pos == null || e.isDestroyed()) { + continue; + } + if (pos.getY() >= 0 && pos.getY() < height && pos.getX() >= 0 && pos.getX() < width) { + overlay[pos.getY()][pos.getX()] = (char) ('1' + (i % 9)); + } + } + + StringBuilder sb = new StringBuilder(); + sb.append("BOARD ").append(width).append('x').append(height) + .append(" . clear ^ light woods A heavy woods ~ water % rough") + .append(" # building = road : rubble\n"); + sb.append(" "); + for (int x = 0; x < width; x++) { + sb.append(String.format("%-2d", (x + 1) % 100)); + } + sb.append('\n'); + + for (int y = 0; y < height; y++) { + sb.append(String.format(" %2d ", y + 1)); + // Odd columns sit half a hex lower; indent them so the offset grid reads + // roughly like the real map instead of a rectangular lie. + for (int x = 0; x < width; x++) { + char c = overlay[y][x] != 0 ? overlay[y][x] : terrainChar(board, x, y); + sb.append(c).append(x % 2 == 0 ? ' ' : ' '); + } + sb.append('\n'); + } + return sb.toString(); + } + + private static String renderUnits(Game game, List entities) { + StringBuilder sb = new StringBuilder(); + sb.append("UNITS\n"); + sb.append(String.format(" %-3s %-4s %-12s %-24s %-5s %-4s %-4s %-5s %-11s %-9s %s%n", + "key", "id", "owner", "unit", "pos", "face", "elev", "heat", "armor", "internal", "status")); + + for (int i = 0; i < entities.size(); i++) { + Entity e = entities.get(i); + String status; + if (e.isDestroyed()) { + status = "DESTROYED"; + } else if (e.isDone()) { + status = "done"; + } else { + status = "active"; + } + + sb.append(String.format(" %-3s %-4d %-12s %-24s %-5s %-4s %-4d %-5s %-11s %-9s %s%n", + String.valueOf((char) ('1' + (i % 9))), + e.getId(), + truncate(ownerName(game, e), 12), + truncate(e.getShortName(), 24), + coordLabel(e.getPosition()), + facing(e.getFacing()), + e.getElevation(), + e.getHeat() + "/" + e.getHeatCapacity(), + e.getTotalArmor() + "/" + e.getTotalOArmor(), + e.getTotalInternal() + "/" + e.getTotalOInternal(), + status)); + } + + // Per-location armor is the detail that decides whether you twist, run or + // stand and shoot, so it cannot be dropped from a playable view. + sb.append("\nARMOR BY LOCATION (current/original, internal in parens)\n"); + for (int i = 0; i < entities.size(); i++) { + Entity e = entities.get(i); + if (e.isDestroyed()) { + continue; + } + sb.append(String.format(" [%s] %-24s ", (char) ('1' + (i % 9)), truncate(e.getShortName(), 24))); + for (int loc = 0; loc < e.locations(); loc++) { + sb.append(e.getLocationAbbr(loc)).append(' ') + .append(e.getArmor(loc)).append('/').append(e.getOArmor(loc)) + .append('(').append(e.getInternal(loc)).append(')') + .append(" "); + } + sb.append('\n'); + } + return sb.toString(); + } + + private static String ownerName(Game game, Entity e) { + Player p = game.getPlayer(e.getOwnerId()); + return p == null ? "?" : p.getName(); + } + + private static String truncate(String s, int n) { + if (s == null) { + return ""; + } + return s.length() <= n ? s : s.substring(0, n - 1) + "~"; + } +} diff --git a/harness/src/spike/WatchGame.java b/harness/src/spike/WatchGame.java new file mode 100644 index 0000000..6233916 --- /dev/null +++ b/harness/src/spike/WatchGame.java @@ -0,0 +1,185 @@ +package spike; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import megamek.client.Client; +import megamek.client.bot.princess.Princess; +import megamek.common.Player; +import megamek.common.compute.Compute; +import megamek.common.enums.GamePhase; +import megamek.common.event.GameListenerAdapter; +import megamek.common.event.GamePhaseChangeEvent; +import megamek.common.event.GameReportEvent; +import megamek.common.game.Game; +import megamek.common.game.IGame; +import megamek.common.loaders.MekSummaryCache; +import megamek.common.scenario.Scenario; +import megamek.common.scenario.ScenarioLoader; +import megamek.server.Server; +import megamek.server.totalWarfare.TWGameManager; + +/** + * Runs a bot-vs-bot scenario with an extra observer client attached, dumping full + * text state on every phase change. + * + *

Purpose is twofold. It gives a human-or-agent-readable view of a live match without + * a GUI, and - more importantly - it exercises the exact seam the real product needs: an + * ordinary {@link Client} subscribed via {@link megamek.common.event.GameListener}, + * projecting engine state into a transport-friendly form. Princess proves upstream keeps + * that seam working; this proves it is rich enough to render from. + * + *

Usage: {@code WatchGame [scenarioPath] [outputDir]} + */ +public final class WatchGame { + + private static final String LOCALHOST = "127.0.0.1"; + private static final String OBSERVER_NAME = "Observer"; + + public static void main(String[] args) throws Exception { + String scenarioPath = args.length > 0 + ? args[0] + : "data/scenarios/TrainingScenarios/1-FirstRun.mms"; + Path outDir = Path.of(args.length > 1 ? args[1] : "watch-out"); + int maxRounds = args.length > 2 ? Integer.parseInt(args[2]) : 12; + Files.createDirectories(outDir); + + Compute.d6(); + MekSummaryCache cache = MekSummaryCache.getInstance(); + while (!cache.isInitialized()) { + Thread.sleep(250L); + } + + ScenarioLoader loader = new ScenarioLoader(new File(scenarioPath)); + Scenario scenario = loader.load(); + IGame game = scenario.createGame(); + + TWGameManager gameManager = new TWGameManager(); + int port = 8900; + Server server = new Server("", port, gameManager); + server.setGame(game); + scenario.applyDamage(gameManager); + gameManager.calculatePlayerInitialCounts(); + + List bots = new ArrayList<>(); + for (Player player : game.getPlayersList()) { + Princess bot = new Princess(player.getName(), LOCALHOST, port); + bot.startPrecognition(); + bot.connect(); + bots.add(bot); + } + + // The observer joins as an extra player and is then flagged as an observer + // server-side, so it never owns a turn and never stalls the game. + Client observer = new Client(OBSERVER_NAME, LOCALHOST, port); + AtomicInteger dumpCount = new AtomicInteger(); + StringBuilder reportLog = new StringBuilder(); + + observer.getGame().addGameListener(new GameListenerAdapter() { + @Override + public void gamePhaseChange(GamePhaseChangeEvent e) { + dump(observer, outDir, dumpCount.incrementAndGet(), reportLog); + } + + @Override + public void gameReport(GameReportEvent e) { + if (e.getReport() != null && !e.getReport().isBlank()) { + reportLog.append(e.getReport()).append('\n'); + } + } + }); + observer.connect(); + + waitForConnected(bots, observer); + + for (Player p : server.getGame().getPlayersList()) { + if (OBSERVER_NAME.equals(p.getName())) { + p.setObserver(true); + } + } + + for (Princess bot : bots) { + long deadline = System.currentTimeMillis() + 30_000L; + while (System.currentTimeMillis() < deadline + && (bot.getLocalPlayer() == null + || bot.getGame().getEntitiesOwnedBy(bot.getLocalPlayer()) == 0)) { + Thread.sleep(100L); + } + } + + for (Princess bot : bots) { + bot.sendDone(true); + } + observer.sendDone(true); + + long deadline = System.currentTimeMillis() + 900_000L; + while (System.currentTimeMillis() < deadline) { + Game g = observer.getGame(); + if (g.getPhase() == GamePhase.VICTORY || g.getCurrentRound() >= maxRounds) { + break; + } + Thread.sleep(1000L); + } + + dump(observer, outDir, dumpCount.incrementAndGet(), reportLog); + System.out.printf("wrote %d state dumps to %s%n", dumpCount.get(), outDir.toAbsolutePath()); + + server.die(); + System.exit(0); + } + + private static void waitForConnected(List bots, Client observer) throws InterruptedException { + long deadline = System.currentTimeMillis() + 60_000L; + while (System.currentTimeMillis() < deadline) { + boolean all = observer.isConnected(); + for (Princess bot : bots) { + all &= bot.isConnected(); + } + if (all) { + return; + } + Thread.sleep(100L); + } + } + + private static void dump(Client observer, Path outDir, int seq, StringBuilder reportLog) { + try { + Game g = observer.getGame(); + String body = TextRenderer.render(g, observer.getLocalPlayer()); + + StringBuilder sb = new StringBuilder(body); + sb.append("\nLAST PHASE REPORT\n"); + String phaseReport = observer.phaseReport; + sb.append(phaseReport == null || phaseReport.isBlank() + ? " (none)\n" + : indent(stripHtml(phaseReport))); + + String name = String.format("%03d-r%02d-%s.txt", seq, g.getCurrentRound(), g.getPhase()); + Files.writeString(outDir.resolve(name), sb.toString(), StandardCharsets.UTF_8); + System.out.printf(" dump %s (round %d, %s)%n", name, g.getCurrentRound(), g.getPhase()); + } catch (Exception e) { + System.err.println("dump failed: " + e.getMessage()); + } + } + + /** Reports arrive with HTML markup intended for the Swing report pane. */ + private static String stripHtml(String s) { + return s.replaceAll("", "\n") + .replaceAll("<[^>]+>", "") + .replaceAll("\n{3,}", "\n\n") + .trim(); + } + + private static String indent(String s) { + StringBuilder sb = new StringBuilder(); + for (String line : s.split("\n")) { + sb.append(" ").append(line).append('\n'); + } + return sb.toString(); + } +} diff --git a/patches/build-patched-jar.sh b/patches/build-patched-jar.sh new file mode 100755 index 0000000..3c2c8c6 --- /dev/null +++ b/patches/build-patched-jar.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Build a patched MegaMek.jar carrying the multi-tenancy fix. +# +# MegaMek.jar sets "Sealed: true", so a replacement megamek.server.* class cannot +# simply be placed earlier on the classpath - the JVM refuses to load a sealed +# package from a second codebase. Injecting the recompiled classes back into a +# copy of the jar keeps the seal intact. +# +# The pristine release tree is never modified; output is MegaMek-patched.jar. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MM_HOME="${MM_HOME:-$ROOT/MegaMek-0.51.00}" +JAVA_HOME="${JAVA_HOME:-$ROOT/jdk-21.0.12+8}" + +CP="$MM_HOME/MegaMek.jar" +for jar in "$MM_HOME"/lib/*.jar; do CP="$CP:$jar"; done + +OUT="$ROOT/patches/out" +rm -rf "$OUT" +mkdir -p "$OUT" + +echo "compiling patched sources..." +"$JAVA_HOME/bin/javac" \ + -nowarn \ + -classpath "$CP" \ + -d "$OUT" \ + "$ROOT"/patches/src/megamek/server/Server.java \ + "$ROOT"/patches/src/megamek/server/AbstractGameManager.java + +PATCHED_JAR="$ROOT/MegaMek-patched.jar" +cp "$MM_HOME/MegaMek.jar" "$PATCHED_JAR" + +echo "injecting $(find "$OUT" -name '*.class' | wc -l) recompiled classes..." +"$JAVA_HOME/bin/jar" --update --file "$PATCHED_JAR" -C "$OUT" megamek + +echo "built -> $PATCHED_JAR" diff --git a/patches/src/megamek/server/AbstractGameManager.java b/patches/src/megamek/server/AbstractGameManager.java new file mode 100644 index 0000000..f7ec30d --- /dev/null +++ b/patches/src/megamek/server/AbstractGameManager.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2024-2025 The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ + +package megamek.server; + +import megamek.common.Player; +import megamek.common.enums.GamePhase; +import megamek.common.game.IGame; +import megamek.common.net.enums.PacketCommand; +import megamek.common.net.packets.InvalidPacketDataException; +import megamek.common.net.packets.Packet; +import megamek.common.options.OptionsConstants; +import megamek.common.preference.PreferenceManager; +import megamek.common.util.StringUtil; +import megamek.logging.MMLogger; +import megamek.server.trigger.TriggerSituation; + +public abstract class AbstractGameManager implements IGameManager { + private static final MMLogger logger = MMLogger.create(AbstractGameManager.class); + + /** + * MULTI-TENANCY PATCH: the Server that owns this manager. + * + *

Previously every method below reached for {@link Server#getServerInstance()}, + * a static pointing at the most recently constructed Server. With one game per JVM + * that is always the right answer. With two, the older game silently starts + * transmitting to the newer game's connections and both wedge. + * + *

Falls back to the static when unset so any construction path that does not go + * through the Server constructor behaves exactly as before. + */ + private Server owningServer; + + void setOwningServer(Server server) { + this.owningServer = server; + } + + protected Server server() { + return owningServer != null ? owningServer : Server.getServerInstance(); + } + + protected final GameManagerPacketHelper packetHelper = new GameManagerPacketHelper(this); + protected final GameManagerSaveHelper saveHandler = new GameManagerSaveHelper(this); + protected final AutosaveService autoSaveService = new AutosaveService(this); + protected final GameManagerScriptedEventHelper scriptedEventHelper = new GameManagerScriptedEventHelper(this); + + /** + * Sends the given packet to all connections (all connected Clients = players). + * + * @see Server#send(Packet) + */ + @Override + public void send(Packet packet) { + server().send(packet); + } + + /** + * Sends the given packet to the given connection (= player ID). + * + * @see Server#send(int, Packet) + */ + @Override + public void send(int connId, Packet p) { + server().send(connId, p); + } + + @Override + public void handlePacket(int connId, Packet packet) { + if (packet.command() == PacketCommand.PLAYER_READY) { + try { + receivePlayerDone(packet, connId); + if (getGame().getPlayer(connId) != null) { + send(packetHelper.createPlayerDonePacket(connId)); + checkReady(); + } + } catch (InvalidPacketDataException e) { + logger.error("Invalid packet data:", e); + } + } + } + + /** + * Ends this phase and moves on to the next. + */ + protected abstract void endCurrentPhase(); + + /** + * Do anything we need to work through the current phase, such as give a turn to the first player to play. + */ + protected abstract void executeCurrentPhase(); + + /** + * Prepares for the game's current phase. This typically involves resetting the states of units in the game and + * making sure the clients have the information they need for the new phase. + */ + protected abstract void prepareForCurrentPhase(); + + /** + * Switches to the given new Phase and preforms preparation, checks if it should be skipped and executes it. + */ + public final void changePhase(GamePhase newPhase) { + if (getGame().getPhase().isExchange() || getGame().getPhase().isStartingScenario()) { + scriptedEventHelper.processScriptedEvents(TriggerSituation.GAME_START); + } + if (newPhase.isInitiative()) { + scriptedEventHelper.processScriptedEvents(TriggerSituation.ROUND_START); + } + if (getGame().getPhase().isEnd()) { + // the endreport phase cannot be used here as it may be skipped + scriptedEventHelper.processScriptedEvents(TriggerSituation.ROUND_END); + } + scriptedEventHelper.processScriptedEvents(TriggerSituation.PHASE_END); + + getGame().setLastPhase(getGame().getPhase()); + getGame().setPhase(newPhase); + + scriptedEventHelper.processScriptedEvents(TriggerSituation.PHASE_START); + if (getGame().getPhase().isVictory()) { + scriptedEventHelper.processScriptedEvents(TriggerSituation.GAME_END); + } + prepareForCurrentPhase(); + + if (getGame().shouldSkipCurrentPhase()) { + endCurrentPhase(); + } else { + // tell the players about the new phase + sendPhaseChange(); + executeCurrentPhase(); + } + } + + protected void sendPhaseChange() { + send(packetHelper.createPhaseChangePacket()); + } + + /** + * Called when a player declares that they are "done". By default, this method advances to the next phase, if + *
+ * - all non-ghost, non-observer players are done, + *
+ * - the present phase does not use turns (e.g. if it's a report phase), and + *
+ * - we are not in an empty lobby (= no units at all). + *
+ * In other circumstances, ending the current phase is triggered elsewhere. Note that specifically, ghost players + * are not checked for their status here so the game can advance through non-turn (report) phases even with ghost + * players. + */ + protected void checkReady() { + for (Player player : getGame().getPlayersList()) { + if (!player.isGhost() && !player.isObserver() && !player.isDone()) { + return; + } + } + + if (!getGame().getPhase().usesTurns() && !isEmptyLobby()) { + endCurrentPhase(); + } + } + + /** + * Sends out the player ready stats for all players to all connections + */ + protected void transmitAllPlayerDones() { + getGame().getPlayersList().forEach(player -> send(packetHelper.createPlayerDonePacket(player.getId()))); + } + + /** + * @return True when the game is in the lobby phase and is empty (no units present). + */ + protected boolean isEmptyLobby() { + return getGame().getPhase().isLounge() && getGame().getInGameObjects().isEmpty(); + } + + /** + * Sets a player's ready status as received from the Client. This method does not perform any follow-up actions. + */ + private void receivePlayerDone(Packet packet, int connIndex) throws InvalidPacketDataException { + boolean ready = packet.getBooleanValue(0); + Player player = getGame().getPlayer(connIndex); + if (null != player) { + player.setDone(ready); + } else { + logger.error("Tried to set done status of non-existent player!"); + } + } + + /** + * Sends out the player object to all players. Private info of the given player is redacted before being sent to + * other players. + * + * @param player The player whose information is to be shared + * + * @see #transmitAllPlayerUpdates() + */ + protected void transmitPlayerUpdate(Player player) { + server().transmitPlayerUpdate(player); + } + + /** + * Shares all player objects with all players. Private info is redacted before being sent to other players. + * + * @see #transmitPlayerUpdate(Player) + */ + public void transmitAllPlayerUpdates() { + getGame().getPlayersList().forEach(this::transmitPlayerUpdate); + } + + /** + * Performs an automatic save (does not check the autosave settings - the autosave will simply be done). Depending + * on the settings, the "autosave" filename is appended with a timestamp and/or a chat message is sent announcing + * the autosave. + */ + public void autoSave() { + String fileName = "autosave"; + + if (PreferenceManager.getClientPreferences().stampFilenames()) { + fileName = StringUtil.addDateTimeStamp(fileName); + } + + saveGame(fileName, getGame().getOptions().booleanOption(OptionsConstants.BASE_AUTOSAVE_MSG)); + } + + @Override + public void saveGame(String fileName, boolean sendChat) { + saveHandler.saveGame(fileName, sendChat); + } + + @Override + public void sendSaveGame(int connId, String sFile, String sLocalPath) { + saveHandler.sendSaveGame(connId, sFile, sLocalPath); + } + + public void sendChat(String origin, String message) { + server().sendChat(origin, message); + } + + public void sendChat(int connId, String origin, String message) { + server().sendChat(connId, origin, message); + } + + public void sendServerChat(String message) { + server().sendServerChat(message); + } + + public void sendServerChat(int connId, String message) { + server().sendServerChat(connId, message); + } + + /** + * Sends the current list of player turns as stored in the game's turn list to the Clients. + * + * @see IGame#getTurnsList() + */ + public void sendCurrentTurns() { + send(packetHelper.createTurnListPacket()); + } + + /** + * Increment's the server's game round and send it to all the clients + */ + public void incrementAndSendGameRound() { + getGame().incrementCurrentRound(); + send(packetHelper.createCurrentRoundNumberPacket()); + } + + public GameManagerPacketHelper getPacketHelper() { + return packetHelper; + } + + public AutosaveService getAutoSaveService() { + return autoSaveService; + } + + /** + * Sends out a notification message indicating that a ghost player's turn may be skipped with the /skip command. + * + * @param ghost the Player who is ghosted. This value must not be null. + */ + protected void sendGhostSkipMessage(Player ghost) { + String message = String.format( + "Player '%s' is disconnected. You may skip their current turn with the /skip command.", + ghost.getName()); + sendServerChat(message); + } +} diff --git a/patches/src/megamek/server/Server.java b/patches/src/megamek/server/Server.java new file mode 100644 index 0000000..c89a3d8 --- /dev/null +++ b/patches/src/megamek/server/Server.java @@ -0,0 +1,1556 @@ +/* + * Copyright (c) 2000-2005 - Ben Mazur (bmazur@sev.org) + * Copyright (c) 2013 - Edward Cullen (eddy@obsessedcomputers.co.uk) + * Copyright (C) 2002-2025 The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License (GPL), + * version 3 or (at your option) any later version, + * as published by the Free Software Foundation. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * A copy of the GPL should have been included with this project; + * if not, see . + * + * NOTICE: The MegaMek organization is a non-profit group of volunteers + * creating free software for the BattleTech community. + * + * MechWarrior, BattleMech, `Mech and AeroTech are registered trademarks + * of The Topps Company, Inc. All Rights Reserved. + * + * Catalyst Game Labs and the Catalyst Game Labs logo are trademarks of + * InMediaRes Productions, LLC. + * + * MechWarrior Copyright Microsoft Corporation. MegaMek was created under + * Microsoft's "Game Content Usage Rules" + * and it is not endorsed by or + * affiliated with Microsoft. + */ +package megamek.server; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.zip.GZIPInputStream; + +import com.thoughtworks.xstream.XStream; +import megamek.MMConstants; +import megamek.SuiteConstants; +import megamek.Version; +import megamek.client.ui.util.PlayerColour; +import megamek.codeUtilities.StringUtility; +import megamek.common.Player; +import megamek.common.annotations.Nullable; +import megamek.common.board.Board; +import megamek.common.commandLine.AbstractCommandLineParser.ParseException; +import megamek.common.game.Game; +import megamek.common.game.IGame; +import megamek.common.icons.Camouflage; +import megamek.common.loaders.MapSettings; +import megamek.common.net.connections.AbstractConnection; +import megamek.common.net.enums.PacketCommand; +import megamek.common.net.events.DisconnectedEvent; +import megamek.common.net.events.PacketReceivedEvent; +import megamek.common.net.factories.ConnectionFactory; +import megamek.common.net.listeners.ConnectionListener; +import megamek.common.net.packets.InvalidPacketDataException; +import megamek.common.net.packets.Packet; +import megamek.common.options.OptionsConstants; +import megamek.common.preference.PreferenceManager; +import megamek.common.rolls.Roll; +import megamek.common.util.EmailService; +import megamek.common.util.SerializationHelper; +import megamek.logging.MMLogger; +import megamek.server.commands.ServerCommand; + +/** + * @author Ben Mazur + */ +public class Server implements Runnable { + // server setup + private final String password; + + private final IGameManager gameManager; + + private final String metaServerUrl; + + private final ServerSocket serverSocket; + + private final String messageOfTheDay; + + private final EmailService mailer; + + private static final MMLogger LOGGER = MMLogger.create(Server.class); + + private final ReentrantLock GAME_LOCK = new ReentrantLock(); + + public static class ReceivedPacket { + private int connectionId; + private Packet packet; + + public ReceivedPacket(int cid, Packet p) { + setPacket(p); + setConnectionId(cid); + } + + public int getConnectionId() { + return connectionId; + } + + public void setConnectionId(int connectionId) { + this.connectionId = connectionId; + } + + public Packet getPacket() { + return packet; + } + + public void setPacket(Packet packet) { + this.packet = packet; + } + } + + private class PacketPump implements Runnable { + boolean shouldStop; + + PacketPump() { + shouldStop = false; + } + + void signalEnd() { + shouldStop = true; + } + + @Override + public void run() { + while (!shouldStop) { + while (!packetQueue.isEmpty()) { + ReceivedPacket rp = packetQueue.poll(); + synchronized (serverLock) { + handle(rp.getConnectionId(), rp.getPacket()); + } + } + + try { + synchronized (packetQueue) { + packetQueue.wait(); + } + } catch (InterruptedException ignored) { + // If we are interrupted, just keep going, generally this happens after we are signalled to stop. + } + } + } + } + + // commands + private final Map commandsHash = new ConcurrentHashMap<>(); + + // game info + private final List connections = new CopyOnWriteArrayList<>(); + + private final Map connectionHandlers = new ConcurrentHashMap<>(); + + private final ConcurrentLinkedQueue packetQueue = new ConcurrentLinkedQueue<>(); + + private final boolean dedicated; + + private final List connectionsPending = new CopyOnWriteArrayList<>(); + + private final Map connectionIds = new ConcurrentHashMap<>(); + + private int connectionCounter; + + // listens for and connects players + private Thread connector; + + private final PacketPump packetPump; + private Thread packetPumpThread; + + private final Timer watchdogTimer = new Timer("Watchdog Timer"); + + private static Server serverInstance = null; + + private String serverAccessKey = null; + + private Timer serverBrowserUpdateTimer = null; + + /** + * Used to ensure only one thread at a time is accessing this particular instance of the server. + */ + private final Object serverLock = new Object(); + + public static final String ORIGIN = "***Server"; + + // Easter eggs. Happy April Fool's Day!! + private static final String DUNE_CALL = "They tried and failed?"; + + private static final String DUNE_RESPONSE = "They tried and died!"; + + private static final String STAR_WARS_CALL = "I'd just as soon kiss a Wookiee."; + + private static final String STAR_WARS_RESPONSE = "I can arrange that!"; + + private static final String INVADER_ZIM_CALL = "What does the G stand for?"; + + private static final String INVADER_ZIM_RESPONSE = "I don't know."; + + private static final String WARGAMES_CALL = "Shall we play a game?"; + + private static final String WARGAMES_RESPONSE = "Let's play global thermonuclear war."; + + public static final int SERVER_CONN = Integer.MIN_VALUE; + + private final ConnectionListener connectionListener = new ConnectionListener() { + + private boolean isPaused = false; + private final List pausedWaitingList = new ArrayList<>(); + + /** + * Called when it is sensed that a connection has terminated. + */ + @Override + public void disconnected(DisconnectedEvent e) { + AbstractConnection conn = e.getConnection(); + + // write something in the log + LOGGER.info("s: connect {} disconnected.", conn.getId()); + + connections.remove(conn); + synchronized (serverLock) { + connectionsPending.remove(conn); + connectionIds.remove(conn.getId()); + ConnectionHandler ch = connectionHandlers.get(conn.getId()); + if (ch != null) { + ch.signalStop(); + connectionHandlers.remove(conn.getId()); + } + } + + // if there's a player for this connection, remove it too + Player player = getPlayer(conn.getId()); + if (null != player) { + Server.this.disconnected(player); + } + } + + @Override + public void packetReceived(PacketReceivedEvent e) { + ReceivedPacket rp = new ReceivedPacket(e.getConnection().getId(), e.getPacket()); + switch (e.getPacket().command()) { + case CLIENT_FEEDBACK_REQUEST: + // Handled CFR packets specially + gameManager.handleCfrPacket(rp); + break; + case CLOSE_CONNECTION: + case CLIENT_NAME: + case CLIENT_VERSIONS: + case CHAT: + // Some packets should be handled immediately + handle(rp.getConnectionId(), rp.getPacket()); + break; + case PAUSE: + if (!isPaused) { + LOGGER.info("Pause packet received - pausing packet handling"); + sendServerChat("Game is paused."); + } + isPaused = true; + break; + case UNPAUSE: + if (isPaused) { + LOGGER.info("Unpause packet received - resuming packet handling"); + sendServerChat("Game is resumed."); + } + isPaused = false; + synchronized (packetQueue) { + packetQueue.addAll(pausedWaitingList); + packetQueue.notifyAll(); + } + pausedWaitingList.clear(); + break; + default: + if (isPaused) { + pausedWaitingList.add(rp); + } else { + synchronized (packetQueue) { + packetQueue.add(rp); + packetQueue.notifyAll(); + } + } + break; + } + } + }; + + /** + * @param serverAddress IP or Domain Name Of Server + * + * @return valid hostName + * + * @throws ParseException for null or empty serverAddress + */ + public static String validateServerAddress(String serverAddress) throws ParseException { + if ((serverAddress == null) || serverAddress.isBlank()) { + String message = "serverAddress must not be null or empty"; + LOGGER.error(message); + throw new ParseException(message); + } else { + return serverAddress.trim(); + } + } + + /** + * @param playerName throw ParseException if null or empty + * + * @return valid playerName + */ + public static String validatePlayerName(String playerName) throws ParseException { + if (playerName == null) { + String message = "playerName must not be null"; + LOGGER.error(message); + throw new ParseException(message); + } else if (playerName.isBlank()) { + String message = "playerName must not be empty string"; + LOGGER.error(message); + throw new ParseException(message); + } else { + return playerName.trim(); + } + } + + /** + * @param password Password to set for server. + * + * @return valid password or null if no password or password is blank string + */ + public static @Nullable String validatePassword(@Nullable String password) { + return StringUtility.isNullOrBlank(password) ? null : password.trim(); + } + + /** + * Checks a String against the server password + * + * @param password The password provided by the user. + * + * @return true if the user-supplied data matches the server password or no password is set. + */ + public boolean passwordMatches(Object password) { + return StringUtility.isNullOrBlank(this.password) || this.password.equals(password); + } + + /** + * @param port if outside the established range of #{@see MMConstants#MIN_PORT} and #{@see MMConstants#MAX_PORT} + * return #{@see MMConstants#DEFAULT_PORT} or the passed in port. + * + * @return valid port number + */ + public static int validatePort(int port) { + if (port < MMConstants.MIN_PORT || port > MMConstants.MAX_PORT) { + return MMConstants.DEFAULT_PORT; + } + + return port; + } + + public Server(@Nullable String password, int port, IGameManager gameManager) throws IOException { + this(password, port, gameManager, false, "", null, false); + } + + public Server(@Nullable String password, int port, IGameManager gameManager, boolean registerWithServerBrowser, + @Nullable String metaServerUrl) throws IOException { + this(password, port, gameManager, registerWithServerBrowser, metaServerUrl, null, false); + } + + /** + * Construct a new GameHost and begin listening for incoming clients. + * + * @param password the String that is set as a password + * @param port the int value that specifies the port that is used + * @param gameManager the {@link IGameManager} instance for this server instance. + * @param registerWithServerBrowser a boolean indicating whether we should register with the master + * server browser on ... + * @param mailer an email service instance to use for sending round reports. + * @param dedicated set to true if this server is started from a GUI-less context + */ + public Server(@Nullable String password, int port, IGameManager gameManager, boolean registerWithServerBrowser, + @Nullable String metaServerUrl, @Nullable EmailService mailer, boolean dedicated) throws IOException { + this.metaServerUrl = StringUtility.isNullOrBlank(metaServerUrl) ? null : metaServerUrl; + this.password = StringUtility.isNullOrBlank(password) ? null : password; + this.gameManager = gameManager; + this.mailer = mailer; + this.dedicated = dedicated; + + // initialize server socket + serverSocket = new ServerSocket(port); + + messageOfTheDay = createMOTD(); + + // display server start text + LOGGER.info("s: starting a new server..."); + + try { + String host = InetAddress.getLocalHost().getHostName(); + LOGGER.info("s: hostname = '{}' port = {}", host, serverSocket.getLocalPort()); + + InetAddress[] addresses = InetAddress.getAllByName(host); + for (InetAddress address : addresses) { + LOGGER.info("s: hosting on address = {}", address.getHostAddress()); + } + } catch (Exception ignored) { + } + + if (password != null) { + LOGGER.info("s: password is set (redacted)"); + } + + for (ServerCommand command : gameManager.getCommandList(this)) { + registerCommand(command); + } + + packetPump = new PacketPump(); + packetPumpThread = new Thread(packetPump, "Packet Pump"); + packetPumpThread.start(); + + if (registerWithServerBrowser) { + if (!StringUtility.isNullOrBlank(metaServerUrl)) { + final TimerTask register = new TimerTask() { + @Override + public void run() { + registerWithServerBrowser(true, Server.getServerInstance().metaServerUrl); + } + }; + serverBrowserUpdateTimer = new Timer("Server Browser Register Timer", true); + serverBrowserUpdateTimer.schedule(register, 1, 40000); + } else { + LOGGER.error("Invalid URL for server browser {}", this.metaServerUrl); + } + } + + // Fully initialized, now accept connections + connector = new Thread(this, "Connection Listener"); + connector.start(); + + serverInstance = this; + + // MULTI-TENANCY PATCH: give the manager a direct reference to its own server. + // The static serverInstance still exists (nothing else is disturbed), but a + // manager that knows its owner no longer has to guess via the static, which + // always points at the most recently constructed Server. + if (gameManager instanceof AbstractGameManager abstractGameManager) { + abstractGameManager.setOwningServer(this); + } + } + + public IGameManager getGameManager() { + return gameManager; + } + + /** + * Sets the game for this server. Restores any transient fields, and sets all players as ghosts. This should only be + * called during server initialization before any players have connected. + */ + public void setGame(IGame g) { + gameManager.setGame(g); + } + + public IGame getGame() { + return gameManager.getGame(); + } + + public EmailService getEmailService() { + return mailer; + } + + /** + * Make a default message o' the day containing the version string, and if it was found, the build timestamp + */ + private String createMOTD() { + return "Welcome to MegaMek. Server is running version " + SuiteConstants.VERSION; + } + + /** + * @return true if the server has a password + */ + public boolean isPassworded() { + return password != null; + } + + /** + * @return true if the password matches + */ + public boolean isPassword(Object guess) { + return password.equals(guess); + } + + /** + * Registers a new command in the server command table + */ + private void registerCommand(ServerCommand command) { + commandsHash.put(command.getName(), command); + } + + /** + * Returns the command associated with the specified name + */ + public ServerCommand getCommand(String name) { + return commandsHash.get(name); + } + + /** + * @return true run from a GUI-less context + */ + public boolean getDedicated() { + return dedicated; + } + + /** + * Shuts down the server. + */ + public void die() { + watchdogTimer.cancel(); + + // kill thread accepting new connections + connector = null; + packetPump.signalEnd(); + packetPumpThread.interrupt(); + packetPumpThread = null; + + // close socket + try { + serverSocket.close(); + } catch (Exception ignored) { + } + + // kill pending connections + connectionsPending.forEach(AbstractConnection::close); + connectionsPending.clear(); + + // Send "kill" commands to all connections + // This WILL handle the connection end on both sides + send(new Packet(PacketCommand.CLOSE_CONNECTION)); + connectionIds.clear(); + + // Shutdown Email + if (mailer != null) { + mailer.shutdown(); + } + + // Unregister Server Browser Setup + if (serverBrowserUpdateTimer != null) { + serverBrowserUpdateTimer.cancel(); + } + + if ((metaServerUrl != null) && (!metaServerUrl.isBlank())) { + registerWithServerBrowser(false, metaServerUrl); + } + } + + /** + * Returns an enumeration of all the command names + */ + public Collection getAllCommandNames() { + return commandsHash.keySet(); + } + + /** + * Sent when a client attempts to connect. + */ + void clientVersionCheck(int cn) { + sendToPending(cn, new Packet(PacketCommand.SERVER_VERSION_CHECK)); + } + + /** + * Returns a free connection id. + */ + public int getFreeConnectionId() { + while ((getPendingConnection(connectionCounter) != null) || + (getConnection(connectionCounter) != null) || + (getPlayer(connectionCounter) != null)) { + connectionCounter++; + } + return connectionCounter; + } + + /** + * Returns a free entity id. Perhaps this should be in Game instead. + */ + @Deprecated(since = "0.51.0", forRemoval = true) + public int getFreeEntityId() { + return getGame().getNextEntityId(); + } + + /** + * Allow the player to set whatever parameters he is able to + */ + private void receivePlayerInfo(Packet packet, int connId) throws InvalidPacketDataException { + Player player = packet.getPlayer(0); + + if (player == null) { + return; + } + + Player gamePlayer = getGame().getPlayer(connId); + if (null != gamePlayer) { + gamePlayer.setColour(player.getColour()); + gamePlayer.setStartingPos(player.getStartingPos()); + gamePlayer.setStartWidth(player.getStartWidth()); + gamePlayer.setStartOffset(player.getStartOffset()); + gamePlayer.setStartingAnyNWx(player.getStartingAnyNWx()); + gamePlayer.setStartingAnyNWy(player.getStartingAnyNWy()); + gamePlayer.setStartingAnySEx(player.getStartingAnySEx()); + gamePlayer.setStartingAnySEy(player.getStartingAnySEy()); + gamePlayer.setTeam(player.getTeam()); + gamePlayer.setCamouflage(player.getCamouflage().clone()); + gamePlayer.setNbrMFConventional(player.getNbrMFConventional()); + gamePlayer.setNbrMFCommand(player.getNbrMFCommand()); + gamePlayer.setNbrMFVibra(player.getNbrMFVibra()); + gamePlayer.setNbrMFActive(player.getNbrMFActive()); + gamePlayer.setNbrMFInferno(player.getNbrMFInferno()); + gamePlayer.setNbrMFEMP(player.getNbrMFEMP()); + if (gamePlayer.getConstantInitBonus() != player.getConstantInitBonus()) { + sendServerChat("Player " + + gamePlayer.getName() + + " changed their initiative bonus from " + + gamePlayer.getConstantInitBonus() + + " to " + + player.getConstantInitBonus() + + '.'); + } + gamePlayer.setConstantInitBonus(player.getConstantInitBonus()); + gamePlayer.setEmail(player.getEmail()); + gamePlayer.setGroundObjectsToPlace(new ArrayList<>(player.getGroundObjectsToPlace())); + } + } + + /** + * Correct a duplicate player name + * + * @param oldName the String old player name, that is a duplicate + * + * @return the String new player name + */ + private String correctDupeName(String oldName) { + for (Player player : getGame().getPlayersList()) { + if (player.getName().equals(oldName)) { + // We need to correct it. + String newName = oldName; + int dupNum; + try { + dupNum = Integer.parseInt(oldName.substring(oldName.lastIndexOf('.') + 1)); + dupNum++; + newName = oldName.substring(0, oldName.lastIndexOf('.')); + } catch (Exception e) { + // If this fails, we don't care much. Just assume it's the first time for this name. + dupNum = 2; + } + newName = newName.concat(".").concat(Integer.toString(dupNum)); + return correctDupeName(newName); + } + } + return oldName; + } + + private boolean receivePlayerVersion(Packet packet, int connId) throws InvalidPacketDataException { + final Version version = (Version) packet.getObject(0); + + if (!SuiteConstants.VERSION.is(version)) { + final String message = String.format("Client/Server Version Mismatch -- Client: %s, Server: %s", + version, + SuiteConstants.VERSION); + LOGGER.error(message); + + final Player player = getPlayer(connId); + sendServerChat(String.format("For %s, Server reports:%s%s", + ((player == null) ? "unknown player" : player.getName()), + System.lineSeparator(), + message)); + return false; + } + + return true; + } + + /** + * Receives a player name, sent from a pending connection, and connects that connection. + */ + private void receivePlayerName(Packet packet, int connId) throws InvalidPacketDataException { + final AbstractConnection conn = getPendingConnection(connId); + String name = packet.getStringValue(0); + boolean isBot = packet.getBooleanValue(1); + boolean returning = false; + String message; + + // this had better be from a pending connection + if (conn == null) { + LOGGER.warn("Got a client name from a non-pending connection"); + return; + } + + // check if they're connecting with the same name as a ghost player + for (Player player : getGame().getPlayersList()) { + if (player.getName().equals(name) && player.isGhost()) { + returning = true; + player.setGhost(false); + player.setBot(isBot); + // switch id + connId = player.getId(); + conn.setId(connId); + } + } + + if (!returning) { + // Check to avoid duplicate names... + name = correctDupeName(name); + sendToPending(connId, new Packet(PacketCommand.SERVER_CORRECT_NAME, name)); + } + + // right, switch the connection into the "active" bin + connectionsPending.remove(conn); + connections.add(conn); + connectionIds.put(conn.getId(), conn); + + // add and validate the player info + if (!returning) { + addNewPlayer(connId, name, isBot); + } + + // if it is not the lounge phase, this player becomes an observer + Player player = getPlayer(connId); + if (!getGame().getPhase().isLounge() && (null != player) && (getGame().getEntitiesOwnedBy(player) < 1)) { + player.setObserver(true); + } + + // send the player the messageOfTheDay + sendServerChat(connId, messageOfTheDay); + + // send info that the player has connected + transmitPlayerConnect(player); + + // tell them their local playerId + send(connId, new Packet(PacketCommand.LOCAL_PN, connId)); + + // send current game info + sendCurrentInfo(connId); + + final boolean showIPAddressesInChat = PreferenceManager.getClientPreferences().getShowIPAddressesInChat(); + + try { + InetAddress[] addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + for (InetAddress address : addresses) { + message = String.format("s: Machine IP %s", address.getHostAddress()); + LOGGER.info(message); + + if (showIPAddressesInChat) { + sendServerChat(connId, message); + } + } + } catch (Exception ignored) { + } + + message = String.format("s: Listening on port %d", serverSocket.getLocalPort()); + LOGGER.info(message); + + if (showIPAddressesInChat) { + // Send the port we're listening on. Only useful for the player on the server machine to check. + sendServerChat(connId, message); + } + + // Get the player *again*, because they may have disconnected. + player = getPlayer(connId); + if (null != player) { + String who = String.format("%s connected from %s", player.getName(), getClient(connId).getInetAddress()); + message = String.format("s: player #%d, %s", connId, who); + LOGGER.info(message); + + if (showIPAddressesInChat) { + sendServerChat(who); + } + } // Found the player + } + + /** + * Sends a player the info they need to look at the current phase. This is triggered when a player first connects to + * the server. + */ + public void sendCurrentInfo(int connId) { + transmitPlayerConnect(getClient(connId)); + gameManager.sendCurrentInfo(connId); + } + + /** + * Adds a new player to the game + */ + private Player addNewPlayer(int connId, String name, boolean isBot) { + int team = getTeam(isBot); + Player newPlayer = new Player(connId, name); + newPlayer.setBot(isBot); + PlayerColour colour = newPlayer.getColour(); + final PlayerColour[] colours = PlayerColour.values(); + + for (Player player : getGame().getPlayersList()) { + if (player.getId() == newPlayer.getId()) { + continue; + } + + if ((player.getColour() == colour) && (colours.length > (colour.ordinal() + 1))) { + colour = colours[colour.ordinal() + 1]; + } + } + + newPlayer.setColour(colour); + newPlayer.setCamouflage(new Camouflage(Camouflage.COLOUR_CAMOUFLAGE, colour.name())); + newPlayer.setTeam(Math.min(team, 5)); + getGame().addPlayer(connId, newPlayer); + validatePlayerInfo(connId); + return newPlayer; + } + + private int getTeam(boolean isBot) { + int team = Player.TEAM_UNASSIGNED; + + if (getGame().getPhase().isLounge()) { + team = Player.TEAM_NONE; + final var gOpts = getGame().getOptions(); + if (isBot || !gOpts.booleanOption(OptionsConstants.BASE_SET_DEFAULT_TEAM_1)) { + for (Player p : getGame().getPlayersList()) { + if (p.getTeam() > team) { + team = p.getTeam(); + } + } + team++; + } else { + team = 1; + } + + } + + return team; + } + + /** + * Validates the player info. + */ + public void validatePlayerInfo(int playerId) { + final Player player = getPlayer(playerId); + + if (player != null) { + // TODO : check for duplicate or reserved names + + // Colour Assignment + final PlayerColour[] playerColours = PlayerColour.values(); + boolean allUsed = true; + Set colourUtilization = new HashSet<>(); + for (Player otherPlayer : getGame().getPlayersList()) { + if (otherPlayer.getId() != playerId) { + colourUtilization.add(otherPlayer.getColour()); + } else { + allUsed = false; + } + } + + if (!allUsed && colourUtilization.contains(player.getColour())) { + for (PlayerColour colour : playerColours) { + if (!colourUtilization.contains(colour)) { + player.setColour(colour); + break; + } + } + } + } + } + + /** + * Called when it's been determined that an actual player disconnected. Notifies the other players and does the + * appropriate housekeeping. + */ + void disconnected(Player player) { + // Only do this if nobody is trying to save a game currently + GAME_LOCK.lock(); + try { + gameManager.disconnect(player); + } finally { + GAME_LOCK.unlock(); + } + } + + public void resetGame() { + gameManager.resetGame(); + } + + /** + * send a packet to the connection tells it load a locally saved game + * + * @param connId The int connection id to send to + * @param sFile The String filename to use + */ + public void sendLoadGame(int connId, String sFile) { + String sFinalFile = sFile; + if (!sFinalFile.endsWith(MMConstants.SAVE_FILE_EXT) && !sFinalFile.endsWith(MMConstants.SAVE_FILE_GZ_EXT)) { + sFinalFile = sFile + MMConstants.SAVE_FILE_EXT; + } + if (!sFinalFile.endsWith(".gz")) { + sFinalFile = sFinalFile + ".gz"; + } + send(connId, new Packet(PacketCommand.LOAD_SAVEGAME, sFinalFile)); + } + + /** + * load the game + * + * @param f The File to load + * + * @return A boolean value whether the loading was successful + */ + public boolean loadGame(File f) { + return loadGame(f, true); + } + + /** + * load the game + * + * @param f The File to load + * @param sendInfo Determines whether the connections should be updated with current info. This may be false if some + * reconnection remapping needs to be done first. + * + * @return A boolean value whether the loading was successful + */ + public boolean loadGame(File f, boolean sendInfo) { + String message = String.format("s: Loading saved game file '%s'", f.getAbsolutePath()); + LOGGER.info(message); + + Game newGame; + try (InputStream is = new FileInputStream(f)) { + InputStream gzi; + + if (f.getName().toLowerCase().endsWith(".gz")) { + gzi = new GZIPInputStream(is); + } else { + gzi = is; + } + + XStream xStream = SerializationHelper.getLoadSaveGameXStream(); + newGame = (Game) xStream.fromXML(gzi); + } catch (Exception e) { + message = String.format("Unable to load file: %s", f); + LOGGER.error(e, message); + return false; + } + + setGame(newGame); + + // Saves created before mapName tracking was added land here with Board.BOARD_NAME_UNNAMED. + // Reconstruct the name from the saved MapSettings so the Ruler title and other UI surfaces + // identify the map. Only fills in when the board has the placeholder; saves with an explicit + // name keep theirs. + backfillBoardNameFromMapSettings(newGame); + + if (!sendInfo) { + return true; + } + + // update all the clients with the new game info + for (AbstractConnection conn : connections) { + sendCurrentInfo(conn.getId()); + } + return true; + } + + /** + * If the deserialized game's board has the placeholder map name (saves predate the name fix), + * reconstructs a display name from the saved MapSettings' selected board file names. The actual + * board hex data is unchanged; only the displayable name is filled in. + */ + private static void backfillBoardNameFromMapSettings(Game game) { + Board board = game.getBoard(); + if (board == null || !Board.BOARD_NAME_UNNAMED.equals(board.getBoardName())) { + return; + } + MapSettings ms = game.getMapSettings(); + if (ms == null) { + return; + } + List selected = ms.getBoardsSelectedVector(); + if (selected == null || selected.isEmpty()) { + return; + } + LinkedHashSet names = new LinkedHashSet<>(); + for (String raw : selected) { + if (raw == null) { + continue; + } + String name = raw; + if (name.startsWith(Board.BOARD_REQUEST_ROTATION)) { + name = name.substring(Board.BOARD_REQUEST_ROTATION.length()); + } + // Skip placeholders that don't correspond to an on-disk file + if (MapSettings.BOARD_GENERATED.equals(name) + || MapSettings.BOARD_RANDOM.equals(name) + || name.startsWith(MapSettings.BOARD_SURPRISE)) { + continue; + } + if (!name.isBlank()) { + names.add(name); + } + } + if (names.isEmpty()) { + return; + } + String reconstructed = names.size() == 1 ? names.iterator().next() : String.join(" + ", names); + board.setMapName(reconstructed); + } + + public void saveGame(String fileName) { + GAME_LOCK.lock(); + try { + gameManager.saveGame(fileName); + } finally { + GAME_LOCK.unlock(); + } + } + + public void sendSaveGame(int connId, String fileName, String localPath) { + GAME_LOCK.lock(); + try { + gameManager.sendSaveGame(connId, fileName, localPath); + } finally { + GAME_LOCK.unlock(); + } + } + + /** + * When the load command is used, there is a list of already connected players which have assigned names and player + * id numbers with the id numbers matching the connection numbers. When a new game is loaded, this mapping may need + * to be updated. This method takes a map of player names to their current ids, and uses the list of players to + * figure out what the current ids should change to. + * + * @param nameToIdMap This maps a player name to the current connection ID + * @param idToNameMap This maps a current conn ID to a player name, and is just the inverse mapping from + * nameToIdMap + */ + public void remapConnIds(Map nameToIdMap, Map idToNameMap) { + // Keeps track of connections without Ids + List unassignedConnections = new ArrayList<>(); + + // Keep track of which ids are used + Set usedPlayerIds = new HashSet<>(); + Set currentPlayerNames = new HashSet<>(); + + for (Player p : getGame().getPlayersList()) { + currentPlayerNames.add(p.getName()); + } + + // Map the old connection ID to new value + Map connIdRemapping = new HashMap<>(); + for (Player p : getGame().getPlayersList()) { + // Check to see if this player was already connected + Integer oldId = nameToIdMap.get(p.getName()); + if ((oldId != null) && (oldId != p.getId())) { + connIdRemapping.put(oldId, p.getId()); + } + + // If the old and new Ids match, make sure we remove ghost status + if ((oldId != null) && (oldId == p.getId())) { + p.setGhost(false); + } + + // Check to see if this player's ID is taken + String oldName = idToNameMap.get(p.getId()); + if ((oldName != null) && !oldName.equals(p.getName())) { + // If this name doesn't belong to a current player, unassign it + if (!currentPlayerNames.contains(oldName)) { + unassignedConnections.add(connectionIds.get(p.getId())); + // Make sure we don't add this to unassigned connections twice + connectionIds.remove(p.getId()); + } + // If it does belong to a current player, it'll get handled + // when that player comes up + } + // Keep track of what Ids are used + usedPlayerIds.add(p.getId()); + } + + // Remap old connection Ids to new ones + for (Entry currentConnect : connIdRemapping.entrySet()) { + Integer newId = currentConnect.getValue(); + AbstractConnection conn = connectionIds.get(currentConnect.getKey()); + conn.setId(newId); + + // If this ID is used, make sure we reassign that connection + if (connectionIds.containsKey(newId)) { + unassignedConnections.add(connectionIds.get(newId)); + } + // Map the new ID + connectionIds.put(newId, conn); + + getGame().getPlayer(newId).setGhost(false); + send(newId, new Packet(PacketCommand.LOCAL_PN, newId)); + } + + // It's possible we have players not in the saved game, add 'em + for (AbstractConnection conn : unassignedConnections) { + int newId = 0; + + while (usedPlayerIds.contains(newId)) { + newId++; + } + + String name = idToNameMap.get(conn.getId()); + conn.setId(newId); + Player newPlayer = addNewPlayer(newId, name, false); + newPlayer.setObserver(true); + connectionIds.put(newId, conn); + send(newId, new Packet(PacketCommand.LOCAL_PN, newId)); + } + + // Ensure all clients are up-to-date on player info + transmitAllPlayerUpdates(); + } + + /** + * Shortcut to game.getPlayer(id) + */ + public Player getPlayer(int id) { + return getGame().getPlayer(id); + } + + /** + * a shorter name for getConnection() + */ + private AbstractConnection getClient(int connId) { + return getConnection(connId); + } + + /** + * Executes the process on each active connection. + * + * @param process The process to execute. + */ + public void forEachConnection(Consumer process) { + connections.forEach(process); + } + + /** + * Returns a connection, indexed by id + */ + public AbstractConnection getConnection(int connId) { + return connectionIds.get(connId); + } + + /** + * Returns a pending connection + */ + public @Nullable AbstractConnection getPendingConnection(int connId) { + for (AbstractConnection conn : connectionsPending) { + if (conn.getId() == connId) { + return conn; + } + } + return null; + } + + /** + * Sends out all player info to the specified connection + */ + private void transmitPlayerConnect(AbstractConnection connection) { + for (Player player : getGame().getPlayersList()) { + var connectionId = connection.getId(); + connection.send(createPlayerConnectPacket(player, player.getId() != connectionId)); + } + } + + /** + * Sends out player info to all connections + */ + private void transmitPlayerConnect(Player player) { + for (var connection : connections) { + var playerId = player.getId(); + connection.send(createPlayerConnectPacket(player, playerId != connection.getId())); + } + } + + /** + * Creates a packet informing that the player has connected + */ + private Packet createPlayerConnectPacket(Player player, boolean isPrivate) { + var playerId = player.getId(); + var destPlayer = player; + if (isPrivate) { + // Sending the player's data to another player's + // connection, need to redact any private data + destPlayer = player.copy(); + destPlayer.redactPrivateData(); + } + return new Packet(PacketCommand.PLAYER_ADD, playerId, destPlayer); + } + + /** + * Sends out player info updates for a player to all connections + */ + void transmitPlayerUpdate(Player player) { + for (var connection : connections) { + var playerId = player.getId(); + var destPlayer = player; + + if (playerId != connection.getId()) { + // Sending the player's data to another player's + // connection, need to redact any private data + destPlayer = player.copy(); + destPlayer.redactPrivateData(); + } + connection.send(new Packet(PacketCommand.PLAYER_UPDATE, playerId, destPlayer)); + } + } + + /** + * Sends out the player info updates for all players to all connections + */ + private void transmitAllPlayerUpdates() { + for (Player player : getGame().getPlayersList()) { + transmitPlayerUpdate(player); + } + } + + /** + * Player can request its own change of team + * + * @param teamID target team id + * @param player player requesting the change + */ + public void requestTeamChangeForPlayer(int teamID, Player player) { + gameManager.requestTeamChangeForPlayer(teamID, player); + } + + public void requestGameMaster(Player player) { + gameManager.requestGameMaster(player); + } + + public static String formatChatMessage(String origin, String message) { + return origin + ": " + message; + } + + /** + * Transmits a chat message to all players + */ + public void sendChat(int connId, String origin, String message) { + send(connId, new Packet(PacketCommand.CHAT, formatChatMessage(origin, message))); + } + + /** + * Transmits a chat message to all players + */ + public void sendChat(String origin, String message) { + send(new Packet(PacketCommand.CHAT, formatChatMessage(origin, message))); + } + + public void sendServerChat(int connId, String message) { + sendChat(connId, ORIGIN, message); + } + + public void sendServerChat(String message) { + sendChat(ORIGIN, message); + } + + /** + * Sends the given packet to all connections (all connected Clients = players). + */ + void send(Packet packet) { + connections.stream().filter(Objects::nonNull).forEach(connection -> connection.send(packet)); + } + + /** + * Sends the given packet to the given connection (= player ID) if it is not null. Does nothing otherwise. + */ + public void send(int connId, Packet packet) { + AbstractConnection connection = getClient(connId); + + if (connection != null) { + connection.send(packet); + } + } + + /** + * Send a packet to a pending connection + */ + private void sendToPending(int connId, Packet packet) { + AbstractConnection pendingConn = getPendingConnection(connId); + + if (pendingConn != null) { + pendingConn.send(packet); + } + // What should we do if we've lost this client? + // For now, nothing. + } + + /** + * Process an in-game command + */ + private void processCommand(int connId, String commandString) { + // all tokens are read as strings; if they're numbers, string-ize 'em. + // replaced the tokenizer with the split function. + String[] args = commandString.split("\\s+"); + + // figure out which command this is + String commandName = args[0].substring(1); + + // process it + ServerCommand command = getCommand(commandName); + if (command != null) { + command.run(connId, args); + } else { + sendServerChat(connId, "Command not recognized. Type /help for a list of commands."); + } + } + + /** + * Process a packet from a connection. + * + * @param connId - the int ID the connection that received the packet. + * @param packet - the Packet to be processed. + */ + protected void handle(int connId, Packet packet) { + Player player = getGame().getPlayer(connId); + + // Check player. Please note, the connection may be pending. + if ((null == player) && (null == getPendingConnection(connId))) { + String message = String.format("Server does not recognize player at connection %d", connId); + LOGGER.error(message); + return; + } + + if (packet == null) { + LOGGER.error("Got null packet"); + return; + } + + // act on it + try { + switch (packet.command()) { + case CLIENT_VERSIONS: + final boolean valid = receivePlayerVersion(packet, connId); + if (valid) { + sendToPending(connId, new Packet(PacketCommand.SERVER_GREETING)); + } else { + sendToPending(connId, new Packet(PacketCommand.ILLEGAL_CLIENT_VERSION, SuiteConstants.VERSION)); + getPendingConnection(connId).close(); + } + break; + case CLOSE_CONNECTION: + // We have a client going down! + AbstractConnection c = getConnection(connId); + if (c != null) { + c.close(); + } + break; + case CLIENT_NAME: + receivePlayerName(packet, connId); + break; + case PLAYER_UPDATE: + receivePlayerInfo(packet, connId); + validatePlayerInfo(connId); + transmitPlayerUpdate(getPlayer(connId)); + break; + case CHAT: + String chat = packet.getStringValue(0); + if (chat.startsWith("/")) { + processCommand(connId, chat); + } else if (packet.data().length > 1) { + connId = packet.getIntValue(1); + if (connId == Player.PLAYER_NONE) { + sendServerChat(chat); + } else { + sendServerChat(connId, chat); + } + } else { + if (player != null) { + sendChat(player.getName(), chat); + } + } + + // Easter eggs. Happy April Fool's Day!! + if (DUNE_CALL.equalsIgnoreCase(chat)) { + sendServerChat(DUNE_RESPONSE); + } else if (STAR_WARS_CALL.equalsIgnoreCase(chat)) { + sendServerChat(STAR_WARS_RESPONSE); + } else if (INVADER_ZIM_CALL.equalsIgnoreCase(chat)) { + sendServerChat(INVADER_ZIM_RESPONSE); + } else if (WARGAMES_CALL.equalsIgnoreCase(chat)) { + wargamesResponse(); + } + + break; + case LOAD_GAME: + try { + sendServerChat(getPlayer(connId).getName() + " loaded a new game."); + setGame((Game) packet.getObject(0)); + for (AbstractConnection conn : connections) { + sendCurrentInfo(conn.getId()); + } + } catch (Exception e) { + LOGGER.error(e, "Error loading save game sent from client"); + } + break; + default: + // We don't want to change the game state while other threads may be in the game + GAME_LOCK.lock(); + try { + gameManager.handlePacket(connId, packet); + } finally { + GAME_LOCK.unlock(); + } + } + } catch (InvalidPacketDataException e) { + LOGGER.error("Invalid packet data:", e); + } + } + + private void wargamesResponse() { + sendServerChat(WARGAMES_RESPONSE); + } + + /** + * Listen for incoming clients. + */ + @Override + public void run() { + String message; + Thread currentThread = Thread.currentThread(); + LOGGER.info("s: listening for clients..."); + while (connector == currentThread) { + try { + Socket s = serverSocket.accept(); + synchronized (serverLock) { + int id = getFreeConnectionId(); + message = String.format("s: accepting player connection #%d...", id); + LOGGER.info(message); + + AbstractConnection c = ConnectionFactory.getInstance().createServerConnection(s, id); + c.addConnectionListener(connectionListener); + c.open(); + connectionsPending.add(c); + ConnectionHandler ch = new ConnectionHandler(c); + Thread newConnThread = new Thread(ch, "Connection " + id); + newConnThread.start(); + connectionHandlers.put(id, ch); + + clientVersionCheck(id); + ConnectionWatchdog w = new ConnectionWatchdog(this, id); + watchdogTimer.schedule(w, 1000, 500); + } + } catch (Exception ignored) { + + } + } + } + + /** + * @return a String representing the hostname + */ + public String getHost() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (Exception ex) { + LOGGER.error(ex, "Get Host exception"); + return ""; + } + } + + /** + * @return the int this server is listening on + */ + public int getPort() { + return serverSocket.getLocalPort(); + } + + /** + * @return the current server instance. This may be null if a server has not been started + */ + public static @Nullable Server getServerInstance() { + return serverInstance; + } + + private void registerWithServerBrowser(boolean register, String urlString) { + try { + URL url = new URL(urlString); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + OutputStream os = conn.getOutputStream(); + DataOutputStream dos = new DataOutputStream(os); + StringBuilder content = new StringBuilder("port=" + + URLEncoder.encode(Integer.toString(serverSocket.getLocalPort()), + StandardCharsets.UTF_8)); + if (register) { + for (AbstractConnection abstractConnection : connections) { + content.append("&players[]=").append(getPlayer(abstractConnection.getId()).getName()); + } + + if (!getGame().getPhase().isLounge() && !getGame().getPhase().isUnknown()) { + content.append("&close=yes"); + } + + content.append("&version=").append(SuiteConstants.VERSION); + + if (isPassworded()) { + content.append("&pw=yes"); + } + } else { + content.append("&delete=yes"); + } + + if (serverAccessKey != null) { + content.append("&key=").append(serverAccessKey); + } + + dos.writeBytes(content.toString()); + dos.flush(); + + InputStream is = conn.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line; + if (conn.getResponseCode() == 200) { + while ((line = br.readLine()) != null) { + if (serverAccessKey == null) { + serverAccessKey = line; + } + } + } + } catch (Exception ignored) { + + } + } + + /** + * Adds a roll report to the GameManager's current pending report list. + * + * @param roll The roll to add + */ + public void reportRoll(Roll roll) { + gameManager.addReport(getGame().getNewReport(1230).addRoll(roll)); + } +} diff --git a/results/patched8.csv b/results/patched8.csv new file mode 100644 index 0000000..446ca46 --- /dev/null +++ b/results/patched8.csv @@ -0,0 +1,31 @@ +elapsedSec,label,gamesStarted,gamesLive,totalRounds,rssKb,heapUsedKb,heapCommittedKb,nonHeapUsedKb,metaspaceKb,liveHeapKb +0.8,jvm-start,0,0,0,48276,1980,16384,3232,1213,2061 +2.9,unit-cache-loaded,0,0,0,333520,92156,307200,38950,25487,92156 +4.5,settling-0,1,1,0,570452,161352,307200,74693,44313,-1 +5.5,settling-0,1,1,1,597136,251390,307200,83668,45509,-1 +6.5,settling-0,1,1,1,618932,258578,307200,89115,46291,-1 +7.5,settling-0,1,1,1,664900,210413,307200,91267,46325,-1 +8.5,settling-0,1,1,2,736596,138843,368640,94758,47486,-1 +9.5,settling-0,1,1,2,756344,205495,368640,96662,47551,-1 +10.5,settling-0,1,1,2,836008,299562,442368,97480,47560,-1 +11.5,settling-0,1,1,2,836636,379890,442368,96774,47632,-1 +12.5,settling-0,1,1,3,839436,319019,442368,99164,47786,-1 +13.4,settled-game-1,1,1,3,845768,376630,442368,76597,47611,380016 +14.9,settling-1,2,2,4,1301324,548427,884736,79583,47738,-1 +15.9,settling-1,2,2,4,1281912,298409,884736,80142,47780,-1 +16.9,settling-1,2,2,6,1298284,694110,884736,81463,47888,-1 +17.9,settling-1,2,2,6,1298804,514508,884736,81870,47895,-1 +18.9,settling-1,2,2,6,1298840,445865,884736,82193,47905,-1 +19.9,settling-1,2,2,6,1299316,392427,884736,81960,47944,-1 +20.9,settling-1,2,2,7,1299560,651182,884736,83255,47977,-1 +21.9,settling-1,2,2,7,1348528,702077,884736,86970,48481,-1 +22.9,settling-1,2,2,8,1377356,375625,884736,88603,48522,-1 +23.8,settled-game-2,2,2,8,1143264,389865,643072,77671,48590,395145 +25.3,settling-2,3,3,9,1369040,440810,991232,82712,48727,-1 +26.3,settling-2,3,3,10,1536644,639599,991232,84087,48764,-1 +27.3,settling-2,3,3,11,1537072,752695,991232,85919,48915,-1 +28.3,settling-2,3,3,11,1537064,341778,991232,84915,48919,-1 +29.3,settling-2,3,3,12,1538668,665717,991232,85864,48994,-1 +30.3,settling-2,3,3,14,1543808,689700,991232,88308,49894,-1 +31.3,settling-2,3,3,15,1544596,814842,991232,88748,49948,-1 +32.3,settling-2,3,3,16,1544704,669312,991232,89225,49979,-1 diff --git a/results/staggered8.csv b/results/staggered8.csv index 7e0edc7..c616e55 100644 --- a/results/staggered8.csv +++ b/results/staggered8.csv @@ -18,3 +18,213 @@ elapsedSec,label,gamesStarted,gamesLive,totalRounds,rssKb,heapUsedKb,heapCommitt 18.9,settling-1,2,2,5,1075328,313845,696320,79615,47931,-1 19.9,settling-1,2,2,5,1075356,533628,696320,79652,47936,-1 20.9,settling-1,2,2,6,1076796,441359,696320,80060,47994,-1 +21.7,settled-game-2,2,2,6,1007072,292737,602112,80715,48060,293767 +23.5,settling-2,3,3,7,1041044,286572,622592,80817,48161,-1 +24.5,settling-2,3,3,7,1044116,246957,622592,80451,48162,-1 +25.5,settling-2,3,3,8,1046012,353156,622592,82020,48265,-1 +26.5,settling-2,3,3,8,1072640,262242,622592,83079,48753,-1 +27.5,settling-2,3,3,8,1101728,256394,622592,84739,48756,-1 +28.5,settling-2,3,3,8,1103480,307582,622592,81842,48767,-1 +29.5,settling-2,3,3,9,1125812,268958,622592,85738,48833,-1 +30.4,settled-game-3,3,3,9,1132220,330165,622592,82194,48835,333360 +31.9,settling-3,4,4,9,1158672,373028,647168,83008,48888,-1 +32.9,settling-3,4,4,9,1158536,373028,647168,83008,48888,-1 +33.9,settling-3,4,4,9,1158536,373028,647168,83008,48888,-1 +34.9,settling-3,4,4,9,1158484,373028,647168,82446,48888,-1 +35.9,settling-3,4,4,9,1104936,373028,647168,82446,48888,-1 +36.9,settling-3,4,4,9,1104932,373028,647168,81890,48888,-1 +37.9,settling-3,4,4,9,1104932,373028,647168,81890,48888,-1 +38.9,settling-3,4,4,9,1104932,373028,647168,81890,48888,-1 +39.9,settling-3,4,4,9,1104932,377124,647168,81890,48888,-1 +40.9,settling-3,4,4,9,1104932,377124,647168,81890,48888,-1 +41.9,settling-3,4,4,9,1104932,377124,647168,81890,48888,-1 +42.9,settling-3,4,4,9,1104932,377124,647168,81890,48888,-1 +43.9,settling-3,4,4,9,1104932,377124,647168,81894,48888,-1 +44.9,settling-3,4,4,9,1104932,377124,647168,81895,48888,-1 +45.9,settling-3,4,4,9,1104932,377124,647168,81897,48888,-1 +46.9,settling-3,4,4,9,1104936,377124,647168,81909,48892,-1 +47.9,settling-3,4,4,9,1104940,377124,647168,81916,48894,-1 +48.9,settling-3,4,4,9,1104940,377124,647168,81916,48894,-1 +49.9,settling-3,4,4,9,1104940,377124,647168,81916,48894,-1 +50.9,settling-3,4,4,9,1104940,377124,647168,81920,48895,-1 +51.9,settling-3,4,4,9,1104940,377124,647168,81921,48895,-1 +52.9,settling-3,4,4,9,1104940,377124,647168,81930,48895,-1 +53.9,settling-3,4,4,9,1104940,377124,647168,81930,48895,-1 +54.9,settling-3,4,4,9,1104940,377124,647168,81930,48895,-1 +55.9,settling-3,4,4,9,1104940,377124,647168,81931,48895,-1 +56.9,settling-3,4,4,9,1104940,377124,647168,81932,48895,-1 +57.9,settling-3,4,4,9,1104940,377124,647168,81935,48895,-1 +58.9,settling-3,4,4,9,1104940,377124,647168,81935,48895,-1 +59.9,settling-3,4,4,9,1104940,377124,647168,81936,48895,-1 +60.9,settling-3,4,4,9,1104940,377124,647168,81936,48895,-1 +62.0,settling-3,4,4,9,1104940,377124,647168,81936,48895,-1 +63.0,settling-3,4,4,9,1104940,377124,647168,81937,48895,-1 +64.0,settling-3,4,4,9,1104940,377124,647168,81937,48895,-1 +65.0,settling-3,4,4,9,1104940,377124,647168,81937,48895,-1 +66.0,settling-3,4,4,9,1104940,377124,647168,81937,48896,-1 +67.0,settling-3,4,4,9,1104940,377124,647168,81943,48896,-1 +68.0,settling-3,4,4,9,1104940,377124,647168,81945,48897,-1 +69.0,settling-3,4,4,9,1104940,377124,647168,81949,48897,-1 +70.0,settling-3,4,4,9,1104940,377124,647168,81955,48897,-1 +71.0,settling-3,4,4,9,1104940,377124,647168,81955,48897,-1 +72.0,settling-3,4,4,9,1104940,377124,647168,81955,48897,-1 +73.0,settling-3,4,4,9,1104940,377124,647168,81960,48898,-1 +74.0,settling-3,4,4,9,1104944,377124,647168,81960,48900,-1 +75.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +76.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +77.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +78.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +79.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +80.0,settling-3,4,4,9,1104952,377124,647168,81992,48908,-1 +81.0,settling-3,4,4,9,1104952,377124,647168,81992,48909,-1 +82.0,settling-3,4,4,9,1104956,377124,647168,82007,48912,-1 +83.0,settling-3,4,4,9,1104956,377124,647168,82010,48912,-1 +84.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +85.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +86.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +87.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +88.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +89.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +90.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +91.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +92.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +93.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +94.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +95.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +96.0,settling-3,4,4,9,1104960,377124,647168,82019,48913,-1 +97.0,settling-3,4,4,9,1104960,377124,647168,82019,48915,-1 +98.0,settling-3,4,4,9,1104960,377124,647168,82052,48915,-1 +99.0,settling-3,4,4,9,1104960,377124,647168,82052,48915,-1 +100.0,settling-3,4,4,9,1104960,377124,647168,82054,48916,-1 +101.0,settling-3,4,4,9,1104964,377124,647168,82063,48917,-1 +102.0,settling-3,4,4,9,1104964,377124,647168,82063,48917,-1 +103.0,settling-3,4,4,9,1104964,377124,647168,82066,48917,-1 +104.0,settling-3,4,4,9,1104964,377124,647168,82067,48917,-1 +105.0,settling-3,4,4,9,1104964,377124,647168,82067,48917,-1 +106.0,settling-3,4,4,9,1104964,381220,647168,82067,48917,-1 +107.0,settling-3,4,4,9,1104964,381220,647168,82067,48917,-1 +108.0,settling-3,4,4,9,1104964,381220,647168,82067,48917,-1 +109.0,settling-3,4,4,9,1104964,381220,647168,82067,48917,-1 +110.0,settling-3,4,4,9,1104964,381220,647168,82074,48917,-1 +111.0,settling-3,4,4,9,1104964,381220,647168,82074,48917,-1 +112.0,settling-3,4,4,9,1104964,381220,647168,82074,48917,-1 +113.0,settling-3,4,4,9,1104964,381220,647168,82074,48917,-1 +114.0,settling-3,4,4,9,1104964,381220,647168,82074,48917,-1 +115.0,settling-3,4,4,9,1104964,381220,647168,82075,48917,-1 +116.0,settling-3,4,4,9,1104964,381220,647168,82081,48917,-1 +117.0,settling-3,4,4,9,1104964,381220,647168,82081,48917,-1 +118.0,settling-3,4,4,9,1104964,381220,647168,82082,48917,-1 +119.0,settling-3,4,4,9,1104964,381220,647168,82082,48917,-1 +120.0,settling-3,4,4,9,1104964,381220,647168,82082,48919,-1 +121.0,settling-3,4,4,9,1104972,381220,647168,82114,48928,-1 +122.0,settling-3,4,4,9,1104972,381220,647168,82115,48928,-1 +123.0,settling-3,4,4,9,1104976,381220,647168,82126,48930,-1 +124.0,settling-3,4,4,9,1104976,381220,647168,82126,48931,-1 +125.0,settling-3,4,4,9,1104980,381220,647168,82151,48935,-1 +126.0,settling-3,4,4,9,1104980,381220,647168,82151,48937,-1 +127.0,settling-3,4,4,9,1104992,381220,647168,82195,48948,-1 +128.0,settling-3,4,4,9,1104992,381220,647168,82195,48948,-1 +129.0,settling-3,4,4,9,1104992,381220,647168,82195,48948,-1 +130.0,settling-3,4,4,9,1104996,381220,647168,82211,48952,-1 +131.0,settling-3,4,4,9,1105000,381220,647168,82228,48955,-1 +132.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +133.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +134.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +135.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +136.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +137.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +138.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +139.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +140.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +141.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +142.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +143.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +144.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +145.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +146.0,settling-3,4,4,9,1105000,381220,647168,82230,48955,-1 +147.0,settling-3,4,4,9,1105000,381220,647168,82231,48955,-1 +148.0,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +149.0,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +150.0,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +151.0,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +152.0,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +153.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +154.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +155.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +156.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +157.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +158.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +159.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +160.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +161.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +162.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +163.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +164.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +165.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +166.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +167.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +168.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +169.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +170.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +171.1,settling-3,4,4,9,1105000,381220,647168,82233,48955,-1 +172.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +173.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +174.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +175.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +176.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +177.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +178.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +179.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +180.1,settling-3,4,4,9,1105000,385316,647168,82233,48955,-1 +181.1,settling-3,4,4,9,1105156,385316,647168,82233,48955,-1 +182.1,settling-3,4,4,9,1105156,385316,647168,82233,48955,-1 +183.1,settling-3,4,4,9,1105156,385316,647168,82233,48955,-1 +184.1,settling-3,4,4,9,1105156,385316,647168,82233,48955,-1 +185.1,settling-3,4,4,9,1105156,385316,647168,82233,48955,-1 +186.1,settling-3,4,4,9,1105156,385316,647168,82235,48955,-1 +187.1,settling-3,4,4,9,1105156,385316,647168,82235,48955,-1 +188.1,settling-3,4,4,9,1105156,385316,647168,82235,48955,-1 +189.1,settling-3,4,4,9,1105156,385316,647168,82235,48955,-1 +190.1,settling-3,4,4,9,1105156,385316,647168,82243,48956,-1 +191.1,settling-3,4,4,9,1105168,385316,647168,82841,48965,-1 +192.1,settling-3,4,4,9,1105168,385316,647168,82841,48965,-1 +193.1,settling-3,4,4,9,1105168,385316,647168,82841,48965,-1 +194.1,settling-3,4,4,9,1105168,385316,647168,82841,48965,-1 +195.1,settling-3,4,4,9,1105168,385316,647168,82848,48966,-1 +196.1,settling-3,4,4,9,1105168,385316,647168,82848,48966,-1 +197.1,settling-3,4,4,9,1105168,385316,647168,82294,48966,-1 +198.1,settling-3,4,4,9,1105168,385316,647168,82294,48966,-1 +199.1,settling-3,4,4,9,1105168,385316,647168,82294,48966,-1 +200.1,settling-3,4,4,9,1105168,385316,647168,82294,48966,-1 +201.1,settling-3,4,4,9,1105168,385316,647168,82302,48968,-1 +202.1,settling-3,4,4,9,1105168,385316,647168,82302,48968,-1 +203.1,settling-3,4,4,9,1105168,385316,647168,82302,48968,-1 +204.1,settling-3,4,4,9,1105168,385316,647168,82302,48968,-1 +205.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +206.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +207.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +208.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +209.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +210.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +211.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +212.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +213.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +214.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +215.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +216.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +217.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +218.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +219.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +220.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +221.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +222.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +223.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +224.1,settling-3,4,4,9,1105168,389412,647168,82305,48968,-1 +225.1,settling-3,4,4,9,1105168,389412,647168,82305,48970,-1 +226.1,settling-3,4,4,9,1105172,389412,647168,82315,48971,-1 +227.1,settling-3,4,4,9,1105172,389412,647168,82315,48971,-1 +228.1,settling-3,4,4,9,1105172,389412,647168,82315,48971,-1 +229.1,settling-3,4,4,9,1105172,389412,647168,82319,48972,-1 +230.1,settling-3,4,4,9,1105176,389412,647168,82334,48976,-1 +231.1,settling-3,4,4,9,1105176,389412,647168,82340,48977,-1 +232.1,settling-3,4,4,9,1105176,389412,647168,82340,48977,-1 diff --git a/results/stall-jstack.txt b/results/stall-jstack.txt new file mode 100644 index 0000000..bb53d3b --- /dev/null +++ b/results/stall-jstack.txt @@ -0,0 +1,735 @@ +2026-07-30 14:35:35 +Full thread dump OpenJDK 64-Bit Server VM (21.0.12+8-LTS mixed mode, sharing): + +Threads class SMR info: +_java_thread_list=0x00007678d8001fb0, length=50, elements={ +0x000076797002cd20, 0x0000767970139000, 0x000076797013a570, 0x000076797013bd00, +0x000076797013d340, 0x000076797013e8e0, 0x00007679701404a0, 0x0000767970141b60, +0x0000767970182760, 0x0000767970184f90, 0x00007678a00dbfc0, 0x0000767970987160, +0x00007679709a3a00, 0x00007679709a4890, 0x00007679709b3990, 0x00007679709a7a90, +0x00007678c41512c0, 0x00007679709a9ac0, 0x00007679709b9e40, 0x00007678c4154d70, +0x000076785820db10, 0x00007678581b4a70, 0x00007679709a8b50, 0x0000767970119eb0, +0x000076797011a960, 0x000076797011b4a0, 0x00007679709c1320, 0x0000767830001760, +0x00007679709bdda0, 0x00007679709bedd0, 0x00007678300bd300, 0x000076797048f850, +0x00007679704900f0, 0x00007679709bfc90, 0x00007679709c03a0, 0x00007679704958d0, +0x0000767814005380, 0x00007679709bb1b0, 0x00007679709bbe00, 0x0000767814001620, +0x00007679709bce10, 0x0000767970494090, 0x0000767970494b10, 0x00007679704e45f0, +0x0000767970491c70, 0x00007677ec001090, 0x0000767970492c30, 0x000076797046c3f0, +0x00007677ec0024b0, 0x00007678d8000f60 +} + +"main" #1 [188702] prio=5 os_prio=0 cpu=1427.67ms elapsed=180.52s tid=0x000076797002cd20 nid=188702 waiting on condition [0x00007679767fe000] + java.lang.Thread.State: TIMED_WAITING (sleeping) + at java.lang.Thread.sleep0(java.base@21.0.12/Native Method) + at java.lang.Thread.sleep(java.base@21.0.12/Thread.java:509) + at bench.MMBench.waitForSettle(MMBench.java:221) + at bench.MMBench.main(MMBench.java:121) + +"Reference Handler" #9 [188710] daemon prio=10 os_prio=0 cpu=3.90ms elapsed=180.50s tid=0x0000767970139000 nid=188710 waiting on condition [0x0000767950bfe000] + java.lang.Thread.State: RUNNABLE + at java.lang.ref.Reference.waitForReferencePendingList(java.base@21.0.12/Native Method) + at java.lang.ref.Reference.processPendingReferences(java.base@21.0.12/Reference.java:246) + at java.lang.ref.Reference$ReferenceHandler.run(java.base@21.0.12/Reference.java:208) + +"Finalizer" #10 [188711] daemon prio=8 os_prio=0 cpu=0.16ms elapsed=180.50s tid=0x000076797013a570 nid=188711 in Object.wait() [0x0000767950afe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on <0x0000000680001a28> (a java.lang.ref.NativeReferenceQueue$Lock) + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at java.lang.ref.NativeReferenceQueue.await(java.base@21.0.12/NativeReferenceQueue.java:48) + at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.12/ReferenceQueue.java:158) + at java.lang.ref.NativeReferenceQueue.remove(java.base@21.0.12/NativeReferenceQueue.java:89) + - locked <0x0000000680001a28> (a java.lang.ref.NativeReferenceQueue$Lock) + at java.lang.ref.Finalizer$FinalizerThread.run(java.base@21.0.12/Finalizer.java:173) + +"Signal Dispatcher" #11 [188712] daemon prio=9 os_prio=0 cpu=0.26ms elapsed=180.50s tid=0x000076797013bd00 nid=188712 waiting on condition [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + +"Service Thread" #12 [188713] daemon prio=9 os_prio=0 cpu=22.29ms elapsed=180.50s tid=0x000076797013d340 nid=188713 runnable [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + +"Monitor Deflation Thread" #13 [188714] daemon prio=9 os_prio=0 cpu=37.58ms elapsed=180.50s tid=0x000076797013e8e0 nid=188714 runnable [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + +"C2 CompilerThread0" #14 [188715] daemon prio=9 os_prio=0 cpu=11162.84ms elapsed=180.50s tid=0x00007679701404a0 nid=188715 waiting on condition [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + No compile task + +"C1 CompilerThread0" #22 [188716] daemon prio=9 os_prio=0 cpu=1575.20ms elapsed=180.50s tid=0x0000767970141b60 nid=188716 waiting on condition [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + No compile task + +"Notification Thread" #26 [188720] daemon prio=9 os_prio=0 cpu=0.05ms elapsed=180.50s tid=0x0000767970182760 nid=188720 runnable [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + +"Common-Cleaner" #27 [188721] daemon prio=8 os_prio=0 cpu=5.80ms elapsed=180.50s tid=0x0000767970184f90 nid=188721 waiting on condition [0x0000767941ffe000] + java.lang.Thread.State: TIMED_WAITING (parking) + at jdk.internal.misc.Unsafe.park(java.base@21.0.12/Native Method) + - parking to wait for <0x00000006800389c8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.parkNanos(java.base@21.0.12/LockSupport.java:269) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.12/AbstractQueuedSynchronizer.java:1886) + at java.lang.ref.ReferenceQueue.await(java.base@21.0.12/ReferenceQueue.java:71) + at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.12/ReferenceQueue.java:143) + at java.lang.ref.ReferenceQueue.remove(java.base@21.0.12/ReferenceQueue.java:218) + at jdk.internal.ref.CleanerImpl.run(java.base@21.0.12/CleanerImpl.java:140) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + at jdk.internal.misc.InnocuousThread.run(java.base@21.0.12/InnocuousThread.java:186) + +"Timer-0" #33 [188732] daemon prio=4 os_prio=0 cpu=2.49ms elapsed=179.28s tid=0x00007678a00dbfc0 nid=188732 in Object.wait() [0x00007679415fc000] + java.lang.Thread.State: TIMED_WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.util.TimerThread.mainLoop(java.base@21.0.12/Timer.java:563) + - locked <0x0000000686260508> (a java.util.TaskQueue) + at java.util.TimerThread.run(java.base@21.0.12/Timer.java:516) + +"Watchdog Timer" #47 [188749] prio=5 os_prio=0 cpu=0.31ms elapsed=177.27s tid=0x0000767970987160 nid=188749 in Object.wait() [0x00007679419fc000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at java.util.TimerThread.mainLoop(java.base@21.0.12/Timer.java:537) + - locked <0x0000000686a04038> (a java.util.TaskQueue) + at java.util.TimerThread.run(java.base@21.0.12/Timer.java:516) + +"Packet Pump" #48 [188750] prio=5 os_prio=0 cpu=1646.64ms elapsed=177.25s tid=0x00007679709a3a00 nid=188750 in Object.wait() [0x00007679417fc000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.server.Server$PacketPump.run(Server.java:161) + - locked <0x0000000686a040c8> (a java.util.concurrent.ConcurrentLinkedQueue) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection Listener" #49 [188751] prio=5 os_prio=0 cpu=3.18ms elapsed=177.25s tid=0x00007679709a4890 nid=188751 runnable [0x000076795022e000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.Net.accept(java.base@21.0.12/Native Method) + at sun.nio.ch.NioSocketImpl.accept(java.base@21.0.12/NioSocketImpl.java:748) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:698) + at java.net.ServerSocket.platformImplAccept(java.base@21.0.12/ServerSocket.java:663) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:639) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:585) + at java.net.ServerSocket.accept(java.base@21.0.12/ServerSocket.java:543) + at megamek.server.Server.run(Server.java:1436) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeA)" #50 [188752] prio=5 os_prio=0 cpu=885.09ms elapsed=177.16s tid=0x00007679709b3990 nid=188752 in Object.wait() [0x00007679416fc000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x00000006869e43d0> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeA" #51 [188753] prio=5 os_prio=0 cpu=638.23ms elapsed=177.15s tid=0x00007679709a7a90 nid=188753 runnable [0x00007679406cf000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x00000006869f4778> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #52 [188754] prio=5 os_prio=0 cpu=105.00ms elapsed=177.15s tid=0x00007678c41512c0 nid=188754 runnable [0x0000767941afc000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x0000000686a14300> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeB)" #54 [188755] prio=5 os_prio=0 cpu=978.63ms elapsed=177.12s tid=0x00007679709a9ac0 nid=188755 in Object.wait() [0x0000767940361000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x0000000686a24188> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeB" #55 [188756] prio=5 os_prio=0 cpu=600.54ms elapsed=177.12s tid=0x00007679709b9e40 nid=188756 runnable [0x0000767940261000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x0000000686a54428> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 3" #56 [188757] prio=5 os_prio=0 cpu=95.89ms elapsed=177.12s tid=0x00007678c4154d70 nid=188757 runnable [0x0000767940161000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x0000000686a745b8> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Java2D Disposer" #57 [188758] daemon prio=10 os_prio=0 cpu=0.65ms elapsed=177.12s tid=0x000076785820db10 nid=188758 waiting on condition [0x00007678eeffe000] + java.lang.Thread.State: WAITING (parking) + at jdk.internal.misc.Unsafe.park(java.base@21.0.12/Native Method) + - parking to wait for <0x0000000686a24238> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.park(java.base@21.0.12/LockSupport.java:371) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@21.0.12/AbstractQueuedSynchronizer.java:519) + at java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@21.0.12/ForkJoinPool.java:3780) + at java.util.concurrent.ForkJoinPool.managedBlock(java.base@21.0.12/ForkJoinPool.java:3725) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@21.0.12/AbstractQueuedSynchronizer.java:1746) + at java.lang.ref.ReferenceQueue.await(java.base@21.0.12/ReferenceQueue.java:67) + at java.lang.ref.ReferenceQueue.remove0(java.base@21.0.12/ReferenceQueue.java:158) + at java.lang.ref.ReferenceQueue.remove(java.base@21.0.12/ReferenceQueue.java:234) + at sun.java2d.Disposer.run(java.desktop@21.0.12/Disposer.java:145) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"AWT-XAWT" #58 [188759] daemon prio=6 os_prio=0 cpu=38.10ms elapsed=177.12s tid=0x00007678581b4a70 nid=188759 runnable [0x00007678eeefe000] + java.lang.Thread.State: RUNNABLE + at sun.awt.X11.XToolkit.waitForEvents(java.desktop@21.0.12/Native Method) + at sun.awt.X11.XToolkit.run(java.desktop@21.0.12/XToolkit.java:687) + at sun.awt.X11.XToolkit.run(java.desktop@21.0.12/XToolkit.java:651) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Watchdog Timer" #97 [188878] prio=5 os_prio=0 cpu=0.33ms elapsed=165.78s tid=0x00007679709a8b50 nid=188878 in Object.wait() [0x00007678ee7fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at java.util.TimerThread.mainLoop(java.base@21.0.12/Timer.java:537) + - locked <0x00000006880015b8> (a java.util.TaskQueue) + at java.util.TimerThread.run(java.base@21.0.12/Timer.java:516) + +"Packet Pump" #98 [188879] prio=5 os_prio=0 cpu=1045.73ms elapsed=165.78s tid=0x0000767970119eb0 nid=188879 in Object.wait() [0x00007678eeafe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.server.Server$PacketPump.run(Server.java:161) + - locked <0x000000068811c3d8> (a java.util.concurrent.ConcurrentLinkedQueue) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection Listener" #99 [188880] prio=5 os_prio=0 cpu=1.06ms elapsed=165.78s tid=0x000076797011a960 nid=188880 runnable [0x00007678ee9fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.Net.accept(java.base@21.0.12/Native Method) + at sun.nio.ch.NioSocketImpl.accept(java.base@21.0.12/NioSocketImpl.java:748) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:698) + at java.net.ServerSocket.platformImplAccept(java.base@21.0.12/ServerSocket.java:663) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:639) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:585) + at java.net.ServerSocket.accept(java.base@21.0.12/ServerSocket.java:543) + at megamek.server.Server.run(Server.java:1436) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeA)" #100 [188881] prio=5 os_prio=0 cpu=530.19ms elapsed=165.75s tid=0x000076797011b4a0 nid=188881 in Object.wait() [0x00007678ee6fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x00000006880016a8> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeA" #101 [188882] prio=5 os_prio=0 cpu=472.86ms elapsed=165.75s tid=0x00007679709c1320 nid=188882 runnable [0x00007678ee5fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x00000006880018b0> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #102 [188883] prio=5 os_prio=0 cpu=44.54ms elapsed=165.75s tid=0x0000767830001760 nid=188883 runnable [0x00007678ee4fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x00000006853850d0> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeB)" #103 [188884] prio=5 os_prio=0 cpu=619.79ms elapsed=165.72s tid=0x00007679709bdda0 nid=188884 in Object.wait() [0x00007678ee3fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x00000006853e21c8> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeB" #104 [188885] prio=5 os_prio=0 cpu=474.74ms elapsed=165.71s tid=0x00007679709bedd0 nid=188885 runnable [0x00007678ee2fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x00000006853f23e0> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #105 [188886] prio=5 os_prio=0 cpu=43.52ms elapsed=165.71s tid=0x00007678300bd300 nid=188886 runnable [0x00007678ee1fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x0000000689c76b18> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Watchdog Timer" #134 [188965] prio=5 os_prio=0 cpu=0.38ms elapsed=158.28s tid=0x000076797048f850 nid=188965 in Object.wait() [0x0000767941cfd000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at java.util.TimerThread.mainLoop(java.base@21.0.12/Timer.java:537) + - locked <0x000000068aa6e090> (a java.util.TaskQueue) + at java.util.TimerThread.run(java.base@21.0.12/Timer.java:516) + +"Packet Pump" #135 [188966] prio=5 os_prio=0 cpu=1109.61ms elapsed=158.28s tid=0x00007679704900f0 nid=188966 in Object.wait() [0x00007678ee8fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.server.Server$PacketPump.run(Server.java:161) + - locked <0x000000068aaa6690> (a java.util.concurrent.ConcurrentLinkedQueue) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection Listener" #136 [188967] prio=5 os_prio=0 cpu=1.68ms elapsed=158.28s tid=0x00007679709bfc90 nid=188967 runnable [0x00007678ee0fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.Net.accept(java.base@21.0.12/Native Method) + at sun.nio.ch.NioSocketImpl.accept(java.base@21.0.12/NioSocketImpl.java:748) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:698) + at java.net.ServerSocket.platformImplAccept(java.base@21.0.12/ServerSocket.java:663) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:639) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:585) + at java.net.ServerSocket.accept(java.base@21.0.12/ServerSocket.java:543) + at megamek.server.Server.run(Server.java:1436) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeA)" #137 [188968] prio=5 os_prio=0 cpu=813.96ms elapsed=158.24s tid=0x00007679709c03a0 nid=188968 in Object.wait() [0x00007678edffe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x000000068ab0b400> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:469) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeA" #138 [188969] prio=5 os_prio=0 cpu=517.13ms elapsed=158.24s tid=0x00007679704958d0 nid=188969 runnable [0x00007678edefe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068ab29b20> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #139 [188970] prio=5 os_prio=0 cpu=56.29ms elapsed=158.24s tid=0x0000767814005380 nid=188970 runnable [0x00007678eddfe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068aa3cae0> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeB)" #140 [188971] prio=5 os_prio=0 cpu=712.99ms elapsed=158.17s tid=0x00007679709bb1b0 nid=188971 in Object.wait() [0x00007678edcfe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x000000068b80aab8> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeB" #141 [188972] prio=5 os_prio=0 cpu=512.32ms elapsed=158.17s tid=0x00007679709bbe00 nid=188972 runnable [0x00007678edbfe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068b83fb98> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #142 [188973] prio=5 os_prio=0 cpu=82.17ms elapsed=158.03s tid=0x0000767814001620 nid=188973 runnable [0x00007679418fc000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068b86ba20> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Watchdog Timer" #173 [189017] prio=5 os_prio=0 cpu=0.39ms elapsed=149.75s tid=0x00007679709bce10 nid=189017 in Object.wait() [0x000076795032e000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at java.util.TimerThread.mainLoop(java.base@21.0.12/Timer.java:537) + - locked <0x000000068d3ab9a8> (a java.util.TaskQueue) + at java.util.TimerThread.run(java.base@21.0.12/Timer.java:516) + +"Packet Pump" #174 [189018] prio=5 os_prio=0 cpu=180.91ms elapsed=149.75s tid=0x0000767970494090 nid=189018 in Object.wait() [0x00007678eecfe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.server.Server$PacketPump.run(Server.java:161) + - locked <0x000000068d000078> (a java.util.concurrent.ConcurrentLinkedQueue) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection Listener" #175 [189019] prio=5 os_prio=0 cpu=1.63ms elapsed=149.75s tid=0x0000767970494b10 nid=189019 runnable [0x00007678ed6fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.Net.accept(java.base@21.0.12/Native Method) + at sun.nio.ch.NioSocketImpl.accept(java.base@21.0.12/NioSocketImpl.java:748) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:698) + at java.net.ServerSocket.platformImplAccept(java.base@21.0.12/ServerSocket.java:663) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:639) + at java.net.ServerSocket.implAccept(java.base@21.0.12/ServerSocket.java:585) + at java.net.ServerSocket.accept(java.base@21.0.12/ServerSocket.java:543) + at megamek.server.Server.run(Server.java:1436) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeA)" #176 [189020] prio=5 os_prio=0 cpu=0.66ms elapsed=149.71s tid=0x00007679704e45f0 nid=189020 in Object.wait() [0x00007678ed5fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x000000068d325608> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeA" #177 [189021] prio=5 os_prio=0 cpu=98.43ms elapsed=149.71s tid=0x0000767970491c70 nid=189021 runnable [0x00007678ed4fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068d2af3d8> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #178 [189022] prio=5 os_prio=0 cpu=22.98ms elapsed=149.71s tid=0x00007677ec001090 nid=189022 runnable [0x00007678ed3fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068d4101e0> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Princess-precognition (TraineeB)" #179 [189023] prio=5 os_prio=0 cpu=0.66ms elapsed=149.67s tid=0x0000767970492c30 nid=189023 in Object.wait() [0x00007678ed2fe000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait0(java.base@21.0.12/Native Method) + - waiting on + at java.lang.Object.wait(java.base@21.0.12/Object.java:366) + at java.lang.Object.wait(java.base@21.0.12/Object.java:339) + at megamek.client.bot.princess.Precognition.waitForUnpause(Precognition.java:497) + - locked <0x000000068d086410> (a megamek.client.bot.princess.Precognition) + at megamek.client.bot.princess.Precognition.run(Precognition.java:471) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Client Connection, Player TraineeB" #180 [189024] prio=5 os_prio=0 cpu=90.95ms elapsed=149.67s tid=0x000076797046c3f0 nid=189024 runnable [0x00007678ed1fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068d229040> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.client.AbstractClient.updateConnection(AbstractClient.java:135) + at megamek.client.AbstractClient$ConnectionHandler.run(AbstractClient.java:630) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Connection 2" #181 [189025] prio=5 os_prio=0 cpu=21.71ms elapsed=149.67s tid=0x00007677ec0024b0 nid=189025 runnable [0x00007678ed0fe000] + java.lang.Thread.State: RUNNABLE + at sun.nio.ch.SocketDispatcher.read0(java.base@21.0.12/Native Method) + at sun.nio.ch.SocketDispatcher.read(java.base@21.0.12/SocketDispatcher.java:48) + at sun.nio.ch.NioSocketImpl.tryRead(java.base@21.0.12/NioSocketImpl.java:256) + at sun.nio.ch.NioSocketImpl.implRead(java.base@21.0.12/NioSocketImpl.java:307) + at sun.nio.ch.NioSocketImpl.read(java.base@21.0.12/NioSocketImpl.java:346) + at sun.nio.ch.NioSocketImpl$1.read(java.base@21.0.12/NioSocketImpl.java:796) + at java.net.Socket$SocketInputStream.read(java.base@21.0.12/Socket.java:1099) + at java.io.BufferedInputStream.fill(java.base@21.0.12/BufferedInputStream.java:291) + at java.io.BufferedInputStream.implRead(java.base@21.0.12/BufferedInputStream.java:325) + at java.io.BufferedInputStream.read(java.base@21.0.12/BufferedInputStream.java:312) + at java.io.DataInputStream.readUnsignedByte(java.base@21.0.12/DataInputStream.java:295) + at java.io.DataInputStream.readBoolean(java.base@21.0.12/DataInputStream.java:255) + at megamek.common.net.connections.DataStreamConnection.readNetworkPacket(DataStreamConnection.java:97) + - locked <0x000000068d1a2ca8> (a java.io.DataInputStream) + at megamek.common.net.connections.AbstractConnection.update(AbstractConnection.java:316) + at megamek.server.ConnectionHandler.run(ConnectionHandler.java:67) + at java.lang.Thread.runWith(java.base@21.0.12/Thread.java:1596) + at java.lang.Thread.run(java.base@21.0.12/Thread.java:1583) + +"Attach Listener" #185 [189624] daemon prio=9 os_prio=0 cpu=0.33ms elapsed=0.10s tid=0x00007678d8000f60 nid=189624 waiting on condition [0x0000000000000000] + java.lang.Thread.State: RUNNABLE + +"G1 Conc#2" os_prio=0 cpu=471.83ms elapsed=179.04s tid=0x0000767944001830 nid=188744 runnable + +"G1 Conc#1" os_prio=0 cpu=467.15ms elapsed=179.04s tid=0x0000767944000d00 nid=188743 runnable + +"GC Thread#12" os_prio=0 cpu=462.21ms elapsed=179.04s tid=0x00007678e8018180 nid=188742 runnable + +"GC Thread#11" os_prio=0 cpu=435.91ms elapsed=179.04s tid=0x00007678e8017690 nid=188741 runnable + +"GC Thread#10" os_prio=0 cpu=425.09ms elapsed=179.05s tid=0x00007678e8016ba0 nid=188740 runnable + +"GC Thread#9" os_prio=0 cpu=434.80ms elapsed=179.05s tid=0x00007678e80160b0 nid=188739 runnable + +"GC Thread#8" os_prio=0 cpu=447.01ms elapsed=179.05s tid=0x00007678e80155c0 nid=188738 runnable + +"GC Thread#7" os_prio=0 cpu=450.89ms elapsed=179.05s tid=0x00007678e8014ad0 nid=188737 runnable + +"GC Thread#6" os_prio=0 cpu=426.82ms elapsed=179.05s tid=0x00007678e8014030 nid=188736 runnable + +"GC Thread#5" os_prio=0 cpu=467.39ms elapsed=179.05s tid=0x00007678e800b670 nid=188735 runnable + +"GC Thread#4" os_prio=0 cpu=434.03ms elapsed=179.05s tid=0x00007678e800abd0 nid=188734 runnable + +"GC Thread#3" os_prio=0 cpu=475.01ms elapsed=179.05s tid=0x00007678e800a240 nid=188733 runnable + +"GC Thread#2" os_prio=0 cpu=454.38ms elapsed=179.59s tid=0x00007678e8009840 nid=188725 runnable + +"GC Thread#1" os_prio=0 cpu=490.35ms elapsed=180.47s tid=0x00007678e8001170 nid=188722 runnable + +"VM Thread" os_prio=0 cpu=114.91ms elapsed=180.51s tid=0x000076797012fc10 nid=188709 runnable + +"VM Periodic Task Thread" os_prio=0 cpu=204.91ms elapsed=180.51s tid=0x000076797011d820 nid=188708 waiting on condition + +"G1 Service" os_prio=0 cpu=24.66ms elapsed=180.52s tid=0x000076797010c890 nid=188707 runnable + +"G1 Refine#0" os_prio=0 cpu=236.80ms elapsed=180.52s tid=0x000076797010b8d0 nid=188706 runnable + +"G1 Conc#0" os_prio=0 cpu=464.41ms elapsed=180.52s tid=0x0000767970063cc0 nid=188705 runnable + +"G1 Main Marker" os_prio=0 cpu=2.81ms elapsed=180.52s tid=0x0000767970062d20 nid=188704 runnable + +"GC Thread#0" os_prio=0 cpu=419.85ms elapsed=180.52s tid=0x00007679700556e0 nid=188703 runnable + +JNI global refs: 88, weak refs: 0 + -- 2.51.2