{"record":{"id":"25d8a9a6ed558924","repo":"apache/seatunnel","slug":"sftp-connection-pool-has-been-closed","errorCode":null,"errorMessage":"SFTP connection pool has been closed.","messagePattern":"SFTP connection pool has been closed\\.","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/system/SFTPConnectionPool.java","lineNumber":59,"sourceCode":"    // have more live connections. It means that when we have more\n    // live connections than this threshold, any unused connection will be\n    // closed.\n    private int maxConnection;\n    private int liveConnectionCount;\n    private HashMap<ConnectionInfo, HashSet<ChannelSftp>> idleConnections =\n            new HashMap<ConnectionInfo, HashSet<ChannelSftp>>();\n    private HashMap<ChannelSftp, ConnectionInfo> con2infoMap =\n            new HashMap<ChannelSftp, ConnectionInfo>();\n    private HashMap<ChannelSftp, Session> con2sessionMap = new HashMap<ChannelSftp, Session>();\n\n    SFTPConnectionPool(int maxConnection, int liveConnectionCount) {\n        this.maxConnection = maxConnection;\n        this.liveConnectionCount = liveConnectionCount;\n    }\n\n    synchronized ChannelSftp getFromPool(ConnectionInfo info) throws IOException {\n        if (con2infoMap == null) {\n            throw new IOException(\"SFTP connection pool has been closed.\");\n        }\n        Set<ChannelSftp> cons = idleConnections.get(info);\n        ChannelSftp channel;\n\n        if (cons != null && cons.size() > 0) {\n            Iterator<ChannelSftp> it = cons.iterator();\n            if (it.hasNext()) {\n                channel = it.next();\n                it.remove();\n                if (cons.isEmpty()) {\n                    idleConnections.remove(info);\n                }\n                return channel;\n            } else {\n                throw new IOException(\"Connection pool error.\");\n            }\n        }\n        return null;","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/system/SFTPConnectionPool.java#L41-L77","documentation":"getFromPool throws this IOException when the pool's internal con2infoMap is null, which only happens after the pool has been shut down (close() nulls the map). Any request for a channel after shutdown is refused because the pool can no longer hand out or track connections.","triggerScenarios":"Calling getFromPool (directly or via channel()/borrowedChannel() on SFTPFileSystem) after SFTPConnectionPool.close()/shutdown has run — e.g. using a cached SFTPFileSystem whose pool was closed, or concurrent use during filesystem close.","commonSituations":"A thread still performing file reads/writes while another thread closes the filesystem; reusing a FileSystem object across job/attempt lifecycles after cleanup; retry logic re-invoking the pool after a shutdown path.","solutions":["Do not use the filesystem/pool after close(); obtain a fresh SFTPFileSystem (and thus a new pool) for subsequent operations","Guard concurrent close vs. use with proper lifecycle coordination — close only after all in-flight operations complete","Check application code for double-close or stale cached filesystem instances"],"exampleFix":"// before\npool.close();\nChannelSftp ch = pool.getFromPool(info); // throws\n// after\nChannelSftp ch = pool.getFromPool(info);\n// ... use channel ...\npool.close();","handlingStrategy":"try-catch","validationCode":"if (!fs.isOpen() /* or track pool lifecycle yourself */) { recreate filesystem before use; }","typeGuard":null,"tryCatchPattern":"try {\n    ChannelSftp ch = pool.getFromPool(info);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"connection pool has been closed\")) {\n        pool = newPool(); // recreate and retry once\n    } else throw e;\n}","preventionTips":["Close the pool only after all tasks using it have completed","Never cache SFTPFileSystem across job lifecycles","Use reference counting or try-with-resources style ownership for the pool"],"tags":["sftp","connection-pool","lifecycle"],"backgroundTag":"connection-pool-closed","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}