Yongshun Daniel wrote a series of tutorials “no secret under the source code – do the best netty source code analysis tutorial” is the best netty source analysis article I have read so far. But I don’t know why. When I write the third chapter, the author stops the shift. Therefore, I would like to try to write the following chapters with my personal understanding.
Write it first
Senior Yongshun has completed the following chapters:
- Netty source code analysis
- Netty source code analysis zero sharpening knife cutting firewood industry source code analysis environment construction
- Netty source code analysis Part 1: uncover the mysterious red cover of bootstrap (client)
- Netty source code analysis Part 1: uncovering the mysterious red cover of bootstrap (server side)
- Netty source code analysis Part 2: the artery of netty channel pipeline (1)
- Netty source code analysis Part 2: the main artery of netty channel pipeline (2)
- Netty source code analysis 3 I am the famous EventLoop (1)
- Netty source code analysis 3 I am the famous EventLoop (2)
Continuation section:
- Promise and future: Secrets of Gemini
- Netty source code analysis 5 Pentium blood: bytebuf
- Netty source code analysis Part 6 pipeline processor: handler
The netty version used in this article is 4.1.33
The relationship between future < V > and promise < V >
Netty’s internal io.netty.util . concurrent.Future <5> Inherited from java.util.concurrent . future < V >, while promise < V > is a special implementation of the former.
Java Native future < V >
The future < V > interface is provided under the Java concurrent programming package. Future represents the result of the asynchronous operation in asynchronous programming. The internal method of future < V > can realize the operations such as status checking, canceling execution and obtaining execution result. The internal methods are as follows:
//Attempt to cancel execution
boolean cancel(boolean mayInterruptIfRunning);
//Has the execution been cancelled
boolean isCancelled();
//Has the execution been completed
boolean isDone();
//Block get execution result
V get() throws InterruptedException, ExecutionException;
//Block get execution result或超时后返回
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
Netty’s extension of future < V >
The function of native future < V > is limited. Netty extends future < V > and adds the following methods:
- A richer method of state judgment is added
//Judge whether the execution is successful
boolean isSuccess();
//Judge whether the execution can be cancelled
boolean isCancellable();
- Abnormal I / O operation caused by support acquisition
Throwable cause();
- Added the method of monitoring callback to support the callback method specified by the user after the future is completed
//Add callback method
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
//Add multiple callback methods
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
//Delete callback method
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
//Delete multiple callback methods
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
- Two kinds of methods for blocking and waiting for the result to return are added. One of them is the sync method, which blocks the waiting result and throws an exception that causes the failure if the execution fails; the other is the await method, which only blocks the return of waiting result and does not throw an exception.
//Block the wait and throw an exception if it fails
Future<V> sync() throws InterruptedException;
//Same as above, the difference is non interruptible blocking waiting process
Future<V> syncUninterruptibly();
//Blocking wait
Future<V> await() throws InterruptedException;
//Same as above, the difference is non interruptible blocking waiting process
Future<V> awaitUninterruptibly();
Promise<V>
Promise < V > interface continues to inherit future < V > and adds several methods to set state and callback
//Set success status and call back
Promise<V> setSuccess(V result);
boolean trySuccess(V result);
//Set failure status and callback
Promise<V> setFailure(Throwable cause);
boolean tryFailure(Throwable cause);
//Set to non cancelable state
boolean setUncancellable();
It can be seen that promise < V > as a special future < V > only adds some state setting methods. Therefore, it is often used in incoming I / O business code to set the success (or failure) status after I / O ends and call back methods.
Setting I / O execution results through promise
Taking the registration process of client connection as an example, the call link is as follows:
io.netty.bootstrap.Bootstrap.connect()
--> io.netty.bootstrap.Bootstrap.doResolveAndConnect()
---->io.netty.bootstrap.AbstractBootstrap.initAndRegister()
------>io.netty.channel.MultithreadEventLoopGroup.register()
-------->io.netty.channel.SingleThreadEventLoop.register()
Trace to the singlethreadeventloop, and you will see this Code:
@Override
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
A new defaultchannelpromise is created here. The constructor passes in the current channel and the current thread this. From the class diagram in Section 1, we know that defaultchannelpromise implements both future and promise, with all the methods mentioned above.
Then continue to pass the promise into another register method:
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}
In the register method, continue to pass the promise to the unsafe register method, and immediately return the promise in the form of channelfuture. Obviously, this is an asynchronous callback processing: the upper layer business can get the returned channelfuture blocking waiting result or set the callback method, while the promise that continues to pass down can be used to set the execution status and callback the method set.
As we continue to debug, we can see:
// io.netty.channel.AbstractChannel.AbstractUnsafe.java
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
//If it has been registered, it will be set to fail
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
//If the thread type is incompatible, it is set to fail
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@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();
//Failed to set promise to abnormal condition
safeSetFailure(promise, t);
}
}
}
private void register0(ChannelPromise promise) {
try {
//Before registration, set promise to non cancelable transition
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister();
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
//Promise set to success
safeSetSuccess(promise);
pipeline.fireChannelRegistered();
if (isActive()) {
if (firstRegistration) {
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
// Close the channel directly to avoid FD leak.
closeForcibly();
closeFuture.setClosed();
//Failed to set promise to abnormal condition
safeSetFailure(promise, t);
}
}
It can be seen that whether the underlying I / O operation is successful or not can be set by promise, and the outer channel future can perceive the result of I / O operation.
Get I / O execution results through channelfuture
Let’s take a look at the purpose of the returned channel future:
// io.netty.bootstrap.AbstractBootstrap.java
final ChannelFuture initAndRegister() {
//...
ChannelFuture regFuture = config().group().register(channel);
//If the exception is not null, it means that the underlying I / O has failed, and promise has set the failure exception
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}
Here, I / O failure can be detected in advance by checking whether the failure exception stack is empty. We can also see that:
// io.netty.bootstrap.AbstractBootstrap.java
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
//If the registration is successful
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
//If the registration is not completed
// ...
}
}
Here, you can know whether the underlying registration is completed through the channelfuture ා isdone() method. If so, continue to bind.
However, because registration is an asynchronous operation, if the registration may not be completed at this time, the following logic will be entered:
// io.netty.bootstrap.AbstractBootstrap.java
//...
else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
Here, a new pendingregistrationpromise is created, a callback method is added to the original channelfuture object, and the status of pendingregistrationpromise is changed in the callback, and pendingregistrationpromise will continue to be passed to the upper level. When the underlying promise state is set and called back, the callback method is entered. The I / O status continues to pass out.
The principle of result transfer of defaultchannelpromise
We’ve seen the asynchronous model of promise and future. Let’s look at how the bottom is implemented. Take the most commonly used defaultchannelpromise as an example. Its internal is very simple. We mainly look at its parent class, defaultpromise:
//Atomic updater for result field
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");
//Fields for caching execution results
private volatile Object result;
//Thread where promise is located
private final EventExecutor executor;
//One or more callback methods
private Object listeners;
//Number of blocked threads counter
private short waiters;
Set status
Take setting success status as an example
@Override
public Promise<V> setSuccess(V result) {
if (setSuccess0(result)) {
//Call the callback method
notifyListeners();
return this;
}
throw new IllegalStateException("complete already: " + this);
}
private boolean setSuccess0(V result) {
return setValue0(result == null ? SUCCESS : result);
}
private boolean setValue0(Object objResult) {
//Atomic modify the result field to objResult
if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
checkNotifyWaiters();
return true;
}
return false;
}
private synchronized void checkNotifyWaiters() {
if (waiters > 0) {
//If there are other threads waiting for the result of the promise, wake them up
notifyAll();
}
}
To set the state of promise is to modify the result field atomically to the incoming execution result. It is worth noting that the result field has the volatile keyword to ensure visibility between multiple threads. In addition, after setting the state, it will try to wake up all threads that are blocking waiting for the promise to return the result.
The other methods of setting the state are basically the same.
The thread is blocked to execute the result and wait
As mentioned above, other threads will block and wait for the promise to return results. The specific implementation takes the sync method as an example
@Override
public Promise<V> sync() throws InterruptedException {
//Blocking wait
await();
//If there is an exception, it is thrown
rethrowIfFailed();
return this;
}
@Override
public Promise<V> await() throws InterruptedException {
if (isDone()) {
//If it's done, go back directly
return this;
}
//Can be interrupted
if (Thread.interrupted()) {
throw new InterruptedException(toString());
}
//Dead circulation check
checkDeadLock();
synchronized (this) {
while (!isDone()) {
//Up counter (used to record how many threads are waiting for the promise to return results)
incWaiters();
try {
//Blocking wait结果
wait();
} finally {
//Down counter
decWaiters();
}
}
}
return this;
}
All threads that call the sync method are blocked until promise is set to success or failure. This also explains why the netty client or server usually calls the sync method when it starts. Essentially, it blocks the current thread and waits for the I / O result to return asynchronously, as follows:
Bootstrap bootstrap = new Bootstrap();
ChannelFuture future = bootstrap.group(new NioEventLoopGroup(10))
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//Add parser based on newline character to pipeline
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
//Adding string codec to pipeline
ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));
ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
//Add server-side processing logic to the pipeline
ch.pipeline().addLast(new MyClientEchoHandler());
}
}).connect("127.0.0.1", 9898).sync();
future.channel().closeFuture().sync();
Callback mechanism
@Override
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
checkNotNull(listener, "listener");
synchronized (this) {
//Add callback method
addListener0(listener);
}
if (isDone()) {
//If the I / O operation has ended, the callback is triggered directly
notifyListeners();
}
return this;
}
private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
if (listeners == null) {
//Only one callback method is assigned directly
listeners = listener;
} else if (listeners instanceof DefaultFutureListeners) {
//Add the callback method to the listeners array maintained internally by defaultfuturelisteners
((DefaultFutureListeners) listeners).add(listener);
} else {
//If there are multiple callback methods, create a new defaultfuturelisteners to save more callback methods
listeners = new DefaultFutureListeners((GenericFutureListener<?>) listeners, listener);
}
}
As can be seen from the above, after adding a callback method, it will immediately check whether promise has been completed; if promise has been completed, the callback method will be called immediately.
summary
The promise and future mechanism of netty is developed based on future < V > under Java Concurrent package. Among them, future supports blocking waiting, adding callback methods and judging execution status, while promise mainly supports state setting related methods. When the underlying I / O operation changes the execution state through promise, we can get the result immediately by synchronizing the waiting future.
Therefore, as the title of Yongshun Daniel said, promise and future are closely linked like Gemini in netty’s asynchronous model. But I think they’re more like two electrons in a quantum entanglement, because changing the state of one side makes the other party feel it immediately.
So far, the core principles of promise and future have been analyzed.