diff --git a/src/arena/MatchWatcher.java b/src/arena/MatchWatcher.java index 3be1ef8..0191d47 100644 --- a/src/arena/MatchWatcher.java +++ b/src/arena/MatchWatcher.java @@ -77,15 +77,44 @@ public final class MatchWatcher { // The host may still be starting up; a match container brings both up at // once and the ordering between them is not guaranteed. + // Retry the socket, not just the handshake. + // + // `AbstractClient.connect()` opens the connection once and returns + // whether it worked; it never retries, and it starts no thread and + // registers no listener when it fails, so calling it again is safe. + // Attempting it once *before* this loop and then only polling + // isConnected() meant a single attempt decided the match: the + // entrypoint starts this JVM beside the host so their cold starts + // overlap, so that attempt almost always lands before MatchHost has + // finished loading its unit cache and bound the port, and the loop + // then spent its whole deadline waiting on a client that would never + // try again. Every match ran with no observer and uploaded no turn + // reports. + // + // Two clocks, because they answer different questions: the socket is + // retried every couple of seconds, and `connected` - which the server + // sets on its welcome packet, not on open() - is polled in between. long connectDeadline = System.currentTimeMillis() + 120_000L; - observer.connect(); + long nextAttempt = 0L; + int attempts = 0; while (!observer.isConnected()) { - if (System.currentTimeMillis() > connectDeadline) { - System.err.println("[watch] could not connect to " + host + ":" + port + " within 120s"); + long now = System.currentTimeMillis(); + if (now > connectDeadline) { + System.err.println("[watch] could not connect to " + host + ":" + port + + " within 120s (" + attempts + " attempts)"); System.exit(1); } + if (now >= nextAttempt) { + attempts++; + observer.connect(); + nextAttempt = now + 2_000L; + } Thread.sleep(250L); } + if (attempts > 1) { + System.out.println("[watch] connected on attempt " + attempts + + "; the host was still starting up"); + } System.out.println("[watch] connected to " + host + ":" + port + "; spooling to " + spool.toAbsolutePath()); // Deliberately NOT done. Until every human seat is filled, MatchHost