use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class RedirectingHttpRequesterFilterTest method newClient.
private StreamingHttpClient newClient(RedirectConfig config, StreamingHttpClientFilterFactory... other) {
StreamingHttpClientFilterFactory result = new RedirectingHttpRequesterFilter(config);
for (StreamingHttpClientFilterFactory next : other) {
result = appendClientFilterFactory(result, next);
}
StreamingHttpClientFilterFactory mockResponse = client -> new StreamingHttpClientFilter(client) {
@Override
protected Single<StreamingHttpResponse> request(final StreamingHttpRequester delegate, final StreamingHttpRequest request) {
return httpClient.request(request);
}
};
return from(reqRespFactory, mock(HttpExecutionContext.class), appendClientFilterFactory(result, mockResponse));
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class RoundRobinLoadBalancer method selectConnection0.
private Single<C> selectConnection0(final Predicate<C> selector, @Nullable final ContextMap context) {
final List<Host<ResolvedAddress, C>> usedHosts = this.usedHosts;
if (usedHosts.isEmpty()) {
return usedHosts == CLOSED_LIST ? failedLBClosed(targetResource) : // This is the case when SD has emitted some items but none of the hosts are available.
failed(StacklessNoAvailableHostException.newInstance("No hosts are available to connect for " + targetResource + ".", RoundRobinLoadBalancer.class, "selectConnection0(...)"));
}
// try one loop over hosts and if all are expired, give up
final int cursor = (indexUpdater.getAndIncrement(this) & Integer.MAX_VALUE) % usedHosts.size();
final ThreadLocalRandom rnd = ThreadLocalRandom.current();
Host<ResolvedAddress, C> pickedHost = null;
for (int i = 0; i < usedHosts.size(); ++i) {
// for a particular iteration we maintain a local cursor without contention with other requests
final int localCursor = (cursor + i) % usedHosts.size();
final Host<ResolvedAddress, C> host = usedHosts.get(localCursor);
assert host != null : "Host can't be null.";
// Try first to see if an existing connection can be used
final Object[] connections = host.connState.connections;
// Exhaust the linear search space first:
final int linearAttempts = min(connections.length, linearSearchSpace);
for (int j = 0; j < linearAttempts; ++j) {
@SuppressWarnings("unchecked") final C connection = (C) connections[j];
if (selector.test(connection)) {
return succeeded(connection);
}
}
// Try other connections randomly:
if (connections.length > linearAttempts) {
final int diff = connections.length - linearAttempts;
// With small enough search space, attempt number of times equal to number of remaining connections.
// Back off after exploring most of the search space, it gives diminishing returns.
final int randomAttempts = diff < MIN_RANDOM_SEARCH_SPACE ? diff : (int) (diff * RANDOM_SEARCH_FACTOR);
for (int j = 0; j < randomAttempts; ++j) {
@SuppressWarnings("unchecked") final C connection = (C) connections[rnd.nextInt(linearAttempts, connections.length)];
if (selector.test(connection)) {
return succeeded(connection);
}
}
}
// Unhealthy hosts have no open connections – that's why we don't fail earlier, the loop will not progress.
if (host.isActiveAndHealthy()) {
pickedHost = host;
break;
}
}
if (pickedHost == null) {
return failed(StacklessNoAvailableHostException.newInstance("Failed to pick an active host for " + targetResource + ". Either all are busy, expired, or unhealthy: " + usedHosts, RoundRobinLoadBalancer.class, "selectConnection0(...)"));
}
// No connection was selected: create a new one.
final Host<ResolvedAddress, C> host = pickedHost;
// This LB implementation does not automatically provide TransportObserver. Therefore, we pass "null" here.
// Users can apply a ConnectionFactoryFilter if they need to override this "null" value with TransportObserver.
Single<? extends C> establishConnection = connectionFactory.newConnection(host.address, context, null);
if (host.healthCheckConfig != null) {
// Schedule health check before returning
establishConnection = establishConnection.beforeOnError(t -> host.markUnhealthy(t, connectionFactory));
}
return establishConnection.flatMap(newCnx -> {
// used concurrently and hence a new connection can be rejected by the selector.
if (!selector.test(newCnx)) {
// with the fact that select failure does not close a connection.
return newCnx.closeAsync().concat(failed(StacklessConnectionRejectedException.newInstance("Newly created connection " + newCnx + " for " + targetResource + " was rejected by the selection filter.", RoundRobinLoadBalancer.class, "selectConnection0(...)")));
}
if (host.addConnection(newCnx)) {
return succeeded(newCnx);
}
return newCnx.closeAsync().concat(this.usedHosts == CLOSED_LIST ? failedLBClosed(targetResource) : failed(StacklessConnectionRejectedException.newInstance("Failed to add newly created connection " + newCnx + " for " + targetResource + " for " + host, RoundRobinLoadBalancer.class, "selectConnection0(...)")));
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class CancellationTest method testCancelResponseSingle.
private void testCancelResponseSingle(final StreamingHttpRequest req, boolean enableOffload) throws Exception {
final AtomicReference<Throwable> errorRef = new AtomicReference<>();
final CountDownLatch cancelledLatch = new CountDownLatch(1);
// different threads because the write operation will block on the Subscriber creating requestN demand.
if (enableOffload) {
Single<StreamingHttpResponse> respSingle = execRule.executor().submit(() -> jerseyRouter.handle(ctx, req, HTTP_REQ_RES_FACTORY)).flatMap(identity()).beforeOnError((err) -> {
// Ignore racy cancellation, it's ordered safely.
if (!(err instanceof IllegalStateException)) {
errorRef.compareAndSet(null, err);
}
}).afterCancel(cancelledLatch::countDown);
toSource(respSingle).subscribe(new SingleSource.Subscriber<StreamingHttpResponse>() {
@Override
public void onSubscribe(final Cancellable cancellable) {
cancellable.cancel();
}
@Override
public void onSuccess(@Nullable final StreamingHttpResponse result) {
if (result == null) {
errorRef.compareAndSet(null, new NullPointerException("result == null not expected."));
cancelledLatch.countDown();
} else {
result.messageBody().ignoreElements().afterFinally(cancelledLatch::countDown).subscribe();
}
}
@Override
public void onError(final Throwable t) {
// Ignore racy cancellation, it's ordered safely.
if (!(t instanceof IllegalStateException)) {
errorRef.compareAndSet(null, t);
}
cancelledLatch.countDown();
}
});
} else {
jerseyRouter.handle(ctx, req, HTTP_REQ_RES_FACTORY).beforeOnError(errorRef::set).afterCancel(cancelledLatch::countDown).ignoreElement().subscribe().cancel();
}
cancelledLatch.await();
final Throwable error = errorRef.get();
if (error != null) {
throw new AssertionError(error);
}
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class AbstractTimeoutHttpFilter method withTimeout.
/**
* Returns the response single for the provided request which will be completed within the specified timeout or
* generate an error with a timeout exception. The timer begins when the {@link Single} is subscribed. The timeout
* action, if it occurs, will execute on the filter's chosen timeout executor, or if none is specified, the executor
* from the request or connection context or, if neither are specified, the global executor.
*
* @param request The request requiring a response.
* @param responseFunction Function which generates the response.
* @param contextExecutor Executor from the request/connection context to be used for the timeout terminal signal if
* no specific timeout executor is defined for filter.
* @return response single
*/
final Single<StreamingHttpResponse> withTimeout(final StreamingHttpRequest request, final Function<StreamingHttpRequest, Single<StreamingHttpResponse>> responseFunction, final Executor contextExecutor) {
final Executor useForTimeout = null != this.timeoutExecutor ? this.timeoutExecutor : contextExecutor;
return Single.defer(() -> {
final Duration timeout = timeoutForRequest.apply(request, useForTimeout);
Single<StreamingHttpResponse> response = responseFunction.apply(request);
if (null != timeout) {
final Single<StreamingHttpResponse> timeoutResponse = response.timeout(timeout, useForTimeout);
if (fullRequestResponse) {
final long deadline = useForTimeout.currentTime(NANOSECONDS) + timeout.toNanos();
response = timeoutResponse.map(resp -> resp.transformMessageBody(body -> defer(() -> {
final Duration remaining = ofNanos(deadline - useForTimeout.currentTime(NANOSECONDS));
return (body.timeoutTerminal(remaining, useForTimeout)).onErrorMap(TimeoutException.class, t -> new MappedTimeoutException("message body timeout after " + timeout.toMillis() + "ms", t)).shareContextOnSubscribe();
})));
} else {
response = timeoutResponse;
}
}
return response.shareContextOnSubscribe();
});
}
use of io.servicetalk.concurrent.api.Single in project servicetalk by apple.
the class ProtobufSerializerMessageBodyReaderWriter method writeTo.
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void writeTo(final Object o, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType, final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws WebApplicationException {
final BufferAllocator allocator = ctxRefProvider.get().get().executionContext().bufferAllocator();
final Publisher<Buffer> bufferPublisher;
if (o instanceof Single) {
final Class<? extends MessageLite> clazz = genericType instanceof Class ? (Class) genericType : getSourceClass(genericType);
Serializer serializer = getSerializerFactory(mediaType).serializerDeserializer(clazz);
bufferPublisher = ((Single) o).map(t -> serializer.serialize(t, allocator)).toPublisher();
} else if (o instanceof Publisher) {
final Class<? extends MessageLite> clazz = genericType instanceof Class ? (Class) genericType : getSourceClass(genericType);
StreamingSerializer serializer = getSerializerFactory(mediaType).streamingSerializerDeserializer(clazz);
bufferPublisher = serializer.serialize((Publisher) o, allocator);
} else {
Serializer serializer = getSerializerFactory(mediaType).serializerDeserializer((Class<? extends MessageLite>) o.getClass());
bufferPublisher = Publisher.from(serializer.serialize(o, allocator));
}
setResponseBufferPublisher(bufferPublisher, requestCtxProvider.get());
}
Aggregations