use of io.servicetalk.concurrent.api.Publisher 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.Publisher in project servicetalk by apple.
the class HelloWorldJaxRsResource method randomHello.
/**
* Resource that only relies on {@link Single}/{@link Publisher} for consuming and producing data,
* and returns a JAX-RS {@link Response} in order to set its status.
* No OIO adaptation is involved when requests are dispatched to it,
* allowing it to fully benefit from ReactiveStream's features like flow control.
* Behind the scene, ServiceTalk's aggregation mechanism is used to provide the resource with a
* {@link Single Single<Buffer>} that contains the whole request entity as a {@link Buffer}.
* Note that the {@link ConnectionContext} could also be injected into a class-level {@code @Context} field.
* <p>
* Test with:
* <pre>
* curl -i -H 'content-type: text/plain' -d 'kitty' http://localhost:8080/greetings/random-hello
* </pre>
*
* @param who the recipient of the greetings.
* @param ctx the {@link ConnectionContext}.
* @return greetings as a JAX-RS {@link Response}.
*/
@POST
@Path("random-hello")
@Consumes(TEXT_PLAIN)
@Produces(TEXT_PLAIN)
public Response randomHello(final Single<Buffer> who, @Context final ConnectionContext ctx) {
if (random() < .5) {
return accepted("greetings accepted, call again for a response").build();
}
final BufferAllocator allocator = ctx.executionContext().bufferAllocator();
final Publisher<Buffer> payload = from(allocator.fromAscii("hello ")).concat(who);
// Wrap content Publisher to capture its generic type (i.e. Buffer) so it is handled correctly
final GenericEntity<Publisher<Buffer>> entity = new GenericEntity<Publisher<Buffer>>(payload) {
};
return ok(entity).build();
}
use of io.servicetalk.concurrent.api.Publisher in project servicetalk by apple.
the class HttpDataSourceTransformations method aggregatePayloadAndTrailers.
static Single<PayloadAndTrailers> aggregatePayloadAndTrailers(final DefaultPayloadInfo payloadInfo, final Publisher<?> payloadAndTrailers, final BufferAllocator allocator) {
if (payloadAndTrailers == empty()) {
payloadInfo.setEmpty(true).setMayHaveTrailersAndGenericTypeBuffer(false);
return succeeded(EMPTY_PAYLOAD_AND_TRAILERS);
}
return payloadAndTrailers.collect(PayloadAndTrailers::new, (pair, nextItem) -> {
if (nextItem instanceof Buffer) {
try {
Buffer buffer = (Buffer) nextItem;
if (isAlwaysEmpty(pair.payload)) {
pair.payload = buffer;
} else if (pair.payload instanceof CompositeBuffer) {
((CompositeBuffer) pair.payload).addBuffer(buffer);
} else {
Buffer oldBuffer = pair.payload;
pair.payload = allocator.newCompositeBuffer(MAX_VALUE).addBuffer(oldBuffer).addBuffer(buffer);
}
} catch (IllegalArgumentException cause) {
BufferOverflowException ex = new BufferOverflowException();
ex.initCause(cause);
throw ex;
}
} else if (nextItem instanceof HttpHeaders) {
pair.trailers = (HttpHeaders) nextItem;
} else {
throw new UnsupportedHttpChunkException(nextItem);
}
return pair;
}).map(pair -> {
if (isAlwaysEmpty(pair.payload)) {
payloadInfo.setEmpty(true);
}
if (pair.trailers == null) {
payloadInfo.setMayHaveTrailersAndGenericTypeBuffer(false);
}
return pair;
});
}
use of io.servicetalk.concurrent.api.Publisher in project servicetalk by apple.
the class NettyPipelinedConnectionTest method writeThrowsClosesConnection.
@Test
void writeThrowsClosesConnection() {
TestPublisher<Integer> mockReadPublisher2 = new TestPublisher<>();
@SuppressWarnings("unchecked") NettyConnection<Integer, Integer> mockConnection = mock(NettyConnection.class);
doAnswer((Answer<Publisher<Integer>>) invocation -> mockReadPublisher2).when(mockConnection).read();
doAnswer((Answer<Completable>) invocation -> {
throw DELIBERATE_EXCEPTION;
}).when(mockConnection).write(eq(writePublisher1), any(), any());
doAnswer((Answer<Completable>) invocation -> {
Publisher<Integer> writePub = invocation.getArgument(0);
return writePub.ignoreElements();
}).when(mockConnection).write(eq(writePublisher2), any(), any());
when(mockConnection.closeAsync()).thenReturn(completed());
requester = new NettyPipelinedConnection<>(mockConnection, 2);
toSource(requester.write(writePublisher1)).subscribe(readSubscriber);
toSource(requester.write(writePublisher2)).subscribe(readSubscriber2);
Subscription readSubscription = readSubscriber.awaitSubscription();
readSubscription.request(1);
assertThat(readSubscriber.awaitOnError(), is(DELIBERATE_EXCEPTION));
assertFalse(writePublisher1.isSubscribed());
verify(mockConnection).closeAsync();
}
use of io.servicetalk.concurrent.api.Publisher in project servicetalk by apple.
the class NettyPipelinedConnectionTest method readSubscribeThrowsWritesStillProcessed.
@Test
void readSubscribeThrowsWritesStillProcessed() {
AtomicBoolean thrownError = new AtomicBoolean();
Publisher<Integer> mockReadPublisher = new Publisher<Integer>() {
@Override
protected void handleSubscribe(final PublisherSource.Subscriber<? super Integer> subscriber) {
if (thrownError.compareAndSet(false, true)) {
throw DELIBERATE_EXCEPTION;
} else {
deliverCompleteFromSource(subscriber);
}
}
};
@SuppressWarnings("unchecked") NettyConnection<Integer, Integer> mockConnection = mock(NettyConnection.class);
when(mockConnection.read()).thenReturn(mockReadPublisher);
doAnswer((Answer<Completable>) invocation -> {
Publisher<Integer> writePub = invocation.getArgument(0);
return writePub.ignoreElements();
}).when(mockConnection).write(any(), any(), any());
requester = new NettyPipelinedConnection<>(mockConnection, 2);
toSource(requester.write(writePublisher1)).subscribe(readSubscriber);
toSource(requester.write(writePublisher2)).subscribe(readSubscriber2);
Subscription readSubscription = readSubscriber.awaitSubscription();
readSubscription.request(1);
assertTrue(writePublisher1.isSubscribed());
writePublisher1.onError(newSecondException());
assertThat(readSubscriber.awaitOnError(), is(DELIBERATE_EXCEPTION));
readSubscriber2.awaitSubscription();
assertTrue(writePublisher2.isSubscribed());
writePublisher2.onComplete();
readSubscriber2.awaitOnComplete();
verify(mockConnection, never()).closeAsync();
}
Aggregations