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 StreamingHttpServiceToOffloadedStreamingHttpService method handle.
@Override
public Single<StreamingHttpResponse> handle(final HttpServiceContext ctx, StreamingHttpRequest request, final StreamingHttpResponseFactory responseFactory) {
// We compute the difference between the ExecutionStrategy from the current ExecutionContext and
// this ExecutionStrategy to understand if we need to offload more than we already offloaded:
final HttpExecutionStrategy additionalOffloads = ctx.executionContext().executionStrategy().missing(strategy);
Executor useExecutor = null != executor ? executor : ctx.executionContext().executor();
// The service should see this ExecutionStrategy and Executor inside the ExecutionContext:
final HttpServiceContext wrappedCtx = new ExecutionContextOverridingServiceContext(ctx, strategy, useExecutor);
if (!additionalOffloads.isRequestResponseOffloaded()) {
// No additional offloading needed.
return delegate.handle(wrappedCtx, request, responseFactory);
} else {
if (additionalOffloads.isDataReceiveOffloaded()) {
request = request.transformMessageBody(p -> p.publishOn(useExecutor, shouldOffload));
}
final Single<StreamingHttpResponse> resp;
if (additionalOffloads.isMetadataReceiveOffloaded() && shouldOffload.getAsBoolean()) {
final StreamingHttpRequest r = request;
resp = useExecutor.submit(() -> delegate.handle(wrappedCtx, r, responseFactory).shareContextOnSubscribe()).flatMap(identity());
} else {
resp = delegate.handle(wrappedCtx, request, responseFactory);
}
return additionalOffloads.isSendOffloaded() ? // contract and hence have to offload both meta and data separately.
resp.map(r -> r.transformMessageBody(p -> p.subscribeOn(useExecutor, shouldOffload))).subscribeOn(useExecutor, shouldOffload) : resp;
}
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class AbstractNettyHttpServerTest method startServer.
private void startServer() throws Exception {
final InetSocketAddress bindAddress = localAddress(0);
service(new TestServiceStreaming(publisherSupplier));
// A small SNDBUF is needed to test that the server defers closing the connection until writes are complete.
// However, if it is too small, tests that expect certain chunks of data will see those chunks broken up
// differently.
final HttpServerBuilder serverBuilder = HttpServers.forAddress(bindAddress).executor(serverExecutor).socketOption(StandardSocketOptions.SO_SNDBUF, 100).protocols(protocol).transportObserver(serverTransportObserver).enableWireLogging("servicetalk-tests-wire-logger", TRACE, () -> true);
configureServerBuilder(serverBuilder);
if (sslEnabled) {
serverBuilder.sslConfig(new ServerSslConfigBuilder(DefaultTestCerts::loadServerPem, DefaultTestCerts::loadServerKey).build());
}
if (nonOffloadingServiceFilterFactory != null) {
serverBuilder.appendNonOffloadingServiceFilter(nonOffloadingServiceFilterFactory);
}
if (serviceFilterFactory != null) {
serverBuilder.appendServiceFilter(serviceFilterFactory);
}
if (serverLifecycleObserver != NoopHttpLifecycleObserver.INSTANCE) {
serverBuilder.lifecycleObserver(serverLifecycleObserver);
}
serverContext = awaitIndefinitelyNonNull(listen(serverBuilder.ioExecutor(serverIoExecutor).appendConnectionAcceptorFilter(original -> new DelegatingConnectionAcceptor(connectionAcceptor))).beforeOnSuccess(ctx -> LOGGER.debug("Server started on {}.", ctx.listenAddress())).beforeOnError(throwable -> LOGGER.debug("Failed starting server on {}.", bindAddress)));
final SingleAddressHttpClientBuilder<HostAndPort, InetSocketAddress> clientBuilder = newClientBuilder();
if (sslEnabled) {
clientBuilder.sslConfig(new ClientSslConfigBuilder(DefaultTestCerts::loadServerCAPem).peerHost(serverPemHostname()).build());
}
if (connectionFactoryFilter != null) {
clientBuilder.appendConnectionFactoryFilter(connectionFactoryFilter);
}
if (connectionFilterFactory != null) {
clientBuilder.appendConnectionFilter(connectionFilterFactory);
}
if (clientTransportObserver != NoopTransportObserver.INSTANCE) {
clientBuilder.appendConnectionFactoryFilter(new TransportObserverConnectionFactoryFilter<>(clientTransportObserver));
}
if (clientLifecycleObserver != NoopHttpLifecycleObserver.INSTANCE) {
clientBuilder.appendClientFilter(new HttpLifecycleObserverRequesterFilter(clientLifecycleObserver));
}
if (clientFilterFactory != null) {
clientBuilder.appendClientFilter(clientFilterFactory);
}
httpClient = clientBuilder.ioExecutor(clientIoExecutor).executor(clientExecutor).executionStrategy(defaultStrategy()).protocols(protocol).enableWireLogging("servicetalk-tests-wire-logger", TRACE, Boolean.TRUE::booleanValue).buildStreaming();
httpConnection = httpClient.reserveConnection(httpClient.get("/")).toFuture().get();
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class LingeringRoundRobinLoadBalancerTest method closureOfLastConnectionDoesntRaceWithNewAvailableEvent.
// Concurrency test, worth running ~10K times to spot concurrency issues.
@Test
void closureOfLastConnectionDoesntRaceWithNewAvailableEvent() throws Exception {
Executor executor = Executors.newFixedSizeExecutor(1);
try {
sendServiceDiscoveryEvents(upEvent("address-1"));
TestLoadBalancedConnection conn = lb.selectConnection(alwaysNewConnectionFilter(), null).toFuture().get();
sendServiceDiscoveryEvents(downEvent("address-1"));
assertConnectionCount(lb.usedAddresses(), connectionsCount("address-1", 1));
Future<Object> f = executor.submit(() -> {
sendServiceDiscoveryEvents(upEvent("address-1"));
return null;
}).toFuture();
conn.closeAsync().toFuture().get();
f.get();
assertConnectionCount(lb.usedAddresses(), connectionsCount("address-1", 0));
} finally {
executor.closeAsync().toFuture().get();
}
}
use of io.servicetalk.concurrent.api.Executor in project servicetalk by apple.
the class RoundRobinLoadBalancerTest method hostUnhealthyDoesntRaceToRunHealthCheck.
// Concurrency test, run multiple times (at least 1000).
@Test
void hostUnhealthyDoesntRaceToRunHealthCheck() throws Exception {
serviceDiscoveryPublisher.onComplete();
final Single<TestLoadBalancedConnection> properConnection = newRealizedConnectionSingle("address-1");
final int timeAdvancementsTillHealthy = 3;
final UnhealthyHostConnectionFactory unhealthyHostConnectionFactory = new UnhealthyHostConnectionFactory("address-1", timeAdvancementsTillHealthy, properConnection);
final DelegatingConnectionFactory connectionFactory = unhealthyHostConnectionFactory.createFactory();
lb = defaultLb(connectionFactory);
sendServiceDiscoveryEvents(upEvent("address-1"));
// Imitate concurrency by running multiple threads attempting to establish connections.
ExecutorService executor = Executors.newFixedThreadPool(3);
try {
final Runnable runnable = () -> assertThrows(ExecutionException.class, () -> lb.selectConnection(any(), null).toFuture().get());
for (int i = 0; i < 1000; i++) {
executor.submit(runnable);
}
// From test main thread, wait until the host becomes UNHEALTHY, which is apparent from
// NoHostAvailableException being thrown from selection AFTER a health check was scheduled by any thread.
final Executor executorForRetries = io.servicetalk.concurrent.api.Executors.newFixedSizeExecutor(1);
try {
awaitIndefinitely(lb.selectConnection(any(), null).retryWhen(retryWithConstantBackoffFullJitter((t) -> t instanceof DeliberateException || testExecutor.scheduledTasksPending() == 0, // try to prevent stack overflow
Duration.ofMillis(30), executorForRetries)));
} catch (Exception e) {
assertThat(e.getCause(), instanceOf(NoAvailableHostException.class));
} finally {
executorForRetries.closeAsync().toFuture().get();
}
// is not selected. If our assumption doesn't hold, it means more than one health check was scheduled.
for (int i = 0; i < timeAdvancementsTillHealthy - 1; ++i) {
unhealthyHostConnectionFactory.advanceTime(testExecutor);
// Assert still unhealthy
Exception e = assertThrows(ExecutionException.class, () -> lb.selectConnection(any(), null).toFuture().get());
assertThat(e.getCause(), instanceOf(NoAvailableHostException.class));
}
} finally {
// Shutdown the concurrent validation of unhealthiness.
executor.shutdownNow();
executor.awaitTermination(10, SECONDS);
}
unhealthyHostConnectionFactory.advanceTime(testExecutor);
final TestLoadBalancedConnection selectedConnection = lb.selectConnection(any(), null).toFuture().get();
assertThat(selectedConnection, equalTo(properConnection.toFuture().get()));
}
Aggregations