PromiseNotifier does not propagate cancel events

Motivation:
If the Future that the PromiseNotifier is listening to is cancelled, it does not propagate the cancel to all the promises it is expected to notify.

Modifications:
- If the future is cancelled then all the promises should be cancelled
- Add a UnaryPromiseNotifier if a collection of promises is not necessary

Result:
PromiseNotifier propagates cancel events to all promises
This commit is contained in:
Scott Mitchell 2016-02-12 10:23:39 -08:00
parent 6e9d2bf13c
commit 050ac709ba
3 changed files with 64 additions and 7 deletions

View File

@ -57,9 +57,13 @@ public class PromiseNotifier<V, F extends Future<V>> implements GenericFutureLis
logger.warn("Failed to mark a promise as success because it is done already: {}", p); logger.warn("Failed to mark a promise as success because it is done already: {}", p);
} }
} }
return; } else if (future.isCancelled()) {
for (Promise<? super V> p: promises) {
if (!p.cancel(false)) {
logger.warn("Failed to cancel a promise because it is done already: {}", p);
} }
}
} else {
Throwable cause = future.cause(); Throwable cause = future.cause();
for (Promise<? super V> p: promises) { for (Promise<? super V> p: promises) {
if (!p.tryFailure(cause)) { if (!p.tryFailure(cause)) {
@ -68,3 +72,4 @@ public class PromiseNotifier<V, F extends Future<V>> implements GenericFutureLis
} }
} }
} }
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2016 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.util.concurrent;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
public final class UnaryPromiseNotifier<T> implements FutureListener<T> {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(UnaryPromiseNotifier.class);
private final Promise<? super T> promise;
public UnaryPromiseNotifier(Promise<? super T> promise) {
this.promise = ObjectUtil.checkNotNull(promise, "promise");
}
@Override
public void operationComplete(Future<T> future) throws Exception {
cascadeTo(future, promise);
}
public static <X> void cascadeTo(Future<X> completedFuture, Promise<? super X> promise) {
if (completedFuture.isSuccess()) {
if (!promise.trySuccess(completedFuture.getNow())) {
logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
}
} else if (completedFuture.isCancelled()) {
if (!promise.cancel(false)) {
logger.warn("Failed to cancel a promise because it is done already: {}", promise);
}
} else {
if (!promise.tryFailure(completedFuture.cause())) {
logger.warn("Failed to mark a promise as failure because it's done already: {}", promise,
completedFuture.cause());
}
}
}
}

View File

@ -78,6 +78,7 @@ public class PromiseNotifierTest {
Future<Void> future = createStrictMock(Future.class); Future<Void> future = createStrictMock(Future.class);
Throwable t = createStrictMock(Throwable.class); Throwable t = createStrictMock(Throwable.class);
expect(future.isSuccess()).andReturn(false); expect(future.isSuccess()).andReturn(false);
expect(future.isCancelled()).andReturn(false);
expect(future.cause()).andReturn(t); expect(future.cause()).andReturn(t);
expect(p1.tryFailure(t)).andReturn(true); expect(p1.tryFailure(t)).andReturn(true);
expect(p2.tryFailure(t)).andReturn(true); expect(p2.tryFailure(t)).andReturn(true);