Johan Almqvist created CAMEL-25896:
--------------------------------------

             Summary: camel-ftp - Let sendNoop=false also skip the consumer's 
pre-poll noop (SFTP sends [email protected] on every poll)
                 Key: CAMEL-25896
                 URL: https://issues.apache.org/jira/browse/CAMEL-25896
             Project: Camel
          Issue Type: Improvement
          Components: camel-ftp, camel-mina-sftp, camel-sftp
    Affects Versions: 4.23.0, 4.18.5
            Reporter: Johan Almqvist


h3. Problem

Before every poll, {{RemoteFileConsumer.connectIfNecessary()}} calls 
{{getOperations().sendNoop()}}. For the JSch-based sftp component, 
{{SftpOperations.sendNoop()}} calls {{Session.sendKeepAliveMsg()}}, which sends 
a {{[email protected]}} global request. The {{sendNoop}} option (CAMEL-8807) 
is read only by {{RemoteFileProducer}}, so a consumer cannot turn this off. The 
code is the same in 4.18.5, in 4.23.0 and on main, where {{RemoteFileConsumer}} 
now lives in camel-ftp-common.

ProFTPD's mod_sftp does not support that request. It replies with a failure and 
writes one SFTPLog line for each one:

{noformat}
<timestamp> mod_sftp/<version>[<pid>]: client sent GLOBAL_REQUEST for 
'[email protected]', denying
{noformat}

With the default {{delay=500}}, that is about two lines a second, roughly 
170,000 a day, for each route polling the server. The volume grows with every 
route, and it buries authentication failures and other real errors in the same 
log. mod_sftp writes the line for every global request it does not handle (the 
same code in 1.3.7 to 1.3.9, 1.3.10rc4 and master), so the server cannot 
silence it without turning SFTPLog off entirely.

h3. For the JSch-based sftp component the keepalive does not change the outcome

* {{Session.sendKeepAliveMsg()}} writes the request and returns without waiting 
for the reply, so it cannot tell a live server from a dead one.
* {{SftpOperations.sendNoop()}} only sends it when {{isConnected()}} is true. 
Otherwise it returns false without sending anything.
* If the write throws, {{sendNoop()}} returns false and 
{{connectIfNecessary()}} calls {{connect()}}. That returns at once while 
{{isConnected()}} is still true, so nothing reconnects at that point. If JSch 
has already marked the session disconnected, {{isConnected()}} alone triggers 
the same reconnect.
* When the server closes the socket (the CAMEL-6591 case), JSch's reader thread 
marks the session disconnected, so {{isConnected()}} already returns false and 
{{sendNoop()}} sends nothing.
* A session that dies without the client noticing is recovered as it is today: 
the poll fails, {{RemoteFilePollingConsumerPollStrategy.rollback()}} 
force-disconnects, and the next poll reconnects.

So for an sftp consumer the result is the same as calling {{isConnected()}}, 
plus one packet per poll.

h3. Proposal

Let {{sendNoop}} also apply to the consumer's pre-poll check. With 
{{sendNoop=false}}, the consumer relies on {{isConnected()}}:

{code:java}
protected void connectIfNecessary() {
    boolean isConnected = false;
    try {
        if (getEndpoint().getConfiguration().isSendNoop()) {
            isConnected = getOperations().sendNoop();
        } else {
            isConnected = getOperations().isConnected();
        }
    } catch (Exception ex) {
        // unchanged
    }
    // unchanged from here
}
{code}

* The default stays {{true}}. The only users affected are those who already set 
{{sendNoop=false}} on a consumer URI, where it is ignored today (for example a 
URI shared between {{from()}} and {{to()}}). That is worth a line in the 
upgrade guide.
* {{RemoteFileConsumer}} is shared, so this also applies to ftp, ftps and 
mina-sftp. There {{sendNoop()}} is a real round trip (FTP {{NOOP}}, MINA 
{{stat("/")}}). With {{sendNoop=false}}, a connection the server has closed is 
found only when the poll fails, and the poll strategy then reconnects on the 
next poll. That is the same trade-off {{sendNoop=false}} already makes for 
producers.
* The {{@UriParam}} label changes from {{producer,advanced}} to 
{{common,advanced}} (as {{fastExistsCheck}} uses). The description and the 
{{setSendNoop()}} Javadoc cover the pre-poll check as well as the pre-write 
check. Regenerating updates the component JSON, the catalog and the endpoint 
DSL.
* Tests: a consumer counterpart of the existing producer {{sendNoop=false}} 
test for ftp and sftp, and a unit test that with {{sendNoop=false}} the 
consumer calls {{isConnected()}} instead of {{sendNoop()}} and still reconnects 
when it returns false.

h3. Alternatives considered

* Going back to {{sendIgnore()}}: CAMEL-10149 moved away from it because 
ProFTPD closed the connection on {{SSH_MSG_IGNORE}}.
* {{disconnect=true}}: no keepalive, but a full SSH handshake and login on 
every poll.
* camel-mina-sftp: its {{sendNoop()}} is an SFTP {{stat("/")}} with no global 
request, but switching components is not always an option.
* Changing {{SftpOperations.sendNoop()}} for everyone, either to return 
{{isConnected()}} or to make a real round trip. That would change the default 
for every SFTP user, so this issue proposes an opt-in that matches CAMEL-8807, 
but either would also solve it.

h3. Workaround today

The only way to stop the keepalive today is to subclass {{SftpOperations}}, 
{{SftpEndpoint}} and {{SftpComponent}} and register the subclass as {{sftp}}.



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

Reply via email to