Add and Remove ChannelPipeline-ChannelHandler for Netty Source Analysis

Posted by jlh3590 on Sun, 08 Dec 2019 03:28:03 +0100

In the previous article, we analyzed and summarized the construction and initialization of ChannelPipeline in Netty. In this article, we will do a specific code analysis on the addition and deletion of ChannelHandler.

1. Addition of ChannelHandler

Below is the official demo source for Netty. You can see that adding a custom channelHandler to ChannelPipeline was performed at the service-side initialization.

            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100)
                    .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline p = ch.pipeline();
                            if (sslCtx != null) {
                                p.addLast(sslCtx.newHandler(ch.alloc()));
                            }
                            // p.addLast(new LoggingHandler(LogLevel.INFO));
                            // towards ChannelPipeline Add Customization in channelHandler
                            p.addLast(serverHandler);
                        }
                    });

We can see that the code above calls ChannelPipeline's addLast method to add channelHandler, so let's analyze the source implementation of the addLast method.

First look at the specific source code implementation of the addLast square method

    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            //judge handler Is it added repeatedly
            checkMultiplicity(handler);

            //Establish ChannelHandlerContext node  filterName Check for duplicate names
            newCtx = newContext(group, filterName(name, handler), handler);

            //Increase in Bidirectional Chain List ChannelHandlerContext
            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventLoop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {//Determine if in the same thread
                callHandlerAddedInEventLoop(newCtx, executor);
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

Analyzing the addLast method code shows that ChannelHandler can be added in four basic steps

1. Verify that ChannelHandler is added repeatedly

Let's look at the implementation of the checkMultiplicity method

    private static void checkMultiplicity(ChannelHandler handler) {
        if (handler instanceof ChannelHandlerAdapter) {
            ChannelHandlerAdapter h = (ChannelHandlerAdapter) handler;
            if (!h.isSharable() && h.added) {//If the handler Not shared and already added
                throw new ChannelPipelineException(
                        h.getClass().getName() +
                        " is not a @Sharable handler, so can't be added or removed multiple times.");
            }
            h.added = true;//After adding,Modify Identity
        }
    }

2. Create a HandlerContext object

We've said before that netty encapsulates a channelhandler into a ChannelHandlerContext object, as shown in the following code
newCtx = newContext(group, filterName(name, handler), handler);
When encapsulating an object, we can give a name to the channelhandler to be added, and the filterName method can determine if the handler has a duplicate name.
    private String filterName(String name, ChannelHandler handler) {
        if (name == null) {
            return generateName(handler);//Return a default name
        }
        checkDuplicateName(name);
        return name;
    }
The checkDuplicateName method traverses nodes in the chain table and throws an exception if a duplicate name is queried
    private void checkDuplicateName(String name) {
        if (context0(name) != null) { //Traverse Nodes,Find if there are duplicates name
            throw new IllegalArgumentException("Duplicate handler name: " + name);
        }
    }
    private AbstractChannelHandlerContext context0(String name) {
        AbstractChannelHandlerContext context = head.next;
        while (context != tail) {
            if (context.name().equals(name)) {
                return context;
            }
            context = context.next;
        }
        return null;
    }

3. Add context to the list of chains

After making a series of judgments, we add the ChannelHandlerContext to the bidirectional list in the pipeline using the addLast0 method

    //Equivalent to in tail Insert a node before the node, that is addLast
    private void addLast0(AbstractChannelHandlerContext newCtx) {
        AbstractChannelHandlerContext prev = tail.prev;//Get it tail The preceding node of a node
        newCtx.prev = prev;//Put the current context The leading node is set to prev
        newCtx.next = tail;//Put the current context Postnode is set to tail
        prev.next = newCtx;//hold prev The node's rear node is set to context
        tail.prev = newCtx;//hold tail The preceding node of the node is set to context
    }

The internal implementation of addLast0 is simple by inserting a node in front of the tail node, that is, placing the ChannelHandlerContext at the end of the chain table.

4. Call the callback method to notify that the addition was successful

This step is called when ChannelHandler is added to Pipeline, notifies ChannelHandler of the success of the addition through the callHandlerAdded() callback method, and executes the handlerAdded() method;

First, determine if the current thread is consistent with the eventloop thread. Inconsistent words are encapsulated as task submission to the eventloop thread, and the callHandlerAdded0 method is executed directly by the same thread. Let's see how it is implemented

    private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
            ctx.callHandlerAdded();//call callHandlerAdded callback
        } catch (Throwable t) {
            boolean removed = false;
            try {
                remove0(ctx);//If an exception occurs, set the ctx delete
                ctx.callHandlerRemoved();//call callHandlerRemoved callback
                removed = true;
            } catch (Throwable t2) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Failed to remove a handler: " + ctx.name(), t2);
                }
            }

            if (removed) {
                fireExceptionCaught(new ChannelPipelineException(
                        ctx.handler().getClass().getName() +
                        ".handlerAdded() has thrown an exception; removed.", t));
            } else {
                fireExceptionCaught(new ChannelPipelineException(
                        ctx.handler().getClass().getName() +
                        ".handlerAdded() has thrown an exception; also failed to remove.", t));
            }
        }
    }
    final void callHandlerAdded() throws Exception {
        // We must call setAddComplete before calling handlerAdded. Otherwise if the handlerAdded method generates
        // any pipeline events ctx.handler() will miss them because the state will not allow it.
        if (setAddComplete()) {//In Add handler Previously, the state was guaranteed to be addable
            handler().handlerAdded(this);
        }
    }

With the code above, we can override the handlerAdded method of ChannelHandler to execute some functional logic that needs to be triggered when ChannelHandler is added to Pipeline.

2. Deletion of ChannelHandler

The removal of ChannelHandler is achieved mainly through the remove method of ChannelPipeline

Let's first look at the implementation of the remove method source code

    @Override
    public final ChannelPipeline remove(ChannelHandler handler) {
        remove(getContextOrDie(handler));//delete handler
        return this;
    }
private AbstractChannelHandlerContext remove(final AbstractChannelHandlerContext ctx) {
        //Head and end nodes cannot be deleted
        assert ctx != head && ctx != tail;

        //Lock, thread safety
        synchronized (this) {
            remove0(ctx);//Delete in Chain List context object

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we remove the context from the pipeline and add a task that will call
            // ChannelHandler.handlerRemoved(...) once the channel is registered.
            //Here the main judgment is to change pipline Corresponding channel Is it registered eventloop upper
            if (!registered) {
                callHandlerCallbackLater(ctx, false);
                return ctx;
            }

            
            EventExecutor executor = ctx.executor();
            if (!executor.inEventLoop()) {//Determine if in the same thread
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
        }
        //Callback method, notification handler Deleted
        callHandlerRemoved0(ctx);
        return ctx;
    }

Deleting the entire process is similar to adding

1. Get the ChannelHandlerContext object

    //According to the incoming handler Get their packages ChannelHandlerContext object
    private AbstractChannelHandlerContext getContextOrDie(ChannelHandler handler) {
        //according to context Method gets this from the list of chains handler Of ChannelHandlerContext object
        AbstractChannelHandlerContext ctx = (AbstractChannelHandlerContext) context(handler);
        if (ctx == null) {
            throw new NoSuchElementException(handler.getClass().getName());
        } else {
            return ctx;
        }
    }
    @Override
    public final ChannelHandlerContext context(ChannelHandler handler) {
        if (handler == null) {
            throw new NullPointerException("handler");
        }

        AbstractChannelHandlerContext ctx = head.next;
        //Traverse the list to get this handler Encapsulated ChannelHandlerContext object
        for (;;) {

            if (ctx == null) {
                return null;
            }

            if (ctx.handler() == handler) {
                return ctx;
            }

            ctx = ctx.next;
        }
    }

2. Determine whether it is the first and last node

First determine whether the deleted node is neither a head node nor a tail node

   //Head and end nodes cannot be deleted
   assert ctx != head && ctx != tail;

3. Perform deletion

Then delete the specified Context node through the remove0 method

    private static void remove0(AbstractChannelHandlerContext ctx) {
        AbstractChannelHandlerContext prev = ctx.prev;//Get the preceding node of the current node
        AbstractChannelHandlerContext next = ctx.next;//Get the back node of the current node
        prev.next = next;//hold prev Set the rear node to next
        next.prev = prev;//hold next Pre-node set to prev
    }

4. Callback method to notify deletion success

It also determines whether the current thread is consistent with the eventloop thread, encapsulating inconsistencies as task submissions to the eventloop thread

 EventExecutor executor = ctx.executor();
            if (!executor.inEventLoop()) {//Determine if in the same thread
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerRemoved0(ctx);
                    }
                });
                return ctx;
            }
 //Callback method, notification handler Deleted
 callHandlerRemoved0(ctx);

Call the callHandlerRemoved() callback method to notify the trigger handlerRemoved that the deletion was successful.

    private void callHandlerRemoved0(final AbstractChannelHandlerContext ctx) {
        // Notify the complete removal.
        try {
            ctx.callHandlerRemoved();//call ctx Callback method in
        } catch (Throwable t) {
            fireExceptionCaught(new ChannelPipelineException(
                    ctx.handler().getClass().getName() + ".handlerRemoved() has thrown an exception.", t));
        }
    }
    final void callHandlerRemoved() throws Exception {
        try {
            // Only call handlerRemoved(...) if we called handlerAdded(...) before.
            if (handlerState == ADD_COMPLETE) {
                handler().handlerRemoved(this);//notice handler Delete success events
            }
        } finally {
            // Mark the handler as removed in any case.
            setRemoved();
        }
    }

3. Summary

From the above, we comb the addition and deletion of the two-way chain table based on ChannelPipeline after the channelHandler is encapsulated as a ChannelHandlerContext object. The whole process is simple and clear.Where there are deficiencies and inaccuracies, it is also expected to point out and culvert

 

Watch the WeChat Public Number for more technical articles.

 

Topics: Java Netty