Summary
org.exist.remoteconsole.RemoteConsoleEndpoint (monex 4.x) leaks exactly one
non-daemon JVM thread per WebSocket connection to /exist/rconsole. On a
production eXist 6.4.0 system where an application opens an rconsole socket on
every page load, this exhausted the JVM's native thread limit after ~4 days
(946 of 1003 JVM threads were leaked global.rconsole threads), crashing the
server.
Root cause
@ServerEndpoint classes are instantiated once per connection (JSR-356
default lifecycle). The constructor creates a single-thread
ScheduledExecutorService whose thread (named global.rconsole, non-daemon)
pings all sessions every 500 ms:
public RemoteConsoleEndpoint() {
ConsoleModule.setAdapter(new RemoteConsoleAdapter(this::sendAll, this::sendAll));
final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(
runnable -> ThreadUtils.newGlobalThread("rconsole", runnable));
service.scheduleAtFixedRate(this::pingAll, 500, 500, TimeUnit.MILLISECONDS);
}
@OnClose removes the session from the static map but never calls
service.shutdown(), so the executor thread survives forever. One leaked
thread per open/close cycle; the reference is not even kept, so it can never
be shut down later.
Reproduction
Against a stock eXist 6.4.0 + monex 4.2.x install, open and cleanly close N
WebSocket connections to /exist/rconsole, then count threads:
sort /proc/<exist-pid>/task/*/comm | uniq -c | grep rconsole
50 open/close cycles → exactly 50 global.rconsole threads (verified
2026-07-08 on eXist 6.4.0 / monex 4.2.2 and 4.2.4).
Fix (verified)
Store the executor as an instance field and shut it down on close:
private final ScheduledExecutorService service;
// in constructor: this.service = ...
@OnClose
public void closeSession(final Session session, final CloseReason closeReason) {
sessions.remove(session);
service.shutdownNow();
}
With this patch, 500 open/close cycles leak 0 threads and
console:send() channel delivery still works.
Why an issue and not a PR
We understand #367 (merged) removed monex's Java for the 5.x line (eXist
7.0+), so the 4.x branch appears frozen. Filing this for disclosure so other
eXist 6.x users hitting mysterious global.rconsole thread growth can find
it — happy to turn the patch above into a PR if a 4.x maintenance release is
ever considered.
A working patched branch (off v4.2.4) is available here for reference:
https://github.com/luckydem/monex/tree/epod/4.2.4-rconsole-thread-fix
Summary
org.exist.remoteconsole.RemoteConsoleEndpoint(monex 4.x) leaks exactly onenon-daemon JVM thread per WebSocket connection to
/exist/rconsole. On aproduction eXist 6.4.0 system where an application opens an rconsole socket on
every page load, this exhausted the JVM's native thread limit after ~4 days
(946 of 1003 JVM threads were leaked
global.rconsolethreads), crashing theserver.
Root cause
@ServerEndpointclasses are instantiated once per connection (JSR-356default lifecycle). The constructor creates a single-thread
ScheduledExecutorServicewhose thread (namedglobal.rconsole, non-daemon)pings all sessions every 500 ms:
@OnCloseremoves the session from the static map but never callsservice.shutdown(), so the executor thread survives forever. One leakedthread per open/close cycle; the reference is not even kept, so it can never
be shut down later.
Reproduction
Against a stock eXist 6.4.0 + monex 4.2.x install, open and cleanly close N
WebSocket connections to
/exist/rconsole, then count threads:50 open/close cycles → exactly 50
global.rconsolethreads (verified2026-07-08 on eXist 6.4.0 / monex 4.2.2 and 4.2.4).
Fix (verified)
Store the executor as an instance field and shut it down on close:
With this patch, 500 open/close cycles leak 0 threads and
console:send()channel delivery still works.Why an issue and not a PR
We understand #367 (merged) removed monex's Java for the 5.x line (eXist
7.0+), so the 4.x branch appears frozen. Filing this for disclosure so other
eXist 6.x users hitting mysterious
global.rconsolethread growth can findit — happy to turn the patch above into a PR if a 4.x maintenance release is
ever considered.
A working patched branch (off
v4.2.4) is available here for reference:https://github.com/luckydem/monex/tree/epod/4.2.4-rconsole-thread-fix