package bench; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import megamek.common.compute.Compute; 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.common.units.Entity; import megamek.server.Server; import megamek.server.totalWarfare.TWGameManager; /** * Does two games in one JVM actually give you two independent games? * *

The multi-tenancy question hinges on this. If {@code scenario.createGame()} hands * back Entity instances that are shared with a previously created game - because the * unit cache hands out references rather than copies - then two servers in one JVM are * silently corrupting each other and no amount of memory saving is worth it. * *

Checks, in order of how fatal a failure would be: *

    *
  1. Entity object identity across two games from the same scenario.
  2. *
  3. Entity ID space overlap (expected to overlap; only a problem if objects are shared too).
  4. *
  5. Game/Board/Player object identity.
  6. *
  7. Whether two Servers can bind and coexist at all.
  8. *
*/ public final class IsolationCheck { public static void main(String[] args) throws Exception { String scenarioPath = args.length > 0 ? args[0] : "data/scenarios/TrainingScenarios/1-FirstRun.mms"; Compute.d6(); MekSummaryCache cache = MekSummaryCache.getInstance(); while (!cache.isInitialized()) { Thread.sleep(250L); } System.out.printf("unit cache ready (%d units)%n%n", cache.getAllMeks().length); IGame gameA = load(scenarioPath); IGame gameB = load(scenarioPath); List a = new ArrayList<>(((Game) gameA).getEntitiesVector()); List b = new ArrayList<>(((Game) gameB).getEntitiesVector()); System.out.printf("game A entities: %d, game B entities: %d%n", a.size(), b.size()); // 1. THE question: are any Entity objects literally shared between games? Set identitiesA = new HashSet<>(); for (Entity e : a) { identitiesA.add(System.identityHashCode(e)); } int shared = 0; for (Entity e : b) { if (identitiesA.contains(System.identityHashCode(e))) { shared++; } } System.out.printf("%n[1] shared Entity instances between games: %d -> %s%n", shared, shared == 0 ? "PASS (independent objects)" : "FAIL (games would corrupt each other)"); // 2. ID overlap is fine on its own - each Game indexes its own entities. Set idsA = new HashSet<>(); for (Entity e : a) { idsA.add(e.getId()); } int idOverlap = 0; for (Entity e : b) { if (idsA.contains(e.getId())) { idOverlap++; } } System.out.printf("[2] entity ID overlap: %d of %d -> %s%n", idOverlap, b.size(), "expected; per-Game id space, only fatal if [1] failed"); // 3. Container objects must also be distinct. System.out.printf("[3] Game objects distinct: %s%n", gameA != gameB); System.out.printf(" Board objects distinct: %s%n", gameA.getBoard(0) != gameB.getBoard(0)); System.out.printf(" Player lists distinct: %s%n", gameA.getPlayersList() != gameB.getPlayersList()); // 4. Can two Servers coexist in one JVM? System.out.println(); TWGameManager gmA = new TWGameManager(); TWGameManager gmB = new TWGameManager(); Server serverA = new Server("", 9901, gmA); Server serverB = new Server("", 9902, gmB); serverA.setGame(gameA); serverB.setGame(gameB); System.out.printf("[4] two Servers constructed and bound: PASS (ports 9901/9902)%n"); System.out.printf(" server A game == game A: %s%n", serverA.getGame() == gameA); 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); } private static IGame load(String scenarioPath) throws Exception { ScenarioLoader loader = new ScenarioLoader(new File(scenarioPath)); Scenario scenario = loader.load(); return scenario.createGame(); } }