alibaba/canal · error · IOException

socket is closed !

Error message

socket is closed !

What it means

Thrown by NettySocketChannel.writeCache when the ByteBuf cache field is null. cache is created in the field initializer and only set to null inside close() (after release()). So this error means a writeCache was attempted on a channel that has been closed, i.e. use-after-close of the netty channel's receive buffer.

Source

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

    private static final int    DEFAULT_MAX_BUFFER_SIZE  = 16 * DEFAULT_INIT_BUFFER_SIZE;                                        // 16MB,默认最大缓存大小
    private Channel             channel                  = null;
    private Object              lock                     = new Object();
    private ByteBuf             cache                    = PooledByteBufAllocator.DEFAULT.directBuffer(DEFAULT_INIT_BUFFER_SIZE); // 缓存大小
    private int                 maxDirectBuffer          = cache.maxCapacity();

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public void writeCache(ByteBuf buf) throws InterruptedException, IOException {
        synchronized (lock) {
            while (true) {
                if (null == cache) {
                    throw new IOException("socket is closed !");
                }

                // source buffer is empty.
                if (!buf.isReadable()) {
                    break;
                }

                // 默认缓存大小不够用时需自动清理或扩充,否则将因缓存空间不足而造成I/O超时假象
                int length = buf.readableBytes();
                int deltaSize = length - cache.writableBytes();
                if (deltaSize > 0) {
                    // 首先避免频繁分配内存(扩容/收缩),其次避免频繁移动内存(清理)
                    if (cache.readerIndex() >= deltaSize) { // 可以清理
                        // 回收已读空间,重置读写指针
                        cache.discardReadBytes();
                        // 恢复自动扩充的过大缓存到默认初始缓存大小,释放空间
                        int oldCapacity = cache.capacity();
                        if (oldCapacity > DEFAULT_MAX_BUFFER_SIZE) { // 尝试收缩

View on GitHub (pinned to 87be50e876)

Solutions

  1. Guard writeCache against null cache and drop the buffer quietly after close.
  2. Unregister the inbound handler / remove the channel from active set before close().
  3. Ensure close() and the inbound write path are not concurrent; use netty's closeFuture to stop writes.
  4. Do not reuse a NettySocketChannel after close(); build a fresh one per connection.

Example fix

// before
public void writeCache(ByteBuf buf) {
    synchronized (lock) {
        if (null == cache) throw new IOException("socket is closed !");
        ...
    }
}

// after
public void writeCache(ByteBuf buf) throws IOException {
    synchronized (lock) {
        if (null == cache) {
            buf.release();
            throw new java.nio.channels.ClosedChannelException();
        }
        ...
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Netty inbound: check channel is still open before writing to cache
if (channel == null || !channel.isActive()) { buf.release(); return; }

Type guard

public static boolean cacheAlive(NettySocketChannel ch) {
    return ch != null && ch.getChannel() != null && ch.getChannel().isActive();
}

Try / catch

try {
    nettyChannel.writeCache(buf);
} catch (java.io.IOException e) {
    if ("socket is closed !".equals(e.getMessage())) {
        // drop buffer, stop inbound processing for this channel
    }
    throw e;
}

Prevention

When it happens

Trigger: The netty inbound handler invoking writeCache(ByteBuf) after close() ran (which nulls cache and the channel), typically when an inbound frame arrives during shutdown or after an exception closed the channel. Concurrent close + inbound write race.

Common situations: Channel close triggered by a pipeline exception while a subsequent inbound buffer is being written to cache; reconnect path that did not unregister the inbound handler before close; netty event for a channel already returned to the pool and closed.

Related errors


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