shashank created CAMEL-25100:
--------------------------------
Summary: camel-core - OnCompletion EIP (default after-consumer
mode) cannot read a body spooled to disk by stream caching, as the spool file
is deleted before the onCompletion runs
Key: CAMEL-25100
URL: https://issues.apache.org/jira/browse/CAMEL-25100
Project: Camel
Issue Type: Bug
Components: camel-core
Reporter: shashank
With stream caching spooled to disk ({{spoolEnabled=true}}, a body over the
spool threshold), the body is a {{FileInputStreamCache}}. Its temporary file is
deleted by an on completion that
{{FileInputStreamCache.TempFileManager.addExchange}} registers on the exchange
({{FileInputStreamCache.java:255}}). That on completion is a
{{SynchronizationAdapter}} with the default order 0
({{SynchronizationAdapter.java:49}}).
An {{onCompletion()}} in the default mode ({{modeAfterConsumer}}) also runs as
an on completion of the unit of work
({{OnCompletionSynchronizationAfterConsumer}}), with the order
{{Ordered.LOWEST}} ({{OnCompletionProcessor.java:326}}). {{UnitOfWorkHelper}}
sorts the on completions by their order ({{UnitOfWorkHelper.java:112}}), so the
spool file is always deleted before the onCompletion starts. The onCompletion
then works on a copy of the exchange ({{prepareExchange}},
{{OnCompletionProcessor.java:280}}) whose body is the {{FileInputStreamCache}}
of the deleted file.
Results with a small spool threshold and a streamed body of 16 KB (and, in the
original investigation, 100 KB with a 1 KB threshold, 3 runs each):
* {{onCompletion().convertBodyTo(byte[].class)}}: fails with a
{{NoSuchFileException}} for the spool file, every time.
* {{onCompletion().onFailureOnly()}} on a route that fails: the same.
* {{onCompletion().parallelProcessing()}}: the onCompletion route stops at the
first step that reads the body, and nothing is logged at WARN or above.
* {{onCompletion().modeBeforeConsumer()}}: works, as it runs before the unit of
work is done.
* Without an onCompletion, and for bodies below the threshold (kept in memory),
there is no problem.
Typical onCompletion uses (archive or audit the message, send a notification
with the payload) therefore fail as soon as the payload is big enough to be
spooled.
h3. Proposed fix
* The stream cache clean-up on completion returns {{Ordered.LOWEST}} from
{{getOrder()}}, so it runs after the other on completions of the exchange,
which may still read the body.
* The after-consumer onCompletion uses {{Ordered.LOWEST - 1}}, so it still runs
before the on completions that want to be last (such as the FTP and SMB
consumers' disconnect), and {{prepareExchange}} gives its copy its own
reference to the stream cache: it removes {{CamelStreamCacheUnitOfWork}} from
the copy and replaces the body with {{sc.copy(copy)}}, as the Wire Tap EIP does
({{WireTapProcessor.java:282}}). The copy keeps the file until the onCompletion
route is done, which also covers {{parallelProcessing}}.
* With {{parallelProcessing}}, a task that never runs must release the copy's
reference: when the thread pool rejects the task, discards it because it is
shut down, or drops it when the processor shuts the pool down with
{{shutdownNow}}. Otherwise the file would be kept until the spool directory is
removed when the context stops. Since CAMEL-25012 the processor also counts its
parallel tasks as pending for the graceful shutdown, so a task that never runs
must also stop being counted. That already happened for a rejected task and for
the tasks dropped by {{shutdownNow}}, but not for a task that a thread pool
that is shut down discards without an exception (such as with the
{{CallerRuns}} policy): it stayed counted as pending. Both are now done in one
place: each parallel task is either run or discarded, never both, and a
discarded task is no longer counted as pending and releases its copy.
The first point changes the order of every stream cache clean-up, not only for
routes with an onCompletion: a spooled file is now deleted after the other on
completions of the exchange instead of among the order 0 ones. The file is only
deleted later than before, never earlier, so nothing that worked before can now
see a deleted file. On completions that share the order {{Ordered.LOWEST}} keep
their reverse registration order among themselves. This needs an upgrade guide
note.
With this change the onCompletion variants above read the full body, and no
spool file is left behind afterwards, also when a parallel task is rejected,
discarded, or dropped at shutdown.
Affected: long-standing, all 4.x versions (the order of the two on completions
has not changed).
Duplicate check (2026-09-28): JIRA "onCompletion" with "stream cache", "stream
caching", "spool" or "FileInputStreamCache": CAMEL-2776 (2010, deletion on
close), CAMEL-2636 and unrelated issues. CAMEL-25012 (fixed) is about the
graceful shutdown of the parallel onCompletion, not this, but this change
builds on it. GitHub pull requests: nothing on this.
_Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)