6. Pipeline 的生命周期

Pipeline 的生命周期

本文以 Netty 4.2 为背景,分析 ChannelPipeline 从创建、添加 Handler、响应 Channel 注册、传播事件,到移除 Handler 和销毁链表的完整过程。

Pipeline 依附于 Channel 存在。一个 Channel 在构造时就会创建一个 Pipeline,之后 Pipeline 中 Handler 的生命周期又会受到 Channel 注册、注销和关闭流程的推动。因此,本文不会重复展开 Channel 的底层注册和关闭逻辑,只关注它们与 Pipeline 发生连接的位置。Channel 本身的状态变化可以参考《Channel的生命周期》。

1. 先建立整体认识

1.1 Pipeline 没有独立于 Channel 的生命周期

一个 Channel 只持有一个 Pipeline:

text 复制代码
Channel
    -> ChannelPipeline
        -> HeadContext
        -> 业务 ChannelHandlerContext
        -> TailContext

Pipeline 没有独立状态,也没有单独注册到 EventLoop 的操作。它的关键阶段由 Channel 推动:

text 复制代码
Channel 构造
    -> 创建 Pipeline

向 Pipeline 添加 Handler
    -> 创建 Context 并插入链表

Channel 注册
    -> 执行注册前积累的 handlerAdded()
    -> 传播 channelRegistered()

Channel 激活和读写
    -> 在 Pipeline 中传播入站事件和出站操作

Channel 关闭并注销
    -> 传播 channelInactive()
    -> 传播 channelUnregistered()
    -> 删除业务 Context
    -> 执行 handlerRemoved()

这里需要区分两个概念:

  • Pipeline 对象的生命周期:从 Channel 构造开始,最终与 Channel 一起变为不可达对象并由 GC 回收。
  • Pipeline 中 Context 的生命周期:从插入链表开始,到移出链表并完成 handlerRemoved() 为止。

现在我们根据这两条主线来阅读

1.2 Channel、Pipeline、Context 和 Handler 的关系

四者的职责如下:

text 复制代码
Channel
    表示一条网络通道,并持有一个 Pipeline

ChannelPipeline
    维护由 ChannelHandlerContext 组成的双向链表

ChannelHandlerContext
    保存链表位置、所属 Pipeline、Handler、执行器和生命周期状态

ChannelHandler
    实现具体的入站事件或出站操作处理逻辑(我们的逻辑一般写在这里)

Pipeline 链表中真正连接的是 ChannelHandlerContext,不是 Handler 本身。DefaultChannelHandlerContext 通过组合关系持有 Handler:

java 复制代码
final class DefaultChannelHandlerContext extends AbstractChannelHandlerContext {

    private final ChannelHandler handler;

    DefaultChannelHandlerContext(
            DefaultChannelPipeline pipeline,
            EventExecutor executor,
            String name,
            ChannelHandler handler) {
        super(pipeline, executor, name, handler.getClass());
        this.handler = handler;
    }
}

Handler 只负责处理逻辑。它在当前 Pipeline 中的位置、执行线程和生命周期状态属于 Context,。

2. Pipeline 的创建

2.1 AbstractChannel 创建 Pipeline

Pipeline 在 AbstractChannel 的构造方法中创建:

java 复制代码
protected AbstractChannel(Channel parent, ChannelId id) {
    this.parent = parent;
    this.id = id;
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

newChannelPipeline() 默认返回 DefaultChannelPipeline

java 复制代码
protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

因此,Channel 完成对象构造时就已经拥有 Pipeline。此时 Channel 通常还没有注册到 EventLoop,也没有激活,但 Pipeline 已经存在。

2.2 初始化 HeadContext 和 TailContext

DefaultChannelPipeline 构造方法会创建两个固定节点:

java 复制代码
protected DefaultChannelPipeline(Channel channel) {
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise = new VoidChannelPromise(channel, true);

    tail = new TailContext(this);
    head = new HeadContext(this);

    head.next = tail;
    tail.prev = head;
}

初始链表如下:

text 复制代码
head <-> tail

加入业务 Handler 后变为:

text 复制代码
head <-> context1 <-> context2 <-> ... <-> tail

HeadContextTailContext 不只是链表哨兵,也是事件传播的边界节点。

HeadContext 同时实现入站和出站接口:

  • 入站事件从 head 开始向 tail 传播。
  • 出站操作到达 head 后,由 HeadContext 调用 Unsafe,进入 Channel 的底层传输实现。

TailContext 只实现入站接口:

  • 它接收入站方向最终没有被业务 Handler 处理的事件。
  • 出站操作以 tail 为查找起点,但 TailContext 自己不负责执行出站操作。

传播方向可以概括为:

text 复制代码
入站事件:head -> 业务 Context -> tail
出站操作:tail -> 业务 Context -> head -> Unsafe

两个内部节点在构造时直接调用 setAddComplete()不需要等待 handlerAdded() 回调, 所有这个两个节点有点特殊。

3. Handler 如何加入 Pipeline

3.1 添加操作统一进入 internalAdd()

addFirst()addLast()addBefore()addAfter() 最终都进入 internalAdd()。它的主要步骤如下:

text 复制代码
checkMultiplicity(handler)
    -> 检查 Handler 实例是否允许再次添加

filterName(name, handler)
    -> 生成或检查 Context 名称

newContext(group, name, handler)
    -> 创建 DefaultChannelHandlerContext

addFirst0()/addLast0()/addBefore0()/addAfter0()
    -> Context 插入双向链表

根据 Pipeline 注册状态
    -> 立即执行或延迟执行 handlerAdded()

关键源码可以简化为:

java 复制代码
private ChannelPipeline internalAdd(
        EventExecutorGroup group,
        String name,
        ChannelHandler handler,
        String baseName,
        AddStrategy addStrategy) {

    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);
        name = filterName(name, handler);
        newCtx = newContext(group, name, handler);

        // 根据 addStrategy 将 newCtx 插入双向链表
        ...

        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }

    callHandlerAdded0(newCtx);
    return this;
}

3.2 Context 插入链表不等于添加回调已经完成

addLast0() 为例:

java 复制代码
private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

这个方法只修改链表结构,不执行 Handler 的生命周期回调。因此要区分:

text 复制代码
Context 插入链表
    表示它已经成为 Pipeline 的结构节点

执行 handlerAdded()
    表示 Handler 已进入可以参与事件处理的阶段

Pipeline 的结构修改放在同步块中完成。prevnext 使用 volatile 保证引用变化的可见性,但链表修改的原子性不能只依赖 volatile

3.3 Handler 名称、复用和执行器

filterName() 的作用是处理 Context 名称:

  • 调用方指定名称时,检查名称是否重复。
  • 未指定名称时,根据 Handler 类型生成名称。
  • 自动生成的名称可能包含编号,不一定只等于类名。

checkMultiplicity() 主要防止同一个非 @SharableChannelHandlerAdapter 实例被重复添加:

  • 未标记 @Sharable 的实例不能重复加入 Pipeline。
  • @Sharable 表示开发者声明该实例可以被多个 Pipeline 共享。
  • @Sharable 不会自动保证线程安全。Handler 内存在共享可变状态时,仍需自行保证并发安全。

newContext() 会为 Handler 选择执行器:

java 复制代码
private AbstractChannelHandlerContext newContext(
        EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(
            this, childExecutor(group), name, handler);
}
  • 没有显式传入 EventExecutorGroup 时,Context 默认使用 Channel 的 EventLoop。
  • 显式传入 Group 时,Context 使用该 Group 中选出的 EventExecutor

4. Context 的生命周期状态

4.1 handlerState 描述单个 Context

AbstractChannelHandlerContext 使用 handlerState 记录当前 Context 对应 Handler 的生命周期状态:

java 复制代码
private static final int INIT = 0;
private static final int ADD_PENDING = 1;
private static final int ADD_COMPLETE = 2;
private static final int REMOVE_COMPLETE = 3;

private volatile int handlerState = INIT;

这些状态描述的是单个 Context,不是整个 Pipeline:

text 复制代码
INIT
    Context 刚创建,尚未安排 handlerAdded()

ADD_PENDING
    Context 已进入链表,handlerAdded() 正在等待执行

ADD_COMPLETE
    Context 已允许参与事件处理

REMOVE_COMPLETE
    Context 已完成删除状态切换,不再参与正常事件处理

典型变化为:

text 复制代码
INIT -> ADD_PENDING -> ADD_COMPLETE -> REMOVE_COMPLETE

如果 Handler 在 Channel 注册后由 EventLoop 线程直接添加,也可能从 INIT 直接进入 ADD_COMPLETE

4.2 为什么先设置 ADD_COMPLETE 再调用 handlerAdded()

callHandlerAdded() 的顺序如下:

java 复制代码
final void callHandlerAdded() throws Exception {
    if (setAddComplete()) {
        handler().handlerAdded(this);
    }
}

Context 会先切换到 ADD_COMPLETE,再进入用户的 handlerAdded()

原因是 Handler 可以在 handlerAdded() 内触发事件。如果状态仍是 ADD_PENDINGinvokeHandler() 可能判断当前 Handler 尚未准备好,导致事件绕过它。

所以 ADD_COMPLETE 更准确的含义是"Context 已允许参与事件处理",不能机械理解成"handlerAdded() 已经执行返回"。

4.3 删除时为什么只对 ADD_COMPLETE 调用 handlerRemoved()

java 复制代码
final void callHandlerRemoved() throws Exception {
    try {
        if (handlerState == ADD_COMPLETE) {
            handler().handlerRemoved(this);
        }
    } finally {
        setRemoved();
    }
}

Netty 只在已经调用过 handlerAdded() 的前提下调用 handlerRemoved()。无论 handlerRemoved() 是否抛出异常,Context 最后都会进入 REMOVE_COMPLETE

5. 注册前添加与注册后添加

5.1 注册前添加:先插入,再暂存回调

DefaultChannelPipeline 内部维护了一个 registered 字段,用于判断是否可以立即安排 Handler 回调。

Channel 尚未注册时:

text 复制代码
Context 插入链表
    -> setAddPending()
    -> 创建 PendingHandlerAddedTask
    -> 加入 pendingHandlerCallbackHead
    -> 暂不执行 handlerAdded()

这样可以保证执行 handlerAdded() 时,Channel 已经绑定 EventLoop,Context 也能确定回调应在哪个执行器中运行。

5.2 Channel 注册推动待处理回调

Channel 注册成功后,AbstractChannel.AbstractUnsafe.register0() 会连接 Channel 与 Pipeline 的生命周期:

text 复制代码
doRegister() 成功
    -> AbstractChannel.registered = true
    -> pipeline.invokeHandlerAddedIfNeeded()
    -> safeSetSuccess(registerPromise)
    -> pipeline.fireChannelRegistered()
    -> 满足条件时 pipeline.fireChannelActive()

其中处理待执行回调的调用链为:

text 复制代码
DefaultChannelPipeline.invokeHandlerAddedIfNeeded()
    -> callHandlerAddedForAllHandlers()
    -> PendingHandlerAddedTask.execute()
    -> callHandlerAdded0()
    -> AbstractChannelHandlerContext.callHandlerAdded()
    -> setAddComplete()
    -> ChannelHandler.handlerAdded()

invokeHandlerAddedIfNeeded() 只在第一次注册时处理一次待执行任务:

java 复制代码
final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        callHandlerAddedForAllHandlers();
    }
}

callHandlerAddedForAllHandlers() 会先在同步块内取出任务链表并把 Pipeline 的 registered 设置为 true,然后在同步块外执行任务。这样可以避免 Handler 在 handlerAdded() 中再次修改 Pipeline 时长期占用 Pipeline 的锁。

这里有两个同名但层次不同的状态:

  • AbstractChannel.registered:Channel 是否已经注册到 EventLoop 的 I/O 处理器。
  • DefaultChannelPipeline.registered:Pipeline 是否已经进入可以立即安排 Handler 回调的阶段。

在首次注册过程中,Channel 的状态先更新,Pipeline 的状态随后在 callHandlerAddedForAllHandlers() 中更新。

DefaultChannelPipeline.registered 不是 Channel.isRegistered() 的实时镜像。它在首次注册时变为 true 后,不会因为 Channel 临时注销而恢复成 false。它的用途是标记 Pipeline 已经越过"注册前暂存 Handler 回调"的阶段;Channel 当前是否仍注册到 EventLoop,应继续查看 AbstractChannel.registered

5.3 注册后添加:直接执行或提交到对应执行器

Channel 已经注册时:

text 复制代码
当前线程属于 Context 的 EventExecutor
    -> 直接执行 callHandlerAdded0()

当前线程不属于 Context 的 EventExecutor
    -> setAddPending()
    -> executor.execute(...)
    -> 在对应 EventExecutor 中执行 callHandlerAdded0()

callHandlerAddedInEventLoop() 的名字容易造成误解。它不是在当前线程直接调用 handlerAdded(),而是把任务提交到目标 EventExecutor

如果 Context 显式绑定了其他 EventExecutorGroupPendingHandlerAddedTask.execute() 也可能只负责提交任务。register0() 不会同步等待这个任务执行完毕,但同一个有序执行器中的提交顺序会保证 handlerAdded() 先于后续事件回调。

6. ChannelInitializer 如何配置业务 Pipeline

6.1 ChannelInitializer 是一次性初始化 Handler

Bootstrap 通常不会直接一次性添加所有业务 Handler,而是先添加 ChannelInitializer。等 Channel 注册后,再由它调用用户实现的 initChannel() 配置业务 Pipeline。

ChannelInitializer.handlerAdded() 的核心逻辑如下:

java 复制代码
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (ctx.channel().isRegistered()) {
        if (initChannel(ctx)) {
            removeState(ctx);
        }
    }
}

典型调用链为:

text 复制代码
Bootstrap.init(channel)
    -> pipeline.addLast(ChannelInitializer)
    -> 此时 Channel 尚未注册,暂存 handlerAdded()

AbstractUnsafe.register0()
    -> Channel.registered = true
    -> 执行 ChannelInitializer.handlerAdded()
    -> ChannelInitializer.initChannel()
    -> 用户的 initChannel(channel)
    -> 添加业务 Handler
    -> 从 Pipeline 删除 ChannelInitializer
    -> pipeline.fireChannelRegistered()

ChannelInitializer 可以理解为"对每个 Channel 只完成一次初始化任务的 Handler"。它自身标记为 @Sharable,同一个实例可以参与多个 Channel 的初始化;内部的 initMap 用于防止同一个 Channel 重入初始化。

6.2 服务端监听 Channel 的 Pipeline

ServerBootstrap.init() 会先向服务端监听 Channel 的 Pipeline 添加一个 ChannelInitializer

text 复制代码
ServerBootstrap.init(serverChannel)
    -> serverChannel.pipeline().addLast(ChannelInitializer)

ChannelInitializer.initChannel(serverChannel)
    -> 添加 config.handler()
    -> 向 EventLoop 提交任务
    -> 添加 ServerBootstrapAcceptor

config.handler() 属于服务端监听 Channel。ServerBootstrapAcceptor 负责处理 accept() 得到的服务端子 Channel。

6.3 服务端子 Channel 的 Pipeline

当服务端监听 Channel 读取到新连接时,ServerBootstrapAcceptor.channelRead() 执行:

text 复制代码
接收服务端子 Channel
    -> child.pipeline().addLast(childHandler)
    -> 设置 childOptions 和 childAttrs
    -> childGroup.register(child)

这里的 childHandler 通常也是用户配置的 ChannelInitializer。它在服务端子 Channel 注册前加入 Pipeline,注册成功后再执行 handlerAdded()initChannel(),最终添加真正的业务 Handler。

因此需要区分:

  • handler() 配置服务端监听 Channel 的 Handler。
  • childHandler() 配置每个服务端子 Channel 的 Handler。

客户端 Bootstrap.init() 的逻辑更直接:把 config.handler() 添加到客户端 Channel 的 Pipeline,然后注册 Channel。

7. 入站事件和出站操作的传播

7.1 入站事件从 HeadContext 开始

源码为每种入站事件提供具体方法,例如:

text 复制代码
fireChannelRegistered()
fireChannelActive()
fireChannelRead()
fireChannelReadComplete()
fireChannelInactive()
fireChannelUnregistered()

channelRead 为例:

text 复制代码
pipeline.fireChannelRead(msg)
    -> HeadContext.channelRead()
    -> head.fireChannelRead(msg)
    -> findContextInbound(MASK_CHANNEL_READ)
    -> 目标 Handler.channelRead(ctx, msg)
    -> ctx.fireChannelRead(msg)
    -> 继续沿 next 传播
    -> TailContext

Handler 是否继续传播事件由 Handler 自己决定。自定义 Handler 重写入站方法后,如果既不调用 ctx.fireChannelRead(msg),也不消费或释放消息,后续 Handler 将收不到事件,还可能造成引用计数对象泄漏。

7.2 出站操作从 TailContext 开始

出站操作包括:

text 复制代码
bind()
connect()
read()
write()
flush()
close()
deregister()

write 为例:

text 复制代码
channel.write(msg)
    -> pipeline.write(msg)
    -> tail.write(msg)
    -> findContextOutbound(MASK_WRITE)
    -> 目标 Handler.write(ctx, msg, promise)
    -> ctx.write(msg, promise)
    -> 继续沿 prev 传播
    -> HeadContext.write()
    -> Unsafe.write()

源码中不存在通用的 in()out() 方法。各种事件的方法结构相似,但阅读时应选择真实方法跟踪,不能把概念性伪代码当成源码调用。

7.3 executionMask 优化了什么

executionMaskChannelHandlerMask.mask(handlerClass) 计算。一个 int 的不同位表示 Handler 是否需要处理某种事件。

Netty 会按 Handler 类型缓存计算结果,避免在事件传播的高频路径中反复通过反射判断 Handler 是否处理某个方法。

查找 Context 时仍然会沿双向链表逐个检查节点:

java 复制代码
private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.next;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
    return ctx;
}

private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.prev;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
    return ctx;
}

因此,executionMask 不是把链表变成可以直接定位节点的索引。它的优化点是:

  • 跳过不处理当前事件的 Handler 方法调用。
  • 在满足顺序约束时,避免不必要的线程切换。

Pipeline 仍然是标准的责任链结构,只是在节点选择上做了高频路径优化。

7.4 为什么 skipContext() 还要比较 EventExecutor

java 复制代码
private static boolean skipContext(
        AbstractChannelHandlerContext ctx,
        EventExecutor currentExecutor,
        int mask,
        int onlyMask) {

    return (ctx.executionMask & (onlyMask | mask)) == 0 ||
            (ctx.executor() == currentExecutor &&
                    (ctx.executionMask & mask) == 0);
}

可以分成两种情况理解:

  1. 目标 Context 与本次传播方向和事件完全无关,可以直接跳过。
  2. 目标 Context 与当前传播过程使用同一个执行器,并且不处理本次事件,可以继续查找。

如果目标 Context 使用不同的执行器,即使它不处理当前具体事件,也不能总是在当前线程直接跨过。事件可能仍需先提交到目标执行器,再从那里继续传播,以维持同一 Channel 上不同事件之间的执行顺序。

8. 运行期间删除和替换 Handler

8.1 remove() 先修改链表,再安排 handlerRemoved()

DefaultChannelPipeline.remove() 的核心逻辑为:

java 复制代码
private AbstractChannelHandlerContext remove(
        final AbstractChannelHandlerContext ctx) {
    assert ctx != head && ctx != tail;

    synchronized (this) {
        atomicRemoveFromHandlerList(ctx);

        if (!registered) {
            callHandlerCallbackLater(ctx, false);
            return ctx;
        }

        EventExecutor executor = ctx.executor();
        if (!executor.inEventLoop()) {
            executor.execute(() -> callHandlerRemoved0(ctx));
            return ctx;
        }
    }

    callHandlerRemoved0(ctx);
    return ctx;
}

删除也要区分结构变化和生命周期回调:

text 复制代码
atomicRemoveFromHandlerList(ctx)
    -> Context 立即脱离双向链表

callHandlerRemoved0(ctx)
    -> 在 Context 对应的 EventExecutor 中执行 handlerRemoved()

如果 Channel 尚未注册,删除回调也会进入待处理任务链表。假设某个 Handler 在注册前先添加再删除,在默认 EventLoop 或其他有序 EventExecutor 中,注册时会按任务提交顺序执行:

text 复制代码
handlerAdded()
    -> handlerRemoved()

如果显式绑定的是无序执行器,不能再假设两个任务严格串行执行。此时 Context 状态仍会阻止在没有完成添加状态的情况下调用 handlerRemoved(),但回调的具体并发时机需要由所选执行器负责。

8.2 replace() 为什么先添加新 Handler

replace() 会先使用 replace0() 把旧 Context 替换成新 Context,然后安排生命周期回调:

text 复制代码
replace0(oldCtx, newCtx)
    -> 新 Context 接管旧 Context 的链表位置

callHandlerAdded0(newCtx)
    -> 新 Handler 先完成添加回调

callHandlerRemoved0(oldCtx)
    -> 旧 Handler 再完成删除回调

必须先执行新 Handler 的 handlerAdded()。因为旧 Handler 可能在 handlerRemoved() 中继续传播 channelRead()flush(),这些事件到达新 Handler 前,新 Handler 必须已经进入可执行状态。

replace0() 还会让旧 Context 的 prevnext 指向新 Context。这样旧 Handler 中尚未结束的转发操作会进入新 Context,而不是继续使用已经失效的旧链表位置。

8.3 handlerAdded() 异常时的处理

如果 handlerAdded() 抛出异常,callHandlerAdded0() 会尝试:

text 复制代码
从链表删除 Context
    -> 调用 handlerRemoved()
    -> 传播 ChannelPipelineException

因此,Handler 添加失败后通常不会继续残留在 Pipeline 中。

9. Channel 注销如何销毁 Pipeline

9.1 channelUnregistered() 是销毁入口

Channel 关闭和注销的主线为:

text 复制代码
AbstractUnsafe.deregister()
    -> doDeregister()
    -> pipeline.fireChannelInactive()
    -> AbstractChannel.registered = false
    -> pipeline.fireChannelUnregistered()

fireChannelUnregistered()HeadContext 开始,它属于入站事件,会先沿 Pipeline 向 TailContext 传播。

真正判断是否销毁业务 Context 的代码位于 HeadContext.channelUnregistered(),不是 TailContext

java 复制代码
public void channelUnregistered(ChannelHandlerContext ctx) {
    ctx.fireChannelUnregistered();

    if (!channel.isOpen()) {
        destroy();
    }
}

顺序是先传播 channelUnregistered(),再开始销毁。这保证业务 Handler 有机会先收到注销事件,之后才执行 handlerRemoved()

还要区分两种注销:

text 复制代码
Channel 仍然打开,只是暂时注销
    -> 传播 channelUnregistered()
    -> 不执行 destroy()
    -> 后续仍可能重新注册

Channel 已经关闭并注销
    -> 传播 channelUnregistered()
    -> 执行 destroy()
    -> 删除所有业务 Context

9.2 destroyUp() 不负责删除节点

destroy()head.next 开始执行 destroyUp()

java 复制代码
private synchronized void destroy() {
    destroyUp(head.next, false);
}

destroyUp()head 方向走向 tail,但这个阶段不修改链表:

text 复制代码
head.next
    -> 沿 next 遍历业务 Context
    -> 遇到不同 EventExecutor 时提交继续遍历的任务
    -> 到达 tail
    -> 调用 destroyDown(tail.prev, ...)

它的目的是依次进入各 Context 对应的执行器。在默认 EventLoop 或其他有序执行器中,之前已经提交的事件任务会排在销毁任务之前,避免 Handler 还在处理事件时就被提前删除。如果显式绑定无序执行器,则不能依赖这种任务先后关系。

源码中的:

java 复制代码
ctx = ctx.next;

只是让局部变量移动到下一个节点,不会断开当前节点的引用,也不会修改 Pipeline 链表。

9.3 destroyDown() 才真正删除节点

到达 tail 后,destroyDown()tail.prev 开始反向删除:

java 复制代码
private void destroyDown(
        Thread currentThread,
        AbstractChannelHandlerContext ctx,
        boolean inEventLoop) {

    final AbstractChannelHandlerContext head = this.head;
    for (;;) {
        if (ctx == head) {
            break;
        }

        EventExecutor executor = ctx.executor();
        if (inEventLoop || executor.inEventLoop(currentThread)) {
            atomicRemoveFromHandlerList(ctx);
            callHandlerRemoved0(ctx);
        } else {
            AbstractChannelHandlerContext finalCtx = ctx;
            executor.execute(() ->
                    destroyDown(Thread.currentThread(), finalCtx, true));
            break;
        }

        ctx = ctx.prev;
        inEventLoop = false;
    }
}

实际顺序为:

text 复制代码
destroyUp()
    从 head 向 tail 遍历,只处理执行器切换和任务顺序

destroyDown()
    从 tail 向 head 遍历
    -> atomicRemoveFromHandlerList(ctx)
    -> handlerRemoved()

Handler 按从尾到头的顺序删除。每个 Context 的删除回调都在它所属的 EventExecutor 中执行。

这里所说的"销毁 Pipeline",主要是删除业务 Context 并执行 handlerRemoved()headtail 和 Pipeline 对象仍由 Channel 引用,最终随 Channel 一起由 GC 回收。

10. 完整生命周期与回调顺序

把前面的流程串起来,可以得到:

text 复制代码
创建 Channel
    -> 创建 DefaultChannelPipeline
    -> 创建 HeadContext 和 TailContext

Bootstrap 初始化
    -> 添加 ChannelInitializer 或其他 Handler
    -> Context 插入链表
    -> Context 进入 ADD_PENDING

Channel 首次注册
    -> Channel.registered = true
    -> Pipeline.registered = true
    -> handlerAdded()
    -> ChannelInitializer.initChannel()
    -> 添加业务 Handler
    -> 删除 ChannelInitializer
    -> channelRegistered()
    -> 满足条件时 channelActive()

Channel 运行期间
    -> 入站事件从 head 向 tail 传播
    -> 出站操作从 tail 向 head 传播
    -> 可以动态 add/remove/replace Handler

Channel 关闭并注销
    -> channelInactive()
    -> channelUnregistered()
    -> HeadContext 判断 Channel 已关闭
    -> destroyUp()
    -> destroyDown()
    -> 业务 Handler 按 tail 到 head 的顺序执行 handlerRemoved()

对于普通 Handler,可以记住以下基本顺序:

text 复制代码
handlerAdded()
    -> channelRegistered()
    -> channelActive()
    -> channelRead()/write()/其他事件
    -> channelInactive()
    -> channelUnregistered()
    -> handlerRemoved()

这个顺序描述的是正常的首次注册、激活和关闭流程。动态添加、动态删除、重新注册以及绑定其他 EventExecutorGroup 时,具体时机会有所变化,但 Netty 会通过 Context 状态和有序任务提交维持单个 Context 的合法回调顺序。

相关推荐
青青子衿悠悠我心1 小时前
TRAE Work让财务月底对账3小时变5分钟
后端
创安电气研究所2 小时前
用TypeScript给Modbus设备做一层适配器
后端
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十):增加管理员角色组管理
前端·后端·ai编程
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十一):管理员和角色组的关联
前端·后端·ai编程
Zane19943 小时前
@property 到底是怎么把方法伪装成属性的?一文吃透 property、staticmethod、classmethod
后端·python
XWalnut4 小时前
SpringBoot快速入门
java·spring boot·后端
用户8181870627464 小时前
第21章 JDBC 异常全集与连接池诊断
后端
Java内核笔记4 小时前
告别第三方库!Spring Boot 4 原生 API 版本控制全解析:4 种策略 + 实战案例
java·后端
神奇小汤圆4 小时前
把Spring Boot 4的Native Image玩明白了,启动3秒变50毫秒的踩坑全记录
后端