@nayato there is no good reason... let me fix it.
 Fixed by https://github.com/netty/netty/pull/5251

question on DefaultChannelPipeline.executorSafe - why indirection?
Is there a particular reason to access channel-assigned executor like so `channel.unsafe().invoker().executor()` vs `channel.eventLoop()`?

transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
diff --git a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
index 5e56a974069..85696240a55 100644
--- a/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
+++ b/transport/src/main/java/io/netty/channel/DefaultChannelPipeline.java
@@ -1286,7 +1286,7 @@ private EventExecutor executorSafe(ChannelHandlerInvoker invoker) {
             // We check for channel().isRegistered and handlerAdded because even if isRegistered() is false we
             // can safely access the invoker() if handlerAdded is true. This is because in this case the Channel
             // was previously registered and so we can still access the old EventLoop to dispatch things.
-            return channel.isRegistered() || registered ? channel.unsafe().invoker().executor() : null;
+            return channel.isRegistered() || registered ? channel.eventLoop() : null;
         }
         return invoker.executor();
     }
