Netty source code analysis 4:NioEventLoopGroup

Posted by mosi on Sat, 05 Mar 2022 04:23:40 +0100

Analysis of NioEventLoopGroup's parent class and interface function of its implementation

  • NioEventLoopGroup maintains a group of nioeventloops internally. The relationship between them is somewhat similar to the relationship between thread pool and thread. Its class diagram is as follows: Iterable and ScheduledExecutorService are provided by jdk,

  • The EventExecutorGroup interface, as the top-level interface for Netty to define the event executor group, mainly defines the method to gracefully close the EventExecutorGroup. Secondly, a next() method is defined to poll the internal EventExecutor.

  • AbstractEventExecutorGroup preliminarily implements the EventExecutorGroup interface. In fact, it internally obtains the EventExecutor by calling the next() method, and then executes the corresponding method of the EventExecutor. It is implemented in a simple decorator mode. Check the EventExecutor interface and find that it inherits from the EventExecutorGroup interface.

  • MultithreadEventExecutorGroup defines a multithreaded event executor, that is, it internally maintains an EventExecutor[] children attribute. When submitting a task to the EventExecutorGroup, in fact, it polls the children's elements through the next() method to obtain an EventExecutor, and finally submits the task through the EventExecutor. In addition, promise <? > The terminationfuture property is used to record the asynchronous results of MultithreadEventExecutorGroup. Promise is a high-level interface of Netty encapsulating Future. It provides methods to set asynchronous results, completion status, success status and abnormal results. In addition, an eventexecutorchooserfactory is defined Eventexecutorchooser chooser property, which provides how to poll children and return an EventExecutor. Usually, children are polled sequentially. For children When the length is the nth power of 2, the method of calculating the array subscript is similar to the sum operation in HashMap. Otherwise, the method of calculating the remainder is used

  • MultithreadEventLoopGroup initially implements event polling, and its register related interfaces are implemented by polling next(). That is, when we call MultithreadEventLoopGroup When the register(...) interface is used, the register(...) interface is actually called by calling the children element through polling, which ensures that the registration operation is evenly distributed to each element of children. A default is also defined_ EVENT_ LOOP_ Threads static variable, which is equal to the number of processors in the current running environment * 2

  • NioEventLoopGroup is the event polling group that implements NIO

    • setIoRatio(int ioRatio): set the proportion of IO time. Because we can submit external tasks to NioEventLoopGroup, NioEventLoopGroup will execute additional external tasks in addition to listening to channel events. In fact, this method traverses the internal children and calls its setIoRatio method. Therefore, it actually sets the proportion for the internal EventExecutor(children). The default proportion is 50, that is, the proportion of IO operations and external task events is the same
    • rebuildSelectors(): this method is used to deal with the bug of jdk multiplexer Selector. In fact, like setIoRatio, it also traverses the children and calls the implementation of rebuildSelectors
    • newChild(): create an EventExecutor, that is, initialize every element in children

new NioEventLoopGroup() instantiation process analysis

    public NioEventLoopGroup() {
        this(0);
    }

    public NioEventLoopGroup(int nThreads) {
        this(nThreads, (Executor) null);
    }
    
    public NioEventLoopGroup(ThreadFactory threadFactory) {
        this(0, threadFactory, SelectorProvider.provider());
    }

    public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) {
        this(nThreads, threadFactory, SelectorProvider.provider());
    }

    public NioEventLoopGroup(int nThreads, Executor executor) {
        this(nThreads, executor, SelectorProvider.provider());
    }

    public NioEventLoopGroup(
            int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) {
        this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
    }

    public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory,
        final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) {
        super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
    }

From the above source code, we can see that the parameterless constructor overloads a series of construction methods, and calls the constructor of the parent class to continue the overload. The default parameters are as follows:

  • int nThreads=0: 0 eventexecutors are created by default. In fact, the default in the parent class is used_ EVENT_ LOOP_ Threads value = number of processor cores * 2
  • Executor executor=null, the default executor is null. In fact, the threadpertask executor is used as the default executor. When submitting a task to NioEventLoopGroup, it is actually submitted to an internal EventExecutor, and an executor is maintained inside the EventExecutor, so the task is finally submitted to the executor.
  • SelectorProvider selectorProvider=SelectorProvider.provider(): the provider of multiplexer Selector, which directly uses jdk
  • Selectstrategyfactory: the selection policy factory is used to create a selection policy. As mentioned earlier, the EventExecutor will also execute externally submitted tasks when listening to channel events. By selecting a policy, you can decide whether to block, listen to channel events or execute external tasks
  • RejectedExecutionHandler reject: reject the execution processor. When the EventExecutor uses a bounded queue to temporarily stage externally submitted tasks, how will it reject external tasks when the queue is full? By default, RejectedExecutionException will be thrown to the outside
  • Finally, the constructor method of the parent MultithreadEventLoopGroup is called as follows:
    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

        if (logger.isDebugEnabled()) {
            logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
        }
    }
    protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

Constructor method in MultithreadEventLoopGroup will use default by DEFAULT_EVENT_LOOP_THREADS as nThreads parameter, DEFAULT_EVENT_LOOP_THREADS = number of constructors * 2; You can also use io netty. Eventloopthreads is configured. Finally, the constructor method of the parent class MultithreadEventExecutorGroup is called as follows:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
	//Use the default actuator to select the factory defaulteventexecutorchooserfactory INSTANCE
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        checkPositive(nThreads, "nThreads");
		//If there is no custom actuator (which is ultimately assigned to the member variable of EventExecutor), threadpertaskeexecutor is used
        if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
		//Instantiate children
        children = new EventExecutor[nThreads];
		//The for loop instantiates each element in children
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
            	//It is implemented by newChild() in the subclass
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                if (!success) {
                    for (int j = 0; j < i; j ++) {
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
                        EventExecutor e = children[j];
                        try {
                            while (!e.isTerminated()) {
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }
		//Instantiate the event poller, that is, the default actuator selection factory above defaulteventexecutorchooserfactory INSTANCE
        chooser = chooserFactory.newChooser(children);
		//Define asynchronous event notification, which will be added to the event executor,
		//The logic is also simple. When the last element of children is successfully initialized, set the instantiation result of the current Group
        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
                if (terminatedChildren.incrementAndGet() == children.length) {
                    terminationFuture.setSuccess(null);
                }
            }
        };
		//Add the above notification to each element in children
        for (EventExecutor e: children) {
            e.terminationFuture().addListener(terminationListener);
        }
		//Build an immutable readonlyChildren for traversal.
        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
}
  • SelectStrategyFactory=DefaultSelectStrategyFactory.INSTANCE: the select policy factory is used to decide whether to perform channel listening or perform additional tasks
public final class DefaultSelectStrategyFactory implements SelectStrategyFactory {
    public static final SelectStrategyFactory INSTANCE = new DefaultSelectStrategyFactory();

    private DefaultSelectStrategyFactory() { }

    @Override
    public SelectStrategy newSelectStrategy() {
        return DefaultSelectStrategy.INSTANCE;
    }
}

final class DefaultSelectStrategy implements SelectStrategy {
    static final SelectStrategy INSTANCE = new DefaultSelectStrategy();

    private DefaultSelectStrategy() { }

	//Calculation strategy: if there is a task currently, it is determined by the return value of selectSupplier; otherwise, the listening channel event is blocked
	//selectSupplier different eventexecutors provide different implementations, but they are non blocking to obtain the number of prepared events on the current channel
	//Including Java nio. Selector. Multiplexer under select now() and select now() under linux
    @Override
    public int calculateStrategy(IntSupplier selectSupplier, boolean hasTasks) throws Exception {
        return hasTasks ? selectSupplier.get() : SelectStrategy.SELECT;
    }
}
  • chooserFactory = DefaultEventExecutorChooserFactory. The instance event actuator polls the factory
public final class DefaultEventExecutorChooserFactory implements EventExecutorChooserFactory {

    public static final DefaultEventExecutorChooserFactory INSTANCE = new DefaultEventExecutorChooserFactory();

    private DefaultEventExecutorChooserFactory() { }

    @Override
    public EventExecutorChooser newChooser(EventExecutor[] executors) {
    	//If nThreads is the nth power of 2, use the poweroftwo eventexecutorchooser selector
    	//Otherwise, use the GenericEventExecutorChooser selector
        if (isPowerOfTwo(executors.length)) {
            return new PowerOfTwoEventExecutorChooser(executors);
        } else {
            return new GenericEventExecutorChooser(executors);
        }
    }

    private static boolean isPowerOfTwo(int val) {
        return (val & -val) == val;
    }

    private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
        private final AtomicInteger idx = new AtomicInteger();
        private final EventExecutor[] executors;

        PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }
		//The n-th power of 2 obtains the EventExecutor of the corresponding index position through the sum operation after each index + 1
        @Override
        public EventExecutor next() {
            return executors[idx.getAndIncrement() & executors.length - 1];
        }
    }

    private static final class GenericEventExecutorChooser implements EventExecutorChooser {
		//AtomicLong instead of AtomicInteger prevents overflow
        private final AtomicLong idx = new AtomicLong();
        private final EventExecutor[] executors;

        GenericEventExecutorChooser(EventExecutor[] executors) {
            this.executors = executors;
        }

		//Calculate the subscript position of the array through the remainder operation
        @Override
        public EventExecutor next() {
            return executors[(int) Math.abs(idx.getAndIncrement() % executors.length)];
        }
    }
}
  • Executor = new threadpertaskeexecutor (newDefaultThreadFactory()): EventExecutorGroup gives the submitted task to EventExecutor, and EventExecutor gives the task to threadpertaskeexecutor. Threadpertaskeexecutor creates a thread to execute the final task through newDefaultThreadFactory()
public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        this.threadFactory = ObjectUtil.checkNotNull(threadFactory, "threadFactory");
    }
	//Threadpertaskeexecutor in turn gives the task to threadfactory The newthread () method
    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}

The source code of the thread created by DefaultThreadFactory is as follows, ignoring part of the source code:

public class DefaultThreadFactory implements ThreadFactory {
	//A DefaultThreadFactory is not created. The poolId is incremented by 1 to create different thread group names
    private static final AtomicInteger poolId = new AtomicInteger();
	//No thread in a thread group is created. This value increases by 1. It is used to create different thread names of the same thread group
    private final AtomicInteger nextId = new AtomicInteger();
    private final String prefix;//Prefix of thread Name: NioEventLoopGroup-1-
    private final boolean daemon;//Whether the thread is a daemon thread. The default is false
    private final int priority;//Thread priority: 1-10, using the default value of 5
    protected final ThreadGroup threadGroup;/Parent thread group, default null
    
    public DefaultThreadFactory(String poolName, boolean daemon, int priority, ThreadGroup threadGroup) {
        ObjectUtil.checkNotNull(poolName, "poolName");

        if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
            throw new IllegalArgumentException(
                    "priority: " + priority + " (expected: Thread.MIN_PRIORITY <= priority <= Thread.MAX_PRIORITY)");
        }

        prefix = poolName + '-' + poolId.incrementAndGet() + '-';
        this.daemon = daemon;
        this.priority = priority;
        this.threadGroup = threadGroup;
    }
	
	//Create a thread and set the corresponding properties
    @Override
    public Thread newThread(Runnable r) {
        Thread t = newThread(FastThreadLocalRunnable.wrap(r), prefix + nextId.incrementAndGet());
        try {
            if (t.isDaemon() != daemon) {
                t.setDaemon(daemon);
            }

            if (t.getPriority() != priority) {
                t.setPriority(priority);
            }
        } catch (Exception ignored) {
            // Doesn't matter even if failed to set.
        }
        return t;
    }
	
	//Finally, create FastThreadLocalThread and return it. FastThreadLocalThread is a subclass of Thread
	//It mainly optimizes the ThreadLocal of jdk to increase the access speed
    protected Thread newThread(Runnable r, String name) {
        return new FastThreadLocalThread(threadGroup, r, name);
    }
}

Topics: Java Network Protocol