You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "shashank (Jira)" <ji...@apache.org> on 2026/10/15 11:50:00 UTC
[jira] [Work started] (CAMEL-25680) 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
[ https://issues.apache.org/jira/browse/CAMEL-25680?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Work on CAMEL-25680 started by shashank.
----------------------------------------
> 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
> Priority: Major
>
> 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)