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

Memory Leak in SftpFileSystemProvider #294

Closed
javadaydayup opened this issue Dec 18, 2022 · 6 comments · Fixed by #295
Closed

Memory Leak in SftpFileSystemProvider #294

javadaydayup opened this issue Dec 18, 2022 · 6 comments · Fixed by #295
Assignees
Labels
bug An issue describing a bug in the code
Milestone

Comments

@javadaydayup
Copy link

javadaydayup commented Dec 18, 2022

Version

master

Bug description

The newInputStream and newOutputStream methods of SftpFileSystemProvider call the SftpFileSystem#getClient() method without call the close() method:
image

Actual behavior

I have a thread that uploads a file to the server every 10 seconds, as time goes by, the heap memory will get bigger and bigger, and eventually the heap memory will occur out of memory:
image

When I exported the heap memory, I found that there are a lot of objects of org.apache.sshd.sftp.client.fs.SftpFileSystem$Wrapper
image

Expected behavior

When the program is running normally, the heap memory will not overflow

Relevant log output

No response

Other information

No response

@luchenguang
Copy link

luchenguang commented Dec 19, 2022

Meet the same question when using SftpFileSystem, seems to be caused by SftpFileSystem using a ThreadLocal to cache a sftp client but unable to clean it

tomaswolf added a commit to tomaswolf/mina-sshd that referenced this issue Dec 19, 2022
SftpFileSystem.getClient() returns wrapper instances that need to
be closed to avoid a memory leak via ThreadLocals. Make sure that
the streams returned by SftpFileSystemProvider.newInputStream() and
newOutputStream() do close the client used by ensuring that the
streams returned by SftpFileSystem.read() and write() do so. Also
fix SftpFileSystemProvider.copy() to close the SftpClient it uses.

SftpFileSystemProvider.newDirectoryStream() and newFileChannel()
already do close the SftpClient used.

Fix the SftpFileSystemTest to properly close SftpClients and
DirectoryStreams.

Bug: apache#294
tomaswolf added a commit to tomaswolf/mina-sshd that referenced this issue Dec 19, 2022
SftpFileSystem.getClient() returns reference-counted wrapper
instances that need to be closed to avoid a memory leak via
ThreadLocals. Make sure that the streams returned by
SftpFileSystemProvider.newInputStream() and newOutputStream()
do close the client used by ensuring that the streams returned
by SftpFileSystem.read() and write() do so. Also fix
SftpFileSystemProvider.copy() to close the SftpClient it uses.

SftpFileSystemProvider.newDirectoryStream() and newFileChannel()
already do close the SftpClient used.

Fix the SftpFileSystemTest to properly close SftpClients and
DirectoryStreams.

Bug: apache#294
@tomaswolf tomaswolf added the bug An issue describing a bug in the code label Dec 19, 2022
@tomaswolf tomaswolf added this to the 2.9.3 milestone Dec 19, 2022
@tomaswolf tomaswolf self-assigned this Dec 19, 2022
@tomaswolf
Copy link
Member

Thanks for pointing out this bug. It should be fixed by the referenced pull request.

But personally I'm not convinced using a ThreadLocal to store these wrappers is a good idea. It appears the idea was to use different SftpClients and thus SSH channels for different threads. I cannot be sure though since there is no design documentation and this code was written long before I became involved here. I'm not entirely sure that using ThreadLocals for this works as intended in all cases.

tomaswolf added a commit to tomaswolf/mina-sshd that referenced this issue Dec 20, 2022
SftpFileSystem.getClient() returns reference-counted wrapper
instances that need to be closed to avoid a memory leak via
ThreadLocals. Make sure that the streams returned by
SftpFileSystemProvider.newInputStream() and newOutputStream()
do close the client. Also fix SftpFileSystemProvider.copy() to
close the SftpClient it uses.

SftpFileSystemProvider.newDirectoryStream() and newFileChannel()
already do close the SftpClient used.

Fix the SftpFileSystemTest to properly close SftpClients and
DirectoryStreams.

Bug: apache#294
tomaswolf added a commit to tomaswolf/mina-sshd that referenced this issue Dec 20, 2022
SftpFileSystem.getClient() returns reference-counted wrapper
instances that need to be closed to avoid a memory leak via
ThreadLocals. Make sure that the streams returned by
SftpFileSystemProvider.newInputStream() and newOutputStream()
do close the client. Also fix SftpFileSystemProvider.copy() to
close the SftpClient it uses.

SftpFileSystemProvider.newDirectoryStream() and newFileChannel()
already do close the SftpClient used.

Fix the SftpFileSystemTest to properly close SftpClients and
DirectoryStreams.

Bug: apache#294
@hitYunhongXu
Copy link

hello, how can i get the code without memory leak? it seems that the problem is not fixed in version 2.9.2

@zongbintu
Copy link

zongbintu commented Mar 18, 2023

@tomaswolf
I use ThreadPool operate many files.
sftp version:2.9.3-SNAPSHOT
Using the 2.9.3-SNAPSHOT, still unable to release.

image

@zongbintu
Copy link

zongbintu commented Mar 18, 2023

I switched to SftpClient and it's working normally now.

in ThreadPool use

Bad

    public String downloadFile(String path) {
        try (ClientSession session = client.connect(username, host, port).verify(timeout, TimeUnit.SECONDS).getSession()) {
            session.addPasswordIdentity(password);
            session.auth().verify();
            try (SftpFileSystem fs = SftpClientFactory.instance().createSftpFileSystem(session);
                 ByteArrayOutputStream out = new ByteArrayOutputStream()
            ) {
                Path targetPath = fs.getDefaultDir().resolve(path);
                Files.copy(targetPath, out);
                return out.toString("UTF-8");
            }
        } catch (SftpException e) {
            log.error("download file:{} error:{}.", path, e.getMessage());
        } catch (Exception e) {
            log.error("download file:{} error.", path, e);
        }
        return null;
    }

Good

Version:2.8.0

    public String downloadFile(String path) {
        try (ClientSession session = client.connect(username, host, port).verify(timeout, TimeUnit.SECONDS).getSession()) {
            session.addPasswordIdentity(password);
            session.auth().verify();

            SftpClientFactory factory = SftpClientFactory.instance();

            try (SftpClient sftp = factory.createSftpClient(session);
                 StringWriter writer = new StringWriter();) {
                InputStream inputStream = sftp.read(path);
                IOUtils.copy(inputStream, writer, StandardCharsets.UTF_8);

                String contentStr = writer.toString();

                return contentStr;
            }
        } catch (IOException e) {
            log.error("download file:{} error.", path, e);
        }
        return null;
    }

After downloading, moving, and parsing approximately 40142 files, Each file has an approximate size of 4KB.
image

@tomaswolf
Copy link
Member

I cannot reproduce this. Can you show fully working test code using a thread pool (I presume from an ExecutorService) that downloads a number of files and where at the end, when all downloads are done, there are extra ThreadLocals in the pool threads?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug An issue describing a bug in the code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants