Skip to content

Fix robots staying in memory on Java 24+, and the client spinning when CLEARDATA cannot delete - #120

Merged
flemming-n-larsen merged 2 commits into
robo-code:mainfrom
Chase-san:rumble-1.12-fixes
Nov 1, 2026
Merged

flemming-n-larsen merged 2 commits into
robo-code:mainfrom
Chase-san:rumble-1.12-fixes

Conversation

@Chase-san

Copy link
Copy Markdown
Contributor

This was largely written by Claude Opus 5.5

Two bugs found while testing 1.12.0 against AutoRumble with real robots, on Linux (Java 21) and Windows (Java 8, 21 and 25).

Robots stay in memory on Java 24+

ThreadManager adds a robot's thread to outputStreamThreads before it opens a file or creates the data directory, and only RobocodeSecurityPolicy removes it again. Java 24+ has no SecurityManager, so the thread stays in the list, and through it the whole robot: its proxy, peer, class loader and static data.

Every robot that writes a file is affected, so a long run runs out of memory. Three clients on Java 25, with the -Xmx512M of roborumble.bat, ran out of memory after 59, 86 and 362 battles. The battle thread died and the clients hung. A heap dump showed 164 RobotPeers, of which 162 were held through this list.

This is not from #117 or #118: 1.11.2 does the same, keeping 80 robots after 41 battles. Java 8 and 21 are not affected, as their SecurityManager removes the thread.

The fix removes the thread once the file is open or the directory is created.

CLEARDATA can make the client skip battles or spin

closeOpenStreams only knows files opened for writing through Robocode. A file a robot opened for reading and did not close cannot be deleted on Windows until the stream is garbage collected, and skipping a battle allocates next to nothing, so a collection can be a long time coming. With a test robot that does this, in a 3-robot list, clients skipped 69% of their battles on Java 25 and 82% on Java 21 over two minutes. No real robot did it in about 26,000 battles, though only about 3,200 of those were on Windows.

If the file stays undeletable, e.g. as another program holds it, the client goes through its battles at full speed and asks the server for the ratings on each iteration: 348 iterations, 17,362 skipped battles and 4 MB of log in a minute with ITERATE=YES.

The fix collects the garbage and tries again, and if that fails too, waits a minute before going on. A battle is still skipped when the data cannot be deleted, so results never claim otherwise.

Testing

Test Result
ThreadManagerTest, BattlesRunnerTest on Java 8, 21 and 25 on Windows Pass, and each new test fails without its fix
File and hidden name robot tests on Java 8 and 25, with the two above 18 tests, all pass
Client on Java 25 fighting only robots that save data 205 battles with no out of memory; 2 robots in memory and 13.7 MB of heap after 100 battles
Client with the test robot that leaves a file open for reading 20 of 20 battles on each of Java 8, 21 and 25, with no skips
Client with a file it cannot delete, on Linux 1 iteration and 2 skipped battles in 130 seconds

The full test suite has not been run on this branch.

🤖 Generated with Claude Code

Chase-san and others added 2 commits November 1, 2026 12:17
…s in memory with Java 24+

ThreadManager adds the thread to outputStreamThreads before it opens a
robot's file or creates its data directory, and only RobocodeSecurityPolicy
removes it again, when the SecurityManager asks about the write. Java 24+
has no SecurityManager, so the thread stays in the list, and through it the
whole robot: its proxy, peer, class loader and static data.

This hits every robot that writes a file, so a long run runs out of memory.
Three RoboRumble clients on Java 25, with the -Xmx512M of roborumble.bat, ran
out of memory after 59, 86 and 362 battles. The battle thread died and the
clients hung. A heap dump showed 164 RobotPeers, of which 162 were only held
through this list. Version 1.11.2 does the same. Java 8 and 21 do not, as
their SecurityManager removes the thread.

With this change, a client on Java 25 ran 333 battles without running out of
memory, and held only the 2 robots of the running battle when checked after
120 battles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…TA, and wait if it still fails

closeOpenStreams only knows the files that robots open for writing through
Robocode. A file a robot opened for reading and did not close stays open until
the stream is garbage collected, and until then it cannot be deleted on Windows,
so the client skips battles. Skipping a battle allocates next to nothing, so a
collection can be a long time coming. With a test robot that does this, in a
3-robot list on Windows, clients skipped 69% of their battles on Java 25 and 82%
on Java 21 over two minutes. No real robot did it in about 26,000 battles, but
only about 3,200 of those were on Windows.

So when the data cannot be deleted, collect the garbage and try again. The same
clients then fought 20 of 20 battles on each of Java 8, 21 and 25.

If that fails too, e.g. as another program holds the file, the client had nothing
to slow it down. It went through its battles at full speed and asked the server
for the ratings on each iteration: 348 iterations, 17,362 skipped battles and 4 MB
of log in a minute with ITERATE=YES. Waiting a minute before going on brought this
down to 1 iteration and 2 skipped battles in 130 seconds. Each battle tries again,
so the client fights as soon as the data can be deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 18:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Interrupted pauses do not stop the runner, so the restored interrupt flag bypasses subsequent backoff and permits a tight skip/download loop with undeletable data and ITERATE=YES.

1 open issue
What changed in this PR

This PR addresses robot retention on Java 24+ and prevents persistent CLEARDATA deletion failures from making RoboRumble repeatedly skip battles at full speed.

Changes:

  • Removes robot threads from file-access tracking in finally blocks.
  • Retries data deletion after garbage collection and waits one minute if deletion still fails.
  • Adds regression tests and updates the release notes.
File Description
versions.md Documents the memory fix and revised CLEARDATA behavior.
robocode.roborumble/​src/​test/​java/​net/​sf/​robocode/​roborumble/​battlesengine/​BattlesRunnerTest.java Adds deletion-retry and persistent-failure tests.
robocode.roborumble/​src/​main/​java/​net/​sf/​robocode/​roborumble/​battlesengine/​BattlesRunner.java Adds garbage-collection retry and failure backoff.
robocode.host/​src/​test/​java/​net/​sf/​robocode/​host/​security/​ThreadManagerTest.java Tests that opening a robot file does not retain its thread.
robocode.host/​src/​main/​java/​net/​sf/​robocode/​host/​security/​ThreadManager.java Ensures thread tracking is cleared after directory creation and file opening.

Review effort: Balanced

@flemming-n-larsen
flemming-n-larsen merged commit 29a19f0 into robo-code:main Nov 1, 2026
1 check passed
@flemming-n-larsen

Copy link
Copy Markdown
Member

Thanks, these are great finds, and both got past my follow-up. Merged. I reran the full suite on Java 8 and 25 and it passes, and ThreadManagerTest fails without the fix. I left Copilot's comment about interrupted pauses, as nothing in the client interrupts its main thread.

And no problem about the name. I'd read that too broadly. When the server is up, we'll add the link to AutoRumble without that kind of credit in the README.

@flemming-n-larsen

Copy link
Copy Markdown
Member

Heads up: we're moving the tests from JUnit 4 to JUnit 6 (#121 and #122). New tests need the Jupiter API, and any open branch with JUnit 4 tests will need updating, such as your batch upload code if it has tests. Assertions take the message last, and @Test(expected = ...) is now assertThrows. The tests now compile and run on Java 17 and newer, while Robocode itself is still built for Java 8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants