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)

Reply via email to