shashank created CAMEL-26048:
--------------------------------
Summary: camel-mina - every route restart leaks the acceptor of
the stopped consumer (a non-daemon thread and its selectors)
Key: CAMEL-26048
URL: https://issues.apache.org/jira/browse/CAMEL-26048
Project: Camel
Issue Type: Bug
Components: camel-mina
Reporter: shashank
Assignee: shashank
{{MinaConsumer}} builds its acceptor (or, in client mode, its connector) and
its worker pool in the constructor. {{doStop}} only unbinds the address (client
mode: disposes the connector); {{acceptor.dispose(true)}} and
{{workerPool.shutdown()}} are only in {{doShutdown}} (main 0c1e85c8631c). A
route that is stopped and started again does not shut its consumer down:
{{RouteService}} stops it and the next start creates a new consumer, so the old
consumer's {{doShutdown}} never runs. Each route restart leaks the stopped
acceptor: tcp a {{NioSocketAcceptor}} with its acceptor thread (not a daemon
thread) and one selector per I/O processor ({{availableProcessors() + 1}}) plus
the worker pool; udp a {{NioDatagramAcceptor}} and its worker pool; vm a
{{VmPipeAcceptor}} and its idle status checker thread (not a daemon thread).
Measured on a 10-core machine, 5 restarts of a tcp consumer: 5 more
{{NioSocketAcceptor-N}} threads and about 210 more open file descriptors, never
released; the non-daemon threads also keep the JVM running after the
CamelContext is stopped. Triggers: route stop/start (JMX, control bus, dev
console, RouteController), route policies that stop and start their route, and
the start retries of the {{SupervisingRouteController}} (each failed attempt
creates a new consumer). CAMEL-9738 added the dispose to {{doShutdown}}, which
this path never reaches. Removing a route (route reload, route templates and
kamelets created again) shuts the current consumer down, so there only
consumers left over from earlier restarts leak.
*Fix*: {{doStop}} disposes the acceptor after the unbind and shuts the worker
pool down, also in client mode. It waits for the I/O threads
({{dispose(true)}}) only when the CamelContext is stopping, so
{{CamelContext.stop()}} still waits for them as before; a route stop does not
wait (the address is already unbound). Either way the acceptor is marked
disposed, so the {{dispose(true)}} of {{doShutdown}} then does nothing (javap
{{AbstractIoService.dispose}}, mina-core 2.2.9). The protocol setup runs again
in {{doStart}} when the consumer was stopped, so a consumer that is stopped and
started again (route or CamelContext suspend/resume, a route policy that stops
the consumer itself) gets a new acceptor or connector; before, it bound the old
acceptor again, and in client mode the start failed with
{{IllegalStateException: The connector is being disposed.}}
*Tests*: new {{MinaConsumerRestartTest}}, 6 tests (tcp, udp and vm route
restarts: every stopped acceptor disposed, the tcp and udp worker pools shut
down, the restarted route receives; same-instance stop/start, also in client
mode; a route stop calls {{dispose(false)}} and a CamelContext stop
{{dispose(true)}}), all fail on main, for example {{the acceptor of a stopped
consumer must be disposed ==> expected: <true> but was: <false>}}, and in
client mode {{IllegalStateException: The connector is being disposed.}}. Unit
tests of camel-mina with the fix: 122 run, 0 failures, 4 skipped.
*How it was found*: a per-class thread/fd leak check over module test suites
and a route restart probe (r75).
*Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x
(same consumer). Read, not run.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)