Source code implementation of Netty master slave Reactor multithreading mode

Keywords: Java Netty socket Attribute

Netty -- source code implementation of master-slave Reactor multithreading mode

Overview

What is EventLoopGroup?

EventLoopGroup is a container for storing EventLoop, and it should have the function of thread pool.

graph BT; EventLoopGroup --> EventExecutorGroup; EventExecutorGroup --> ScheduledExecutorService;

Because EventLoopGroup indirectly inherits the ScheduledExecutorService interface, its implementation class should have the function of thread pool.

Take a look at the core properties of NioEventLoopGroup

    // Default thread pool size
    private static final int DEFAULT_EVENT_LOOP_THREADS;

    static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); // CPU cores x 2
    }
    
    // Array to store EventLoop
    private final EventExecutor[] children;

Construction method

    // If the incoming nThread is empty, the default thread pool size (CPU cores x 2) is used
    protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
    }

    // Final construction method
    protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
        // Omit...

        children = new EventExecutor[nThreads]; // Initialize the size of the EventExecutor array

        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(executor, args); // Initialize EventLoop
                success = true;
            } catch (Exception e) {
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
                // Omit...
            }
        }
        // Omit...
    }

After the NioEventLoopGroup instance is created, the size of its EventExecutor array and its stored EventLoop have been initialized.

graph TB; Nioeventloopgroup -- > array [eventexecutor array]; array --> E1[EventLoop]; array --CPU cores x 2 -- > E2 [EventLoop]; array --> E3[EventLoop...];

What is EventLoop?

graph BT; EventLoop --> EventExecutor; NioEventLoop --> EventLoop;

EventLoop is a sub interface of EventExecutor.

Take a look at the core properties of NioEventLoop

  private Selector selector;
  
  private volatile Thread thread;
  
  private final Executor executor;
  
  private final Queue<Runnable> taskQueue;
  
  static final int DEFAULT_MAX_PENDING_EXECUTOR_TASKS = Math.max(16,
            SystemPropertyUtil.getInt("io.netty.eventexecutor.maxPendingTasks", Integer.MAX_VALUE));

Every NioEventLoop has a Selector, thread, Executor, Queue property. When an EventLoop instance is created, its thread property is still NULL, and no thread has been created.

Channel

Take a look at the core properties of Channel

private volatile EventLoop eventLoop; // Reference to EventLoop

Each Channel has a reference to an EventLoop, that is, each Channel will be bound to an EventLoop, and its eventLoop() method returns the EventLoop bound to this Channel.

graph TB; EventLoop --> A[Channel]; EventLoop --> B[Channel]; EventLoop --> C[Channel...];

The core process after Netty starts the server

  // Create bossGroup and workerGroup
  EventLoopGroup bossGroup = new NioEventLoopGroup();
  EventLoopGroup workerGroup = new NioEventLoopGroup();
  // Bind port, start service
  serverBootstrap.bind(8888).sync();

doBind() method of AbstarctBootstrap

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister(); // Initialize the Channel and register the Channel with the Selector in the EventLoop in the bossGroup
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() { 
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {  // When the initAndRegister() method is completed, it is automatically called by the IO thread
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        doBind0(regFuture, channel, localAddress, promise);   // Bind operation
                    }
                }
            });
            return promise;
        }
    }
    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel(); // Create NioServerSocketChannel
            init(channel); // Initialize Channel
        } catch (Throwable t) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        
        // The config() method returns AbstractBootStrap
        // AbstractBootStrap stores bossGroup,ServerBootStrap stores workerGroup
        ChannelFuture regFuture = config().group().register(channel); 
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }

The register() method of EventLoopGroup extracts a EventLoop from the EventLoopGroup through the selector, and then calls the register() method of EventLoop.

    // register() method of EventLoopGroup
    public ChannelFuture register(Channel channel) {
        return next().register(channel); // Call the register() method of EventLoop
    }
    
    /**
     * Take an EventLoop from the EventLoopGroup through the selector
     */
    public EventExecutor next() {
        return chooser.next();
    }

register() method of SingleThreadEventLoop

    public ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise); // Call the Channel's register() method to pass the current object, that is, the current EventLoop instance
        return promise;
    }

register() method of abstarcchannel

    public final void register(EventLoop eventLoop, final ChannelPromise promise) {
        if (eventLoop == null) {
            throw new NullPointerException("eventLoop");
        }
        if (isRegistered()) {
            promise.setFailure(new IllegalStateException("registered to an event loop already"));
            return;
        }
        if (!isCompatible(eventLoop)) {
            promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
            return;
        }

        AbstractChannel.this.eventLoop = eventLoop; // Initialize the EventLoop property of the Channel
           
        // On the first call, the thread in EventLoop is empty, so inEventLoop() returns false
        if (eventLoop.inEventLoop()) { 
            register0(promise); 
        } else {
            try {
                eventLoop.execute(new Runnable() { // Call the execute() method of EventLoop, and put the registration operation as a task into the Runnable instance
                @Override
                public void run() {
                    // The register0() method will eventually call the doRegister() method to register the Channel with the Selector in the EventLoop
                    register0(promise);
                    }
                });
            } catch (Throwable t) {
                logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}",AbstractChannel.this, t);
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
           }
        }
     }

execute() method of SingleThreadEventExecutor

     public void execute(Runnable task) {
         if (task == null) {
             throw new NullPointerException("task");
         }
 
         boolean inEventLoop = inEventLoop(); 
         addTask(task); // Put the task into the task queue of the current EventLoop
         if (!inEventLoop) {
             startThread(); // Start a thread and finally call the doStartThread() method
             if (isShutdown()) {
                 boolean reject = false;
                 try {
                     if (removeTask(task)) {
                         reject = true;
                     }
                 } catch (UnsupportedOperationException e) {
                 
                 }
                 if (reject) {
                     reject();
                 }
             }
         }

         if (!addTaskWakesUp && wakesUpForTask(task)) {
             wakeup(inEventLoop);
         }
     }

doStartThread() method of SingleThreadEventExecutor

     private void doStartThread() {
         assert thread == null; // The thread in the current EventLoop is empty, and the output result must be true
         executor.execute(new Runnable() { // Submit a task to the thread pool, and then create a thread to execute asynchronously at the same time
             @Override
             public void run() {
                 thread = Thread.currentThread(); // Initializes the thread property in the EventLoop, which is the thread in the thread pool that performs the task
                 if (interrupted) {
                     thread.interrupt();
                 }

                 boolean success = false;
                 updateLastExecutionTime();
                 try {
                     SingleThreadEventExecutor.this.run(); // Core method (current object is NioEventLoop)
                     success = true;
                 } catch (Throwable t) {
                     logger.warn("Unexpected exception from an event executor: ", t);
                 } finally {
                     // Omit...
                 }
             }
         });
     }

run() method of NioEventLoop

     protected void run() {
         for (;;) { // Dead cycle
             try {
                 try {
                     switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
                     case SelectStrategy.CONTINUE:
                         continue;

                     case SelectStrategy.BUSY_WAIT:

                     case SelectStrategy.SELECT:
                         select(wakenUp.getAndSet(false));

                         if (wakenUp.get()) {
                             selector.wakeup();
                         }
                     default:
                     }
                 } catch (IOException e) {
                     rebuildSelector0();
                     handleLoopException(e);
                     continue;
                 }

                 cancelledKeys = 0;
                 needsToSelectAgain = false;
                 final int ioRatio = this.ioRatio;
                 if (ioRatio == 100) {
                     try {
                         processSelectedKeys(); // Handle the ready event of the Channel, and finally call the processSelectedKeysOptimized() method
                     } finally {
                         runAllTasks(); // Perform all tasks saved in the task queue
                     }
                 } else {
                     final long ioStartTime = System.nanoTime();
                     try {
                         processSelectedKeys();
                     } finally {
                         // Ensure we always run tasks.
                         final long ioTime = System.nanoTime() - ioStartTime;
                         runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
                     }
                 }
             } catch (Throwable t) {
                 handleLoopException(t);
             }
             try {
                 if (isShuttingDown()) {
                     closeAll();
                     if (confirmShutdown()) {
                         return;
                     }
                 }
             } catch (Throwable t) {
                 handleLoopException(t);
             }
         }
     }
     private void processSelectedKeysOptimized() {
         for (int i = 0; i < selectedKeys.size; ++i) { // Traverse the SelectionKey collection corresponding to the ready Channel. If the Channel has no event ready, the collection is empty
             final SelectionKey k = selectedKeys.keys[i];
             selectedKeys.keys[i] = null;

             final Object a = k.attachment();

             if (a instanceof AbstractNioChannel) {
                 processSelectedKey(k, (AbstractNioChannel) a);
             } else {
                 @SuppressWarnings("unchecked")
                 NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                 processSelectedKey(k, task);
             }

             if (needsToSelectAgain) {
                 selectedKeys.reset(i + 1);

                 selectAgain();
                 i = -1;
             }
         }
     }

processSelectedKey() method of NioEventLoop

     private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
         final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
         if (!k.isValid()) {
             final EventLoop eventLoop;
             try {
                 eventLoop = ch.eventLoop();
             } catch (Throwable ignored) {
                 return;
             }
             if (eventLoop != this || eventLoop == null) {
                 return;
             }
             unsafe.close(unsafe.voidPromise());
             return;
         }

         try {
             int readyOps = k.readyOps(); // Ready events
             if ((readyOps & SelectionKey.OP_CONNECT) != 0) { // Connection ready
                 int ops = k.interestOps();
                 ops &= ~SelectionKey.OP_CONNECT;
                 k.interestOps(ops);
                 unsafe.finishConnect(); // Ready to process connection
             }
           
            if ((readyOps & SelectionKey.OP_WRITE) != 0) { // Write in order
                 ch.unsafe().forceFlush(); // Handle write ready
             }
           
            if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { // Receive and read ready
                 unsafe.read(); // Process receive and read ready
             }
         } catch (CancelledKeyException ignored) {
             unsafe.close(unsafe.voidPromise());
         }
     }

When the thread in the EventLoop processes the ready event of the Channel, it will execute all the tasks saved in the task queue, and the registration task will be executed

     private void register0(ChannelPromise promise) {
         try {
             if (!promise.setUncancellable() || !ensureOpen(promise)) {
                 return;
             }
             boolean firstRegistration = neverRegistered;
             doRegister(); // Perform registration operation
             neverRegistered = false;
             registered = true;

             pipeline.invokeHandlerAddedIfNeeded();

             safeSetSuccess(promise); // ChannelFuture is set to success, and isDone() method will return true
             pipeline.fireChannelRegistered();
             if (isActive()) {
                 if (firstRegistration) {
                     pipeline.fireChannelActive();
                 } else if (config().isAutoRead()) {
                     beginRead();
                 }
             }
          } catch (Throwable t) {
             closeForcibly();
             closeFuture.setClosed();
             safeSetFailure(promise, t); // ChannelFuture is set to fail and isDone() method returns true
          }
     }

doRegister() method of AbstractNioChannel

     protected void doRegister() throws Exception {
         boolean selected = false;
         for (;;) {
             try {
                 // Register Channel to Selector in EventLoop
                 selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); // Register the Channel to the Selector, and pass the event type to be monitored to the Selector after the Channel completes the corresponding operation
                 return;
             } catch (CancelledKeyException e) {
                 if (!selected) {
                     eventLoop().selectNow();
                     selected = true;
                 } else {
                     throw e;
                 }
             }
         }
     }

Go back to the doBind() method of the bstarctbootstrap at the beginning

     private ChannelFuture doBind(final SocketAddress localAddress) {
         final ChannelFuture regFuture = initAndRegister(); 
         final Channel channel = regFuture.channel(); 
         if (regFuture.cause() != null) {
             return regFuture;
         }

         if (regFuture.isDone()) { // Since the registration task is executed asynchronously, and the task has not been executed yet, the isDone() method will return false
             ChannelPromise promise = channel.newPromise();
             doBind0(regFuture, channel, localAddress, promise);
             return promise;
         } else {
             final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
             regFuture.addListener(new ChannelFutureListener() { 
                 @Override
                 public void operationComplete(ChannelFuture future) throws Exception {  // When the registration task is completed, the IO thread will automatically
                     Throwable cause = future.cause();
                     if (cause != null) {
                         promise.setFailure(cause);
                     } else {
                         promise.registered();
                         doBind0(regFuture, channel, localAddress, promise); // Bind operation
                     }
                 }
             });
             return promise;
         }
     }
     private static void doBind0(
         final ChannelFuture regFuture, final Channel channel,
         final SocketAddress localAddress, final ChannelPromise promise) {

         channel.eventLoop().execute(new Runnable() { // Similarly, get the EventLoop bound by the Channel, call the execute() method of the EventLoop, and put the binding operation into the Runnable instance as a task
         @Override
         public void run() {
             if (regFuture.isSuccess()) {
                   channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                } else {
                   promise.setFailure(regFuture.cause());
             }
         }
        });
      }
     public void execute(Runnable task) {
         if (task == null) {
             throw new NullPointerException("task");
         }

         boolean inEventLoop = inEventLoop(); // The inEventLoop() method returns true because the current method is called by the thread that processes the registration task, that is, the thread in the EventLoop
         addTask(task); // Put the task in the queue and wait for it to be executed
         if (!inEventLoop) { // No execution, so far only one thread has been opened
             startThread();
             if (isShutdown()) {
                 boolean reject = false;
                 try {
                     if (removeTask(task)) {
                         reject = true;
                     }
                 } catch (UnsupportedOperationException e) {
                    
                 }
                 if (reject) {
                     reject();
                 }
             }
         }

         if (!addTaskWakesUp && wakesUpForTask(task)) {
             wakeup(inEventLoop);
         }
     }

This is the core process after Netty starts the server

1. Create ServerSocketChannel and initialize it.

2. call the register() method of bossGroup. The method selects a EventLoop from the bossGroup through the selector, and then calls the register() method of EventLoop.

3. finally, we call the register() method of Channel, initialize the eventLoop property of Channel, then register Channel into Selector in bossGroup EventLoop as a task, put it into Runnable instance, and then call EventLoop execute (EventLoop) method.

4. The execute() method puts the task into the task queue, and then submits a task to the thread pool. At this time, a thread is created and the thread property in the EventLoop is initialized.

The 5. task initializes the thread property of EventLoop, then calls the run() method of NioEventLoop, handles the ready events of Channel and executes all tasks saved in the task queue.

6. when the registration task is executed, the thread will call back the operationComplete() method in ChannelFutureListener, bind the operation as a task, and then invoke the execute(Runnable) method of EventLoop.

7. Repeat step 4 to put the task into the task queue. Since the current thread is the thread in the EventLoop, the inEventLoop() method returns true and will not submit the task to the thread pool. The task waits to be executed by the thread in the EventLoop.

Netty's core process for handling receive and read ready events

Thread in EventLoop in BossGroup is processing Channel ready events and executing all tasks saved in task queue

// Create a connection
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8888));

The Selector in EventLoop in BossGroup listens for receiving ready events in ServerSocketChannel

        // processSelectedKey() method of NioEventLoop
        if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
            unsafe.read();
        }
        
        public void read() {
            assert eventLoop().inEventLoop(); // It must be true
            final ChannelConfig config = config();
            final ChannelPipeline pipeline = pipeline();
            final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
            allocHandle.reset(config);

            boolean closed = false;
            Throwable exception = null;
            try {
                try {
                    do {
                        int localRead = doReadMessages(readBuf); // Read data in Channel
                        if (localRead == 0) {
                            break;
                        }
                        if (localRead < 0) {
                            closed = true;
                            break;
                        }

                        allocHandle.incMessagesRead(localRead);
                    } while (allocHandle.continueReading());
                } catch (Throwable t) {
                    exception = t;
                }

                int size = readBuf.size();
                for (int i = 0; i < size; i ++) {
                    readPending = false;
                    pipeline.fireChannelRead(readBuf.get(i)); // Finally, the register() method of ServerBootStrap will be called
                }
                readBuf.clear();
                allocHandle.readComplete();
                pipeline.fireChannelReadComplete();

                if (exception != null) {
                    closed = closeOnReadError(exception);

                    pipeline.fireExceptionCaught(exception);
                }

                if (closed) {
                    inputShutdown = true;
                    if (isOpen()) {
                        close(voidPromise());
                    }
                }
            } finally {
                if (!readPending && !config.isAutoRead()) {
                    removeReadOp();
                }
            }
        }

doReadMessage() method of NioServerSocketChannel

     protected int doReadMessages(List<Object> buf) throws Exception {
         SocketChannel ch = SocketUtils.accept(javaChannel()); // Receive the connection and call the accept() method of ServerSocketChannel

         try {
             if (ch != null) {
                 buf.add(new NioSocketChannel(this, ch)); // Put the received connection into the buffer
                 return 1;
             }
         } catch (Throwable t) {
             logger.warn("Failed to create a new channel from an accepted socket.", t);

             try {
                 ch.close();
             } catch (Throwable t2) {
                 logger.warn("Failed to close a socket.", t2);
             }
         }

         return 0;
     }

register() method of ServerBootstrap

     public void channelRead(ChannelHandlerContext ctx, Object msg) {
        final Channel child = (Channel) msg;

         child.pipeline().addLast(childHandler);

         setChannelOptions(child, childOptions, logger);
         setAttributes(child, childAttrs);

         try {
              // Calling the register() method of workerGroup, the method selects a EventLoop from the workerGroup through the selector, then calls the register() method of EventLoop, and finally calls the register() method of AbstractChannel.
              childGroup.register(child).addListener(new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                  if (!future.isSuccess()) {
                        forceClose(child, future.cause());
                  }
              }
             });
          } catch (Throwable t) {
              forceClose(child, t);
          }
      }

register() method of AbstractChannel

     public final void register(EventLoop eventLoop, final ChannelPromise promise) {
        if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
        }
        if (isRegistered()) {
              promise.setFailure(new IllegalStateException("registered to an event loop already"));
              return;
        }
        if (!isCompatible(eventLoop)) {
              promise.setFailure(
                     new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
              return;
        }

        AbstractChannel.this.eventLoop = eventLoop; // Initialize the eventLoop property of Channel

        // If the new EventLoop is retrieved, its thread property is empty. The current thread is always the thread in the EventLoop in the bossGroup, so inEventLoop() returns false.
        // If the old EventLoop is retrieved, its thread property is not empty. The current thread is always the thread in EventLoop in bossGroup, so inEventLoop() returns false.
        if (eventLoop.inEventLoop()) { // Always return false. The current thread is always the thread in the EventLoop in the bossGroup. It must not be equal to the thread in any EventLoop in the workerGroup.
              register0(promise);
         } else {
             try {
                 eventLoop.execute(new Runnable() { // This method is always executed
                     @Override
                     public void run() {
                        register0(promise);
                     }
                  });
             } catch (Throwable t) {
                  logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}",AbstractChannel.this, t);
                  closeForcibly();
                  closeFuture.setClosed();
                  safeSetFailure(promise, t);
             }
        }
     }
     public void execute(Runnable task) {
         if (task == null) {
             throw new NullPointerException("task");
         }

         boolean inEventLoop = inEventLoop();
         addTask(task); // Put tasks in the queue
         if (!inEventLoop) { 
             startThread(); // This method is called by either the new or the old EventLoop 
             if (isShutdown()) {
                 boolean reject = false;
                 try {
                     if (removeTask(task)) {
                         reject = true;
                     }
                 } catch (UnsupportedOperationException e) {
                
                 }
                 if (reject) {
                     reject();
                 }
             }
         }

         if (!addTaskWakesUp && wakesUpForTask(task)) {
             wakeup(inEventLoop);
         }
     }
     private void startThread() {
         if (state == ST_NOT_STARTED) { // If the old EventLoop is retrieved, its thread property is not empty, so its state property is not equal to st not started, so no new thread will be opened, and the registration task will wait for execution by the thread in the EventLoop
             if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
                 boolean success = false;
                 try {
                     doStartThread(); // The new EventLoop is taken out. Its thread property is null, and its state is equal to st not started. Therefore, you need to start the thread, submit a task to the thread pool, and loop to handle the ready event of the Channel and execute all tasks saved in the task queue
                     success = true;
                 } finally {
                     if (!success) {
                         STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED);
                     }
                 }
             }
         }
     }

This is the core process for Netty to handle receive and read ready events

1. The client establishes a SocketChannel and connects.

2. The Selector in the EventLoop in the bossgroup listens that the ServerSocketChannel has a receive ready event.

3. receives the connection and finally calls the register() method of workerGroup. The method selects a EventLoop from the workGroup through the selector, and then calls the register() method of EventLoop.

4. finally, we call the register() method of Channel, initialize the eventLoop property of Channel, then register Channel into Selector in bossGroup EventLoop as a task, put it into Runnable instance, and then call EventLoop execute (EventLoop) method.

5. The execute() method puts the task into the task queue. If the new EventLoop is taken out, its thread attribute is empty. At this time, the thread will be started, and a task will be submitted to the thread pool. The loop will process the ready event of the Channel and execute all tasks saved in the task queue. If the old EventLoop is taken out, its thread attribute is not empty. Any The service waits to be executed by a thread in the EventLoop.

summary

1. After the NioEventLoopGroup instance is created, the size of its EventExecutor array and its stored EventLoop have been initialized.

2. Each NioEventLoop has a Selector, thread, Executor, Queue property. When a NioEventLoop instance is created, its thread property is still empty.

3. Each Channel will be bound to an EventLoop. Its eventLoop() method returns its bound EventLoop, and the Channel will be registered in the Selector of its bound EventLoop.

The register() method of 4.EventLoopGroup extracts a EventLoop from the EventLoopGroup through the selector and then calls the register() method of EventLoop.

5. The execute() method of the EventLoop will put the task into the task queue. If the inEventLoop() method returns false and its thread property is empty, a thread will be created and a task will be submitted to the thread pool (initialize the thread property in the EventLoop in the task, and then loop to handle the ready event of the Channel and execute all tasks saved in the task queue)

Posted by wmhop on Thu, 05 Dec 2019 20:44:52 -0800