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
- Guard writeCache against null cache and drop the buffer quietly after close.
- Unregister the inbound handler / remove the channel from active set before close().
- Ensure close() and the inbound write path are not concurrent; use netty's closeFuture to stop writes.
- 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
- Unregister inbound handlers before close().
- Do not reuse a NettySocketChannel after close().
- Release the ByteBuf when the channel is closed to avoid leaks.
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
- Socket already closed.
- write failed ! please checking !
- socket has Interrupted !
- end of stream when reading header
- EOF encountered.
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/5399ea28f2ccaa4f.
Report an issue: GitHub.