Skip to content

Commit 8048931

Browse files
stepanchegslandelle
authored andcommitted
Future (#1286)
* simplify AbstractListenableFuture.executionList initialization Just use CAS loop instead of synchronized and two boolean variables. * Allow null executor in ListenableFuture It allows execution of future callback in completion thread, and makes `ListenableFuture` closer to `CompletableFuture` (which allows `null` executor). It also saves a little memory in `toCompletableFuture` implementation.
1 parent 31864fa commit 8048931

File tree

4 files changed

+38
-29
lines changed

4 files changed

+38
-29
lines changed

client/src/main/java/org/asynchttpclient/ListenableFuture.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ public interface ListenableFuture<V> extends Future<V> {
6767
* to the executor} for execution when the {@code Future}'s computation is
6868
* {@linkplain Future#isDone() complete}.
6969
* <br>
70+
* Executor can be <code>null</code>, in that case executor will be executed
71+
* in the thread where completion happens.
72+
* <br>
7073
* There is no guaranteed ordering of execution of listeners, they may get
7174
* called in the order they were added and they may get called out of order,
7275
* but any listener added through this method is guaranteed to be called once
@@ -131,7 +134,11 @@ public void touch() {
131134

132135
@Override
133136
public ListenableFuture<T> addListener(Runnable listener, Executor exec) {
134-
exec.execute(listener);
137+
if (exec != null) {
138+
exec.execute(listener);
139+
} else {
140+
listener.run();
141+
}
135142
return this;
136143
}
137144

client/src/main/java/org/asynchttpclient/future/AbstractListenableFuture.java

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
package org.asynchttpclient.future;
3030

3131
import java.util.concurrent.Executor;
32+
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
3233

3334
import org.asynchttpclient.ListenableFuture;
3435

@@ -43,41 +44,44 @@
4344
*/
4445
public abstract class AbstractListenableFuture<V> implements ListenableFuture<V> {
4546

46-
private volatile boolean hasRun;
47-
private volatile boolean executionListInitialized;
4847
private volatile ExecutionList executionList;
4948

49+
private static final AtomicReferenceFieldUpdater<AbstractListenableFuture, ExecutionList> executionListField =
50+
AtomicReferenceFieldUpdater.newUpdater(AbstractListenableFuture.class, ExecutionList.class, "executionList");
51+
5052
private ExecutionList executionList() {
51-
ExecutionList localExecutionList = executionList;
52-
if (localExecutionList == null) {
53-
synchronized (this) {
54-
localExecutionList = executionList;
55-
if (localExecutionList == null) {
56-
localExecutionList = new ExecutionList();
57-
executionList = localExecutionList;
58-
executionListInitialized = true;
59-
}
60-
}
53+
ExecutionList executionListLocal = this.executionList;
54+
if (executionListLocal != null) {
55+
return executionListLocal;
56+
}
57+
58+
ExecutionList r = new ExecutionList();
59+
if (executionListField.compareAndSet(this, null, r)) {
60+
return r;
61+
} else {
62+
return this.executionList;
6163
}
62-
return localExecutionList;
6364
}
6465

6566
@Override
6667
public ListenableFuture<V> addListener(Runnable listener, Executor exec) {
67-
executionList().add(listener, exec);
68-
if (hasRun) {
69-
runListeners();
68+
if (executionList == null) {
69+
ExecutionList.executeListener(listener, exec);
70+
return this;
7071
}
72+
73+
executionList().add(listener, exec);
7174
return this;
7275
}
7376

7477
/**
7578
* Execute the execution list.
7679
*/
7780
protected void runListeners() {
78-
hasRun = true;
79-
if (executionListInitialized) {
80-
executionList().execute();
81+
if (executionList == null) {
82+
return;
8183
}
84+
85+
executionList().execute();
8286
}
8387
}

client/src/main/java/org/asynchttpclient/future/ExecutionList.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ public void add(Runnable runnable, Executor executor) {
5656
// Fail fast on a null. We throw NPE here because the contract of Executor states that it
5757
// throws NPE on null listener, so we propagate that contract up into the add method as well.
5858
assertNotNull(runnable, "runnable");
59-
assertNotNull(executor, "executor");
6059

6160
// Lock while we check state. We must maintain the lock while adding the new pair so that
6261
// another thread can't run the list out from under us. We only add to the list if we have not
@@ -120,9 +119,13 @@ public void execute() {
120119
/**
121120
* Submits the given runnable to the given {@link Executor} catching and logging all {@linkplain RuntimeException runtime exceptions} thrown by the executor.
122121
*/
123-
private static void executeListener(Runnable runnable, Executor executor) {
122+
static void executeListener(Runnable runnable, Executor executor) {
124123
try {
125-
executor.execute(runnable);
124+
if (executor != null) {
125+
executor.execute(runnable);
126+
} else {
127+
runnable.run();
128+
}
126129
} catch (RuntimeException e) {
127130
// Log it and keep going, bad runnable and/or executor. Don't punish the other runnables if
128131
// we're given a bad one. We only catch RuntimeException because we want Errors to propagate

client/src/main/java/org/asynchttpclient/netty/NettyResponseFuture.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,7 @@ public void run() {
271271
completable.complete((V) CONTENT_UPDATER.get(NettyResponseFuture.this));
272272
}
273273

274-
}, new Executor() {
275-
@Override
276-
public void execute(Runnable command) {
277-
command.run();
278-
}
279-
});
274+
}, null);
280275

281276
return completable;
282277
}

0 commit comments

Comments
 (0)