apache/seatunnel · error · IOException

SFTP connection pool has been closed.

Error message

SFTP connection pool has been closed.

What it means

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.

Source

Thrown at seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/system/SFTPConnectionPool.java:59

    // have more live connections. It means that when we have more
    // live connections than this threshold, any unused connection will be
    // closed.
    private int maxConnection;
    private int liveConnectionCount;
    private HashMap<ConnectionInfo, HashSet<ChannelSftp>> idleConnections =
            new HashMap<ConnectionInfo, HashSet<ChannelSftp>>();
    private HashMap<ChannelSftp, ConnectionInfo> con2infoMap =
            new HashMap<ChannelSftp, ConnectionInfo>();
    private HashMap<ChannelSftp, Session> con2sessionMap = new HashMap<ChannelSftp, Session>();

    SFTPConnectionPool(int maxConnection, int liveConnectionCount) {
        this.maxConnection = maxConnection;
        this.liveConnectionCount = liveConnectionCount;
    }

    synchronized ChannelSftp getFromPool(ConnectionInfo info) throws IOException {
        if (con2infoMap == null) {
            throw new IOException("SFTP connection pool has been closed.");
        }
        Set<ChannelSftp> cons = idleConnections.get(info);
        ChannelSftp channel;

        if (cons != null && cons.size() > 0) {
            Iterator<ChannelSftp> it = cons.iterator();
            if (it.hasNext()) {
                channel = it.next();
                it.remove();
                if (cons.isEmpty()) {
                    idleConnections.remove(info);
                }
                return channel;
            } else {
                throw new IOException("Connection pool error.");
            }
        }
        return null;

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Do not use the filesystem/pool after close(); obtain a fresh SFTPFileSystem (and thus a new pool) for subsequent operations
  2. Guard concurrent close vs. use with proper lifecycle coordination — close only after all in-flight operations complete
  3. Check application code for double-close or stale cached filesystem instances

Example fix

// before
pool.close();
ChannelSftp ch = pool.getFromPool(info); // throws
// after
ChannelSftp ch = pool.getFromPool(info);
// ... use channel ...
pool.close();
Defensive patterns

Strategy: try-catch

Validate before calling

if (!fs.isOpen() /* or track pool lifecycle yourself */) { recreate filesystem before use; }

Try / catch

try {
    ChannelSftp ch = pool.getFromPool(info);
} catch (IOException e) {
    if (e.getMessage().contains("connection pool has been closed")) {
        pool = newPool(); // recreate and retry once
    } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/25d8a9a6ed558924. Report an issue: GitHub.