alibaba/canal · error · SocketException

Socket already closed.

Error message

Socket already closed.

What it means

Thrown by BioSocketChannel.write when the output stream field is null, i.e. the channel has already been closed or never had its OutputStream initialized. write() guards on output != null and throws a SocketException otherwise. It indicates a use-after-close bug or writing on a channel whose construction failed.

Source

Thrown at driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java:43

    private InputStream   input;
    private OutputStream  output;
    private final boolean ssl;

    BioSocketChannel(Socket socket) throws IOException{
        this.socket = socket;
        this.input = new BufferedInputStream(socket.getInputStream(), 16384);
        this.output = socket.getOutputStream();
        this.ssl = (socket instanceof SSLSocket);
    }

    public void write(byte[]... buf) throws IOException {
        OutputStream output = this.output;
        if (output != null) {
            for (byte[] bs : buf) {
                output.write(bs);
            }
        } else {
            throw new SocketException("Socket already closed.");
        }
    }

    public byte[] read(int readSize) throws IOException {
        InputStream input = this.input;
        byte[] data = new byte[readSize];
        int remain = readSize;
        if (input == null) {
            throw new SocketException("Socket already closed.");
        }
        while (remain > 0) {
            try {
                int read = input.read(data, readSize - remain, remain);
                if (read > -1) {
                    remain -= read;
                } else {
                    throw new IOException("EOF encountered.");
                }

View on GitHub (pinned to 87be50e876)

Solutions

  1. Guard callers with isConnected()/an open flag before writing and reopen the channel if closed.
  2. Make close() and write() mutually exclusive, or set output=null only under a lock and check under the same lock.
  3. Discard pooled channels after an error rather than reusing them.
  4. Ensure the constructor fully completes (socket connected) before exposing the channel to writer threads.

Example fix

// before
channel.write(payload); // throws if channel was closed

// after
if (!channel.isConnected()) {
    channel = reopen();
}
channel.write(payload);
Defensive patterns

Strategy: validation

Validate before calling

if (channel == null || !channel.isConnected()) { /* reopen or fail */ }

Type guard

public static boolean isWritableChannel(BioSocketChannel ch) {
    return ch != null && ch.isConnected();
}

Try / catch

try {
    channel.write(payload);
} catch (java.net.SocketException e) {
    if ("Socket already closed.".equals(e.getMessage())) { /* reopen + retry once */ }
    throw e;
}

Prevention

When it happens

Trigger: Calling write(...) on a BioSocketChannel after close() has run, or after the underlying Socket threw during getOutputStream in the constructor. Concurrent threads where one closes the channel while another writes.

Common situations: Connection-pool reuse of a channel that was evicted/closed; reconnect logic that forgets to null-check before retrying a write; a heartbeat thread writing after the parser thread closed the socket on error.

Related errors


AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14). Data as JSON: /api/errors/7ae4df4bced1a951. Report an issue: GitHub.