Federico Mariani created CAMEL-24120:
----------------------------------------

             Summary: camel-smb: consumer downloads every file twice; 
streamDownload=true leaks a remote file handle per file (SmbFile.getBody 
bypasses the retrieved body)
                 Key: CAMEL-24120
                 URL: https://issues.apache.org/jira/browse/CAMEL-24120
             Project: Camel
          Issue Type: Bug
          Components: camel-smb
    Affects Versions: 4.21.0
            Reporter: Federico Mariani


h3. Problem 1 - every consumed file is downloaded twice
{{SmbConsumer}} runs the standard retrieve pipeline: 
{{GenericFileConsumer.processExchange}} -> {{SmbOperations.retrieveFile}} -> 
{{retrieveFileToStreamInBody}} reads the *entire file* into a {{byte[]}} and 
stores it on the {{GenericFile}} via {{target.setBody(body)}} 
(SmbOperations.java:301-330). But the message body is resolved lazily through 
{{GenericFileMessage.createBody()}} -> {{file.getBody()}}, and {{SmbFile}} 
overrides {{getBody()}} (SmbFile.java:89-99) to bypass the stored body entirely:
{code:java}
@Override
public Object getBody() {
    if (!download) {
        return null;
    }
    if (streamDownload) {
        return operations.getBodyAsInputStream(exchange, 
this.getAbsoluteFilePath());
    } else {
        return operations.getBody(this.getAbsoluteFilePath());   // fresh full 
download
    }
}
{code}
The bytes fetched by {{retrieveFile}} are never used - the first body access 
issues a *second full download*. Consequences with the default configuration:
* 2x network transfer and 2x server reads for every consumed file.
* {{localWorkDirectory}} is completely defeated: 
{{retrieveFileToFileInLocalWorkDirectory}} spools the file to local disk (whose 
whole point is keeping big files out of memory), then {{getBody()}} 
re-downloads the whole file *into memory* anyway; the local file is never read.
* The bytes the route sees come from a later read than the one the {{changed}} 
read lock validated.

h3. Problem 2 - streamDownload=true leaks one remote file handle per file
smbj 0.14.0's {{FileInputStream.close()}} only sets {{isClosed = true}} and 
nulls its fields - verified in the bytecode, it does *not* close the underlying 
{{File}} (the remote SMB handle), and neither does reading to EOF. 
{{SmbOperations.getBodyAsInputStream}} (SmbOperations.java:626-641) 
deliberately keeps the {{File}} open ({{// NOSONAR}}) but retains no reference 
to it, and {{releaseRetrievedFileResources}} (SmbOperations.java:416-425) only 
closes the *stream* from the {{CamelSmbFileInputStream}} header. Net effect: 
every file consumed with {{streamDownload=true}} leaks one open SMB file handle 
on the session until the consumer disconnects or stops ({{disconnect=false}} is 
the default, so for a long-running consumer that is effectively forever). 
Handles grow without bound; consumed-and-deleted files stay in delete-pending 
state (removal deferred until the last - leaked - handle closes); other clients 
can hit sharing violations on files Camel has already finished with.

Additionally, the stream created during the retrieve step is dead on arrival: 
{{retrieveFileToStreamInBody}} opens the smbj {{File}} in try-with-resources, 
stores {{getInputStream()}} as body and in the header, then closes the {{File}} 
when the block exits - any read of that stream fails with STATUS_FILE_CLOSED. 
This is currently masked only by Problem 1 (the body access re-opens the file 
and overwrites the header).

h3. History
The lazy {{getBody()}} fetch dates from the CAMEL-21277 WrappedFile rework 
(4.9.0), when the consumer did not yet run the retrieve pipeline. CAMEL-21352 
then added the {{download}}/{{streamDownload}}/{{localWorkDirectory}} retrieve 
machinery on top without removing the lazy override, producing both the 
duplicate download and the second (leaked) open.

h3. Suggested fix
Both defects have the same root - body handling split across {{retrieveFile}} 
and {{getBody()}} - and should be fixed together:
* {{getBody()}} should return the body captured by {{retrieveFile}} (via the 
binding, like FTP's {{RemoteFile}}) and only fall back to a lazy fetch when 
there is none (e.g. {{poll}}/{{pollEnrich}} via {{GenericFilePollingConsumer}}).
* The streamDownload stream must be wrapped in a {{FilterInputStream}} whose 
{{close()}} also closes the smbj {{File}}, so 
{{releaseRetrievedFileResources}}/route completion actually releases the remote 
handle; and the retrieve step must not close the handle backing a stream it 
hands out.
----
_This issue was researched and filed by Claude Code on behalf of [~fmariani] 
(GitHub: Croway), as part of a deep code review of camel-smb. Verified against 
the generic-file framework call chain and the smbj 0.14.0 bytecode; observing 
the duplicate transfer / leaked handle requires server-side instrumentation, so 
no JUnit reproducer is attached for this one._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to