[
https://issues.apache.org/jira/browse/CAMEL-25728?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-25728:
--------------------------------
Fix Version/s: 4.24.0
> camel-file - a done file with a fixed name is deleted by the last file of a
> poll although the poll left files of the directory (maxMessagesPerPoll, not
> started), so they are left until a new done file is written
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25728
> URL: https://issues.apache.org/jira/browse/CAMEL-25728
> Project: Camel
> Issue Type: Bug
> Components: camel-file
> Reporter: shashank
> Assignee: shashank
> Priority: Major
> Fix For: 4.24.0
>
>
> With a done file of a fixed name ({{doneFileName=ready}}), the file consumer
> only picks up the files of a directory while the done file exists, and
> deletes the done file when the last file of the batch
> ({{CamelBatchComplete}}) is committed
> ({{GenericFileOnCompletion.handleDoneFile}}, main 55ed20f14e90 line 189;
> CAMEL-5883). The batch is the poll, not the directory. When the poll does not
> take some files of the directory, the done file is still deleted, and these
> files are left in the directory until a new done file is written, without any
> log:
> * {{maxMessagesPerPoll}}: the poll takes N files; the last of them deletes
> the done file; the other files stay;
> * a file whose processing cannot start (read lock not acquired, begin or
> download failed) is skipped by the poll, and a later file of the same poll
> deletes the done file.
> Trigger: {{file:in?doneFileName=ready&maxMessagesPerPoll=2}} with three files
> and {{ready}}: two files are routed, the third stays in {{in}} until the
> producer writes {{ready}} again. A done file with a fixed name is the
> documented way to say that "all files of this directory are ready" ("Camel
> will automatically delete the done file when it's done consuming the files"),
> and a producer usually writes the next done file only with the next batch.
> The same base classes are used by camel-ftp (ftp, ftps, sftp), camel-smb and
> camel-azure-files.
> A file that was processed and failed is not part of this issue: it is rolled
> back and waits for the next done file (or is retried under the same done file
> when it was the last file of the poll), and {{moveFailed}} or redelivery are
> the tools for it. The fix keeps that behaviour.
> h3. Reproduction
> New test {{FileConsumerDoneFileNameFilesLeftTest}} (11 tests). Without the
> fix four fail:
> * {{maxMessagesPerPoll=2}}, files a, b, c and {{ready}}: {{mock://limit
> Received message count. Expected: <3> but was: <2>}};
> * the same with {{eagerMaxMessagesPerPoll=false}}: {{mock://noteager ...
> Expected: <3> but was: <2>}};
> * the read lock of b not acquired the first time (a custom
> {{exclusiveReadLockStrategy}}): {{mock://notstarted ... Expected: <3> but
> was: <2>}};
> * {{maxMessagesPerPoll=1}}, files a, b, c and {{ready}}, a file x written
> while the first file is routed: {{mock://gate ... Expected: <3> but was:
> <1>}} (x must wait for a new done file, a, b and c must not).
> The other seven pass on main as well and pin the behaviour that must not
> change: exactly {{maxMessagesPerPoll}} files and {{ready}} deleted; the same
> with a file written meanwhile, not consumed; a file failing once waits for
> the next done file; a last file failing once keeps the done file and is
> retried with the file written meanwhile (as on main); a file that always
> fails does not hold the done file, and the next batch (d and {{ready}} again)
> is consumed, also with {{maxMessagesPerPoll}}; a dynamic
> {{$\{file:name\}.done}} unchanged. A test that expects a file not to be
> consumed checks it once the done file is gone (the routes are synchronous),
> so it does not depend on timing.
> A TLA+ model (one directory: files, done file, limit per poll, files failing
> once or always, files not started once, a producer that writes files of a
> next batch at any time and announces them with a new done file, consumer
> restart) checks with the fix: no file consumed unless a done file was created
> after it (except after a rolled back last file, where main does the same),
> every left file eventually consumed, no orphan done file, and every later
> file eventually consumed (no permanent block by a failing file). On main a
> left file is stranded. A previous version of this fix, which kept a failed
> file in the set, blocked the directory behind a file that always fails; the
> model shows that too. Two limits show up as expected (below): a restart while
> the done file is kept, and a producer that rewrites a done file that is still
> there. Not modelled: asynchronous routing, recursive directories, other
> consumers.
> h3. Proposed fix
> The poll remembers the files it did not take (beyond {{maxMessagesPerPoll}},
> or not started), per done file, and the last file of the batch does not
> delete the done file while there are any. The next poll only consumes these
> files, so a file written to the directory meanwhile waits for a new done file
> as on main; the done file is then deleted after its last file as before, or
> by the next poll if the files left are gone. A file that was taken and failed
> is not left (released), so it cannot hold the done file. With exactly
> {{maxMessagesPerPoll}} files nothing is left and nothing changes.
> To know the files beyond the limit, such a consumer checks every file of the
> directory also with {{eagerMaxMessagesPerPoll=true}} (the default), and then
> cuts the list to the same files the eager limit takes (no Exchange for the
> others). Only for a fixed {{doneFileName}} with {{maxMessagesPerPoll}}: each
> poll then costs what {{eagerMaxMessagesPerPoll=false}} costs for the listing
> (20 000 files and {{ready}}: about 0.5 s per poll instead of 0.1 s, as long
> as files are left; with {{recursive=true}} every sub-directory is listed too,
> one more listing each on a remote server). The state is per consumer, bounded
> by the files left, and cleared on stop. After a restart a kept done file
> admits the files present at the first poll, as main does with any done file
> it finds. A producer must not rewrite a done file that is still there to
> announce new files. Dynamic done files ({{$\{file:name\}.done}}),
> {{noop=true}} and {{recursive=true}} (only the done file of the last file's
> directory is deleted per poll) are unchanged.
> Not changed: with asynchronous routing a poll can still run while the last
> file is completing (the same window as on main). A file that can never be
> started (for example behind a stale lock file) keeps the done file until it
> is gone or can be started.
> Affected: main (tested); camel-4.23.x and camel-4.22.x have the same lines
> (169/189), camel-4.18.x and camel-4.14.x (173/193) (read, not run).
> Duplicate check (2026-10-17): JIRA text "done file" with "maxMessagesPerPoll"
> (0), with "rollback"/"retry" (CAMEL-6964, moveFailed only), "doneFileName"
> (16, none about files left); GitHub pull requests "doneFileName": none open
> or related.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)