Architecture of Netty Asynchronous Read-Write Operation and Application of Observer Mode

Keywords: Netty Java

1. Future in Netty

Future in Netty inherits ava.util.concurrent.Future, while Future in Java primarily runs or cancels tasks, and Netty adds more functionality.

public interface Future<V> extends java.util.concurrent.Future<V> {
    
    boolean isSuccess();  //Returns true if and only if the i/o operation completes successfully
    boolean isCancellable(); //Returns true if and only if cancel method is available
    Throwable cause();//If the i/o operation fails, return the reason for its failure.Return null if successful or incomplete
    //Adds the specified listener to this Future, which is notified when the future is complete, and notifies the listener immediately if the future is complete when the listener is added
    Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
    //Remove the listener, if the future is complete, it will not trigger
    Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
    //Wait for this future done
   Future<V> sync() throws InterruptedException;
    //Wait, no interruption
    Future<V> syncUninterruptibly();
    //Waiting for Future to finish
  Future<V> await() throws InterruptedException;
    //Wait for Future to complete, uninterruptable
  Future<V> awaitUninterruptibly();
  //Return immediately, null if the future is not complete at this time
  V getNow();

    @Override
    boolean cancel(boolean mayInterruptIfRunning);
}

2.ChannelFuture

1.ChannelFuture is the result of an asynchronous channel i/o operation.It inherits all I/O operations from Future.Netty in Netty and is asynchronous, which means that any I/O operation will be returned immediately, without guaranteeing that all I/O operations will be completed at the end of the call. Instead, it will return a ChannelFuture instance that provides information about the result or status of the I/O operation.

3.ChannelFuture provides various methods to check if I/O operations have been completed, wait for completion, and return results.Channel FutureListener can also be added to be notified when completed.
4. The addListener (GenericFutureListener) method is non-blocking.It simply adds the specified ChannelFutureListener to ChannelFuture, and the I/O thread notifies the listener when the Future associated with the I/O operation completes (isDone).Channel FutureListener produces the best performance and resource utilization because it does not block at all.
5. By contrast, await() is a blocking operation.Once called, the calling thread will block until the operation is complete.Using await() to implement sequential logic is easy, but the calling thread will unnecessarily block until the I/O operation is complete.And the cost of inter-thread notifications is relatively high.Deadlock may also occur.Because I/O operations may never be completed.

/*
BAD!!!
*/
public void channelRead(ChannelHandlerContext ctx,Object msg){
    ChannelFuture future = ctx.channel().close();
    //Do not use await() in ChannelHandler
    future.awaitUninterrupted();
    
}
/**
GOOD!
**/
public void channelRead(ChannelHandlerContext ctx,Object msg){
    ChannelFuture future = ctx.channel().close();
    future.addListener(new ChannelFutureListener(){
        public void operationComplete(Channel Future){
        
        }
    });
}

6. Do not use await() in I/O threads, otherwise a BlockingOperationException will be thrown to prevent deadlocks.
7. Do not confuse timeout of I/O with timeout of await().The timeout value set using the await(long) method is independent of the I/O timeout.If the I/O operation timed out, Future will be marked Completed but Failed.

/**
*BAD!
***/
Bootstrap b = new Bootstrap();
ChannelFuture f = b.connect(...);
f.awaitUninterrputibly(10,TimeUnit.SECONDS);
if(f.isCancelled()){
    //Unlink Attempt
}else if(!f.isSuccess()){
    //A null pointer exception may be thrown because the future may not be complete
    f.cause.printStackTrace();
}else{
    //Connection Successful
}
/**
    GOOD
*/
Bootstrap b = new Bootstrap();
b.option(ChannelOption.CONNECT_TIMEOUT_MILLS,10000);
ChannelFuture f = b.connect(...);
f.awaitUninterrputibly();

assert f.isDone();

if(f.isCancelled()){
    //Cancel connection attempt
}else if(!f.isSuccess()){
    f.cause.printStackTrace();
}else{
    //Connection Successful
}

3.ChannelFutureListener

1.ChannelFutureListener inherits the GenericFutureListener <ChannelFuture>interface.

public interface GenericFutureListener<F extends Future<?>> extends EventListener {

  /*******
  *This method is called when the operation associated with Future is complete
  ********/
    void operationComplete(F future) throws Exception;
}

2. It is used to monitor the results of ChannelFuture and to notify the results of asynchronous ChannelI/O operations by calling ChannelFuture.addListener(GenericFutureListener) to add a listener.

4.ChannelPromise
1. It inherits the ChannelFuture interface and is an upgraded version of ChannelFuture.It is writable and can set the status of hannelFuture, such as success or failure.

/**
 DefaultPromise.awaitUninterruptibly()
*/
  public Promise<V> awaitUninterruptibly() {
        //If completed, return immediately
        if (isDone()) {
            return this;
        }
        //Check Deadlock
        checkDeadLock();
    
        boolean interrupted = false;
        //Use synchronization so that only one thread modifies waiter
        synchronized (this) {
        //If Continuous Loop Detection is not completed
            while (!isDone()) {
                //++waiters
                incWaiters();
                try {
                
                    wait();
                } catch (InterruptedException e) {
                    // Interrupted while waiting.
                    interrupted = true;
                } finally {
                //--waiters
                    decWaiters();
                }
            }
        }

        if (interrupted) {
            Thread.currentThread().interrupt();
        }

        return this;
    }
/**
DefaultPromise.setSuccess()
**/
 public Promise<V> setSuccess(V result) {
        if (setSuccess0(result)) {
            //Notify listeners of success
            notifyListeners();
            return this;
        }
        throw new IllegalStateException("complete already: " + this);
    }
private void notifyListeners() {
        EventExecutor executor = executor();
        //If the thread currently executing is the one contained in this event loop group
        if (executor.inEventLoop()) {
            final InternalThreadLocalMap threadLocals = InternalThreadLocalMap.get();
            final int stackDepth = threadLocals.futureListenerStackDepth();
            if (stackDepth < MAX_LISTENER_STACK_DEPTH) {
                threadLocals.setFutureListenerStackDepth(stackDepth + 1);
                try {
                    notifyListenersNow();
                } finally {
                    threadLocals.setFutureListenerStackDepth(stackDepth);
                }
                return;
            }
        }

        //If not, submit a new task
        safeExecute(executor, new Runnable() {
            @Override
            public void run() {
                notifyListenersNow();
            }
        });
    }
 private void notifyListeners0(DefaultFutureListeners listeners) {
        GenericFutureListener<?>[] a = listeners.listeners();
        int size = listeners.size();
        for (int i = 0; i < size; i ++) {
        //Corresponding listener in notification array
            notifyListener0(this, a[i]);
        }
    }
 private static void notifyListener0(Future future, GenericFutureListener l) {
        try {
        //Call the operationComplete method
            l.operationComplete(future);
        } catch (Throwable t) {
            if (logger.isWarnEnabled()) {
                logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
            }
        }
    }

Posted by crondeau on Fri, 10 May 2019 21:48:13 -0700