shashank created CAMEL-25680:
--------------------------------
Summary: camel-docker - the events and stats consumers never close
their stream (a restarted route gets every event twice), and the events
consumer stays started without a stream after the Docker daemon restarts
Key: CAMEL-25680
URL: https://issues.apache.org/jira/browse/CAMEL-25680
Project: Camel
Issue Type: Bug
Components: camel-docker
Reporter: shashank
Assignee: shashank
The {{docker:events}} and {{docker:stats}} consumers start a streaming command
with {{exec(callback)}}. docker-java reads the stream on its own thread and the
stream belongs to the callback ({{ResultCallbackTemplate.onStart(Closeable)}}
keeps it, {{close()}} closes it).
h3. 1. The stream is never closed
{{DockerEventsConsumer.doStop()}} and {{DockerStatsConsumer.doStop()}} (main
cd2ce899764e, line 80 and 70) call {{close()}} on the command only. In
docker-java 3.7.1 {{AbstrDockerCmd.close()}} does nothing (read with javap: the
method body is {{return}}), and the callback is not kept anywhere. So after a
stop:
* the HTTP connection of the stream stays open, one per stop (a leak on every
route stop or context reload);
* the old callback keeps calling {{getAsyncProcessor().process(...)}} for every
event it receives. When the route is started again, the new consumer opens a
second stream, and every Docker event is routed twice; after n restarts, n + 1
times.
Trigger: default options, any {{stopRoute}} / {{startRoute}} of a route that
consumes from {{docker:events}} (route controller, a supervising route
controller restarting a route, a route reload).
h3. 2. A stream that fails or ends is not opened again
{{DockerEventsConsumer.EventsCallback}} (85-99) implements only {{onNext}}.
When the stream fails (the daemon restarts, the connection is lost)
docker-java's {{ResultCallbackTemplate.onError}} logs one ERROR ("Error during
callback") and closes it; when the daemon ends the stream ({{onComplete}})
nothing is logged. The consumer stays started and never receives an event
again, until the route is restarted. The same happens when the daemon cannot be
reached when the route starts.
h3. Reproduction
New {{DockerEventsConsumerStreamTest}} (mocked {{DockerClient}} that captures
the callbacks; the test calls {{onStart}} / {{onNext}} / {{onError}} /
{{onComplete}} itself). On main:
* stop and start the route, then the daemon sends one event to each open
stream: {{mock://result Received message count. Expected: <1> but was: <2>}};
* the stream opens only after the route was stopped: {{the stream opened after
the stop was not closed ==> expected: <1> but was: <0>}};
* {{docker:stats}}: {{stopping the route did not close the stats stream ==>
expected: <1> but was: <0>}};
* {{onError(IOException)}} on the open stream, and {{onComplete()}}: no new
{{eventsCmd().exec(...)}} within 10 seconds ({{ConditionTimeout}}).
h3. Proposed fix
* Both consumers keep their callback, close it on stop (the callback also
closes a stream that only opens after the stop, as {{onStart}} reopens a closed
template), and a closed callback routes nothing.
* The events consumer handles {{onError}} and {{onComplete}}: it subscribes
again after 1 second, then with a delay that doubles up to 30 seconds while
subscribing fails (the same delays as the kubernetes consumers'
{{KubernetesHelper.watchAgain}}). A failed open stream is logged once with its
stack trace (WARN), a failed attempt to subscribe again as a WARN line without
stack trace, a clean end as a WARN line. The new stream starts with the events
from then on: events of the time without a stream are not received (as with the
kubernetes watches). The delay runs on a scheduled executor of the consumer,
shut down on stop.
Upgrade guide entry (4.24): the events consumer subscribes again when its
stream fails or ends; stopping a consumer closes its stream.
camel-docker unit tests with the fix: 54 run, 0 failures, 4 skipped (the
integration tests need a daemon). Not run: a real Docker daemon (the module's
integration tests), a daemon restart.
How it was found: a sweep of the reconnect code of the components (onError /
onComplete missing); while writing the test, the no-op {{close()}} of the
command.
Not changed (follow-ups): the stats stream also ends when the container stops
and is not opened again (subscribing again to a stopped container would fail
every time, so it needs its own rule);
{{withSince(String.valueOf(System.currentTimeMillis()))}} passes milliseconds
where the Docker API expects seconds, so the {{CamelDockerInitialRange}} option
likely never replays past events (read, not checked against a daemon).
Affected: main (cd2ce899764e; tested). camel-4.23.x, camel-4.22.x, camel-4.18.x
and camel-4.14.x have the same {{doStop}} (only {{eventsCmd.close()}}) and an
{{EventsCallback}} with only {{onNext}} (read, not run).
Duplicate check (2026-10-15): JIRA component camel-docker with text "events"
(CAMEL-7834 creation 2014, CAMEL-9671 API 1.22, CAMEL-10304 OSGi, CAMEL-10307
upgrade), text "DockerEventsConsumer" (nothing); GitHub pull requests
"DockerEventsConsumer", "DockerStatsConsumer", "camel-docker events" (nothing
related).
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)