use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class AbstractPublisherOffloadingTest method testOffloading.
protected int testOffloading(BiFunction<Publisher<String>, Executor, Publisher<String>> offloadingFunction, TerminalOperation terminal) throws InterruptedException {
Runnable appCode = () -> {
try {
// Insert a custom value into AsyncContext map
AsyncContext.put(ASYNC_CONTEXT_CUSTOM_KEY, ASYNC_CONTEXT_VALUE);
capture(CaptureSlot.APP);
// Add thread recording test points
final Publisher<String> original = testPublisher.liftSync((PublisherOperator<? super String, String>) subscriber -> {
capture(CaptureSlot.OFFLOADED_SUBSCRIBE);
return subscriber;
}).beforeOnSubscribe(cancellable -> capture(CaptureSlot.ORIGINAL_ON_SUBSCRIBE)).beforeRequest((requested) -> capture(CaptureSlot.OFFLOADED_REQUEST)).beforeOnNext((item) -> capture(CaptureSlot.ORIGINAL_ON_NEXT)).beforeFinally(new TerminalSignalConsumer() {
@Override
public void onComplete() {
capture(CaptureSlot.ORIGINAL_ON_COMPLETE);
}
@Override
public void onError(final Throwable throwable) {
capture(CaptureSlot.ORIGINAL_ON_ERROR);
}
@Override
public void cancel() {
capture(CaptureSlot.OFFLOADED_CANCEL);
}
});
// Perform offloading and add more thread recording test points
Publisher<String> offloaded = offloadingFunction.apply(original, testExecutor.executor()).liftSync((PublisherOperator<? super String, String>) subscriber -> {
capture(CaptureSlot.ORIGINAL_SUBSCRIBE);
return subscriber;
}).beforeOnSubscribe(cancellable -> capture(CaptureSlot.OFFLOADED_ON_SUBSCRIBE)).beforeRequest((requested) -> capture(CaptureSlot.ORIGINAL_REQUEST)).beforeOnNext((item) -> capture(CaptureSlot.OFFLOADED_ON_NEXT)).beforeFinally(new TerminalSignalConsumer() {
@Override
public void onComplete() {
capture(CaptureSlot.OFFLOADED_ON_COMPLETE);
}
@Override
public void onError(final Throwable throwable) {
capture(CaptureSlot.OFFLOADED_ON_ERROR);
}
@Override
public void cancel() {
capture(CaptureSlot.ORIGINAL_CANCEL);
}
});
// subscribe
toSource(offloaded).subscribe(testSubscriber);
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded subscribe
testExecutor.executor().executeNextTask();
}
PublisherSource.Subscription subscription = testSubscriber.awaitSubscription();
assertThat("No Subscription", subscription, notNullValue());
testPublisher.awaitSubscribed();
assertThat("Source is not subscribed", testPublisher.isSubscribed());
assertThat("Thread was interrupted", !Thread.currentThread().isInterrupted());
// generate demand
subscription.request(MAX_VALUE);
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded request
testExecutor.executor().executeNextTask();
}
testSubscription.awaitRequestN(1);
// perform terminal
switch(terminal) {
case CANCEL:
subscription.cancel();
break;
case COMPLETE:
testPublisher.onNext(ITEM_VALUE);
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded onNext
testExecutor.executor().executeNextTask();
}
String result = testSubscriber.takeOnNext();
assertThat("result is unexpected value", result, sameInstance(ITEM_VALUE));
testPublisher.onComplete();
break;
case ERROR:
testPublisher.onError(DELIBERATE_EXCEPTION);
break;
default:
throw new AssertionError("unexpected terminal mode");
}
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded terminal
testExecutor.executor().executeNextTask();
}
} catch (Throwable all) {
AbstractOffloadingTest.LOGGER.warn("Unexpected throwable", all);
testSubscriber.onError(all);
}
};
APP_EXECUTOR_EXT.executor().execute(appCode);
// Ensure we reached the correct terminal condition
switch(terminal) {
case CANCEL:
testSubscription.awaitCancelled();
break;
case ERROR:
Throwable thrown = testSubscriber.awaitOnError();
assertThat("unexpected exception " + thrown, thrown, sameInstance(DELIBERATE_EXCEPTION));
break;
case COMPLETE:
testSubscriber.awaitOnComplete();
break;
default:
throw new AssertionError("unexpected terminal mode");
}
// Ensure that Async Context Map was correctly set during signals
ContextMap appMap = capturedContexts.captured(CaptureSlot.APP);
assertThat(appMap, notNullValue());
ContextMap subscribeMap = capturedContexts.captured(CaptureSlot.ORIGINAL_SUBSCRIBE);
assertThat(subscribeMap, notNullValue());
assertThat("Map was shared not copied", subscribeMap, not(sameInstance(appMap)));
assertThat("Missing custom async context entry ", subscribeMap.get(ASYNC_CONTEXT_CUSTOM_KEY), sameInstance(ASYNC_CONTEXT_VALUE));
EnumSet<CaptureSlot> checkSlots = EnumSet.complementOf(EnumSet.of(CaptureSlot.APP, CaptureSlot.ORIGINAL_SUBSCRIBE));
checkSlots.stream().filter(slot -> null != capturedContexts.captured(slot)).forEach(slot -> {
ContextMap map = capturedContexts.captured(slot);
assertThat("Context map was not captured", map, is(notNullValue()));
assertThat("Custom key missing from context map", map.containsKey(ASYNC_CONTEXT_CUSTOM_KEY));
assertThat("Unexpected context map @ slot " + slot + " : " + map, map, sameInstance(subscribeMap));
});
assertThat("Pending offloading", testExecutor.executor().queuedTasksPending(), is(0));
return testExecutor.executor().queuedTasksExecuted();
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class AbstractSingleOffloadingTest method testOffloading.
protected int testOffloading(BiFunction<Single<String>, Executor, Single<String>> offloadingFunction, TerminalOperation terminal) throws InterruptedException {
Runnable appCode = () -> {
try {
// Insert a custom value into AsyncContext map
AsyncContext.put(ASYNC_CONTEXT_CUSTOM_KEY, ASYNC_CONTEXT_VALUE);
capture(CaptureSlot.APP);
// Add thread recording test points
final Single<String> original = testSingle.liftSync((SingleOperator<String, String>) subscriber -> {
capture(CaptureSlot.OFFLOADED_SUBSCRIBE);
return subscriber;
}).beforeOnSubscribe(cancellable -> capture(CaptureSlot.ORIGINAL_ON_SUBSCRIBE)).beforeFinally(new TerminalSignalConsumer() {
@Override
public void onComplete() {
capture(CaptureSlot.ORIGINAL_ON_COMPLETE);
}
@Override
public void onError(final Throwable throwable) {
capture(CaptureSlot.ORIGINAL_ON_ERROR);
}
@Override
public void cancel() {
capture(CaptureSlot.OFFLOADED_CANCEL);
}
});
// Perform offloading and add more thread recording test points
Single<String> offloaded = offloadingFunction.apply(original, testExecutor.executor()).liftSync((SingleOperator<String, String>) subscriber -> {
capture(CaptureSlot.ORIGINAL_SUBSCRIBE);
return subscriber;
}).beforeOnSubscribe(cancellable -> capture(CaptureSlot.OFFLOADED_ON_SUBSCRIBE)).beforeFinally(new TerminalSignalConsumer() {
@Override
public void onComplete() {
capture(CaptureSlot.OFFLOADED_ON_COMPLETE);
}
@Override
public void onError(final Throwable throwable) {
capture(CaptureSlot.OFFLOADED_ON_ERROR);
}
@Override
public void cancel() {
capture(CaptureSlot.ORIGINAL_CANCEL);
}
});
// subscribe and generate terminal
toSource(offloaded).subscribe(testSubscriber);
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded subscribe
testExecutor.executor().executeNextTask();
}
Cancellable cancellable = testSubscriber.awaitSubscription();
assertThat("No Cancellable", cancellable, notNullValue());
testSingle.awaitSubscribed();
assertThat("Source is not subscribed", testSingle.isSubscribed());
assertThat("Thread was interrupted", !Thread.currentThread().isInterrupted());
switch(terminal) {
case CANCEL:
cancellable.cancel();
break;
case COMPLETE:
testSingle.onSuccess(ITEM_VALUE);
break;
case ERROR:
testSingle.onError(DELIBERATE_EXCEPTION);
break;
default:
throw new AssertionError("unexpected terminal mode");
}
assertThat("Unexpected tasks " + testExecutor.executor().queuedTasksPending(), testExecutor.executor().queuedTasksPending(), lessThan(2));
if (1 == testExecutor.executor().queuedTasksPending()) {
// execute offloaded terminal
testExecutor.executor().executeNextTask();
}
} catch (Throwable all) {
AbstractOffloadingTest.LOGGER.warn("Unexpected throwable", all);
testSubscriber.onError(all);
}
};
APP_EXECUTOR_EXT.executor().execute(appCode);
// Ensure we reached the correct terminal condition
switch(terminal) {
case CANCEL:
testCancellable.awaitCancelled();
break;
case ERROR:
Throwable thrown = testSubscriber.awaitOnError();
assertThat("unexpected exception " + thrown, thrown, sameInstance(DELIBERATE_EXCEPTION));
break;
case COMPLETE:
String result = testSubscriber.awaitOnSuccess();
assertThat("Unexpected result", result, sameInstance(ITEM_VALUE));
break;
default:
throw new AssertionError("unexpected terminal mode");
}
// Ensure that Async Context Map was correctly set during signals
ContextMap appMap = capturedContexts.captured(CaptureSlot.APP);
assertThat(appMap, notNullValue());
ContextMap subscribeMap = capturedContexts.captured(CaptureSlot.ORIGINAL_SUBSCRIBE);
assertThat(subscribeMap, notNullValue());
assertThat("Map was shared not copied", subscribeMap, not(sameInstance(appMap)));
assertThat("Missing custom async context entry ", subscribeMap.get(ASYNC_CONTEXT_CUSTOM_KEY), sameInstance(ASYNC_CONTEXT_VALUE));
EnumSet<CaptureSlot> checkSlots = EnumSet.complementOf(EnumSet.of(CaptureSlot.APP, CaptureSlot.ORIGINAL_SUBSCRIBE));
checkSlots.stream().filter(slot -> null != capturedContexts.captured(slot)).forEach(slot -> {
ContextMap map = capturedContexts.captured(slot);
assertThat("Context map was not captured", map, is(notNullValue()));
assertThat("Custom key missing from context map", map.containsKey(ASYNC_CONTEXT_CUSTOM_KEY));
assertThat("Unexpected context map @ slot " + slot + " : " + map, map, sameInstance(subscribeMap));
});
// Ensure that all offloading completed.
assertThat("Offloading pending", testExecutor.executor().queuedTasksPending(), is(0));
return testExecutor.executor().queuedTasksExecuted();
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class TimeoutPublisherTest method concurrentTimeoutInvocation.
@Test
void concurrentTimeoutInvocation() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<Throwable> causeRef = new AtomicReference<>();
// In order to simulate concurrent execution, we introduce an Executor that does not respect the delay for the
// first timer schedule. Internally, we expect the TimeoutPublisher to reschedule the timer. For that we use
// TestExecutor, which will allow us to advance the time and trigger the actual timeout, which will simulate
// concurrent execution.
toSource(publisher.timeout(10, MILLISECONDS, new Executor() {
private final AtomicInteger timerCount = new AtomicInteger();
@Override
public Cancellable schedule(final Runnable task, final long delay, final TimeUnit unit) {
int count = timerCount.incrementAndGet();
if (count <= 2) {
if (count == 1) {
try {
task.run();
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
latch.countDown();
} else {
try {
try {
testExecutor.schedule(task, delay, unit);
testExecutor.advanceTimeBy(delay, unit);
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
latch.countDown();
} catch (Throwable cause) {
causeRef.compareAndSet(null, cause);
countDownToZero(latch);
}
}
}
return IGNORE_CANCEL;
}
@Override
public long currentTime(final TimeUnit unit) {
return testExecutor.currentTime(unit);
}
@Override
public Completable closeAsync() {
throw new UnsupportedOperationException();
}
@Override
public Completable onClose() {
throw new UnsupportedOperationException();
}
@Override
public Cancellable execute(final Runnable task) throws RejectedExecutionException {
throw new UnsupportedOperationException();
}
})).subscribe(subscriber);
latch.await();
assertNull(causeRef.get());
assertThat(subscriber.awaitOnError(), instanceOf(TimeoutException.class));
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class NettyPipelinedConnectionTest method multiThreadedWritesAllComplete.
@Test
void multiThreadedWritesAllComplete() throws Exception {
// Avoid using EmbeddedChannel because it is not thread safe. This test writes/reads from multiple threads.
@SuppressWarnings("unchecked") NettyConnection<Integer, Integer> connection = mock(NettyConnection.class);
Executor connectionExecutor = Executors.newCachedThreadExecutor();
try {
doAnswer((Answer<Completable>) invocation -> {
Publisher<Integer> writeStream = invocation.getArgument(0);
return writeStream.ignoreElements().concat(connectionExecutor.submit(() -> {
}));
}).when(connection).write(any(), any(), any());
doAnswer((Answer<Publisher<Integer>>) invocation -> connectionExecutor.submit(() -> {
}).concat(Publisher.from(1))).when(connection).read();
final int concurrentRequestCount = 300;
NettyPipelinedConnection<Integer, Integer> pipelinedConnection = new NettyPipelinedConnection<>(connection, concurrentRequestCount);
CyclicBarrier requestStartBarrier = new CyclicBarrier(concurrentRequestCount);
List<Future<Collection<Integer>>> futures = new ArrayList<>(concurrentRequestCount);
ExecutorService executor = new ThreadPoolExecutor(0, concurrentRequestCount, 1, SECONDS, new SynchronousQueue<>());
try {
for (int i = 0; i < concurrentRequestCount; ++i) {
final int finalI = i;
futures.add(executor.submit(() -> {
try {
requestStartBarrier.await();
} catch (Exception e) {
return Single.<Collection<Integer>>failed(new AssertionError("failure during request " + finalI, e)).toFuture().get();
}
return pipelinedConnection.write(Publisher.from(finalI)).toFuture().get();
}));
}
for (Future<Collection<Integer>> future : futures) {
assertThat(future.get(), hasSize(1));
}
} finally {
executor.shutdown();
}
} finally {
connectionExecutor.closeAsync().subscribe();
}
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class LingeringRoundRobinLoadBalancerTest method expiringAHostDoesntRaceWithConnectionAdding.
// Concurrency test, worth running >10K times to spot concurrency issues.
@Test
void expiringAHostDoesntRaceWithConnectionAdding() throws Exception {
Executor executor = Executors.newFixedSizeExecutor(1);
try {
sendServiceDiscoveryEvents(upEvent("address-1"));
assertConnectionCount(lb.usedAddresses(), connectionsCount("address-1", 0));
AtomicReference<Exception> e = new AtomicReference<>();
AtomicReference<TestLoadBalancedConnection> connection = new AtomicReference<>();
Future<Object> f = executor.submit(() -> {
try {
connection.set(lb.selectConnection(alwaysNewConnectionFilter()).toFuture().get());
} catch (Exception ex) {
e.set(ex);
}
return null;
}).toFuture();
sendServiceDiscoveryEvents(downEvent("address-1"));
f.get();
Exception thrown = e.get();
if (thrown != null) {
// Connection was not added - SD event arrived before the attempt of adding the connection
assertThat(thrown, instanceOf(ExecutionException.class));
// Either the host was already CLOSED and removed from the usedHosts collection:
assertThat(thrown.getCause(), either(instanceOf(NoAvailableHostException.class)).or(instanceOf(ConnectionRejectedException.class)));
assertAddresses(lb.usedAddresses(), EMPTY_ARRAY);
assertNull(connection.get());
} else {
// Connection was added first -> Let's validate the host was properly EXPIRED:
assertConnectionCount(lb.usedAddresses(), connectionsCount("address-1", 1));
// Confirm host is expired:
ExecutionException ex = assertThrows(ExecutionException.class, () -> lb.selectConnection(alwaysNewConnectionFilter()).toFuture().get());
assertThat(ex.getCause(), instanceOf(NoAvailableHostException.class));
lb.closeAsyncGracefully().toFuture().get();
verify(connection.get(), times(1)).closeAsyncGracefully();
}
} finally {
executor.closeAsync().toFuture().get();
}
}
Aggregations