Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SftpSession.write() is not thread-safe #9796

Closed
artembilan opened this issue Jan 28, 2025 · 0 comments
Closed

SftpSession.write() is not thread-safe #9796

artembilan opened this issue Jan 28, 2025 · 0 comments

Comments

@artembilan
Copy link
Member

Discussed in #9793

Originally posted by vnxvd January 27, 2025
Hello everybody,
I am getting following error message while using Spring Integration Sftp.

WritePendingException: A write operation is already pending; cannot write 6905 bytes, stackTrace: 
org.apache.sshd.common.io.WritePendingException: A write operation is already pending; cannot write 6905 bytes 
at org.apache.sshd.common.channel.ChannelAsyncOutputStream.writeBuffer(ChannelAsyncOutputStream.java:110) 
at org.apache.sshd.sftp.client.impl.DefaultSftpClient.write(DefaultSftpClient.java:308) 
at org.apache.sshd.sftp.client.impl.SftpOutputStreamAsync.internalFlush(SftpOutputStreamAsync.java:360) 
at org.apache.sshd.sftp.client.impl.SftpOutputStreamAsync.flush(SftpOutputStreamAsync.java:293) 
at org.springframework.util.FileCopyUtils.copy(FileCopyUtils.java:112) 
at org.springframework.integration.sftp.session.SftpSession.write(SftpSession.java:140)

This is the code for creating Sftp Session Factory.

DefaultSftpSessionFactory sessionFactory = new DefaultSftpSessionFactory(true);
        sessionFactory.setHost(connection.getHost());
        if (connection.getPort() != null) {
            sessionFactory.setPort(connection.getPort());
        }
        sessionFactory.setUser(connection.getUser());
        if (StringUtils.isNotBlank(connection.getPassword())) {
            sessionFactory.setPassword(connection.getPassword());
        } else if (StringUtils.isNotBlank(connection.getPrivateKey())) {
            sessionFactory.setPrivateKey(new FileSystemResource(connection.getPrivateKey()));
            if (StringUtils.isNotBlank(connection.getPrivateKeyPassphrase())) {
                sessionFactory.setPrivateKeyPassphrase(connection.getPrivateKeyPassphrase());
            }
        }
        sessionFactory.setAllowUnknownKeys(true);
        return sessionFactory;

This is the code for creating Sftp Session and use the session.

SftpSession sftpSession = sessionFactory.getSession();
        if (!sftpSession.exists(targetFile)) {
            try (InputStream is = new FileInputStream(sourceFile)) {
                sftpSession.write(is, targetFile);
            } catch (IOException e) {// handle exception here}
        }

Library versions:
Spring Boot 3.4.0
Spring Integration Sftp 6.4.0
Apache sshd Sftp 2.14.0
As a temporary solution, I am using "synchronized" keyword on the method which using the SftpSession object to fix the error. Please let me know if you have a better idea to fix this error message.
Thanks.

@artembilan artembilan added this to the 6.5.0-M2 milestone Jan 28, 2025
spring-builds pushed a commit that referenced this issue Jan 28, 2025
Fixes: #9796
Issue link: #9796

The `org.apache.sshd.sftp.client.impl.SftpOutputStreamAsync` is shared object for the `DefaultSftpClient`
and it cannot be used concurrently.

The guarded `send()` operation in the `ConcurrentSftpClient` is not enough
since `DefaultSftpClient.write()` is called directly from the `SftpOutputStreamAsync.internalFlush()`.
And this in the end is called from the `SftpSession.write()`

* Fix `DefaultSftpSessionFactory.ConcurrentSftpClient` to override the `write()` method instead.
Guard it with a `Lock` and call `sftpMessage.waitUntilSent();` to ensure that no concurrent access
to the underlying `SftpOutputStreamAsync`.

(cherry picked from commit 91f4fe4)
spring-builds pushed a commit that referenced this issue Jan 28, 2025
Fixes: #9796
Issue link: #9796

The `org.apache.sshd.sftp.client.impl.SftpOutputStreamAsync` is shared object for the `DefaultSftpClient`
and it cannot be used concurrently.

The guarded `send()` operation in the `ConcurrentSftpClient` is not enough
since `DefaultSftpClient.write()` is called directly from the `SftpOutputStreamAsync.internalFlush()`.
And this in the end is called from the `SftpSession.write()`

* Fix `DefaultSftpSessionFactory.ConcurrentSftpClient` to override the `write()` method instead.
Guard it with a `Lock` and call `sftpMessage.waitUntilSent();` to ensure that no concurrent access
to the underlying `SftpOutputStreamAsync`.

(cherry picked from commit 91f4fe4)
artembilan added a commit that referenced this issue Feb 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants