You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@camel.apache.org by "Claus Ibsen (Jira)" <ji...@apache.org> on 2026/10/01 05:48:00 UTC
[jira] [Updated] (CAMEL-25100) 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
[ https://issues.apache.org/jira/browse/CAMEL-25100?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Claus Ibsen updated CAMEL-25100:
--------------------------------
Fix Version/s: 4.23.0
> 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
> Assignee: shashank
> Priority: Minor
> Fix For: 4.23.0
>
>
> 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)