use of com.palantir.dialogue.Response in project dialogue by palantir.
the class QueuedChannelTest method testQueuedResponseAvoidsExecutingCancelled.
@Test
public void testQueuedResponseAvoidsExecutingCancelled() {
Request queued = Request.builder().putHeaderParams("key", "val").build();
when(delegate.maybeExecute(endpoint, queued, DO_NOT_SKIP_LIMITS)).thenReturn(Optional.empty());
ListenableFuture<Response> result = queuedChannel.maybeExecute(endpoint, queued).get();
verify(delegate, times(2)).maybeExecute(endpoint, queued, DO_NOT_SKIP_LIMITS);
assertThat(result.cancel(true)).isTrue();
when(delegate.maybeExecute(endpoint, request, DO_NOT_SKIP_LIMITS)).thenReturn(Optional.of(Futures.immediateFuture(Mockito.mock(Response.class))));
queuedChannel.maybeExecute(endpoint, request);
verify(delegate, times(1)).maybeExecute(endpoint, request, DO_NOT_SKIP_LIMITS);
// Should not have been invoked any more.
verify(delegate, times(2)).maybeExecute(endpoint, queued, DO_NOT_SKIP_LIMITS);
}
use of com.palantir.dialogue.Response in project dialogue by palantir.
the class QueuedChannelTest method testQueuedRequestExecutedOnNextSubmission_throws.
@Test
public void testQueuedRequestExecutedOnNextSubmission_throws() throws ExecutionException, InterruptedException {
// First request is limited by the channel and queued
Request queuedRequest = Mockito.mock(Request.class);
when(delegate.maybeExecute(endpoint, queuedRequest, DO_NOT_SKIP_LIMITS)).thenReturn(Optional.empty());
ListenableFuture<Response> queuedFuture = queuedChannel.maybeExecute(endpoint, queuedRequest).get();
verify(delegate, times(2)).maybeExecute(endpoint, queuedRequest, DO_NOT_SKIP_LIMITS);
assertThat(queuedFuture).isNotDone();
// Second request succeeds and the queued request is attempted, but throws an exception
futureResponse.set(mockResponse);
when(delegate.maybeExecute(endpoint, request, DO_NOT_SKIP_LIMITS)).thenReturn(maybeResponse);
when(delegate.maybeExecute(endpoint, queuedRequest, DO_NOT_SKIP_LIMITS)).thenThrow(new NullPointerException("expected"));
ListenableFuture<Response> completed = queuedChannel.maybeExecute(endpoint, request).get();
// Both results should be completed. The thrown exception should
// be converted into a failed future by NeverThrowLimitedChannel
assertThat(completed).isDone();
assertThat(queuedFuture).isDone();
assertThat(completed.get()).isEqualTo(mockResponse);
assertThatThrownBy(queuedFuture::get).hasRootCauseMessage("expected");
verify(delegate, times(1)).maybeExecute(endpoint, request, DO_NOT_SKIP_LIMITS);
verify(delegate, times(3)).maybeExecute(endpoint, queuedRequest, DO_NOT_SKIP_LIMITS);
}
use of com.palantir.dialogue.Response in project dialogue by palantir.
the class PinUntilErrorNodeSelectionStrategyChannel method maybeExecute.
@Override
public Optional<ListenableFuture<Response>> maybeExecute(Endpoint endpoint, Request request, LimitEnforcement limitEnforcement) {
int pin = currentPin.get();
PinChannel channel = nodeList.get(pin);
// n.b. StickyAttachments.maybeExecute uses the delegate PinChannel, which bypasses the FutureCallback
// instrumentation below on subsequent "sticky" requests.
Optional<ListenableFuture<Response>> maybeResponse = StickyAttachments.maybeAddStickyToken(channel, endpoint, request, limitEnforcement);
if (!maybeResponse.isPresent()) {
return Optional.empty();
}
DialogueFutures.addDirectCallback(maybeResponse.get(), new FutureCallback<>() {
@Override
public void onSuccess(Response response) {
// even if a couple of them get rate limited in the middle.
if (Responses.isServerErrorRange(response) || (Responses.isQosStatus(response) && !Responses.isTooManyRequests(response))) {
OptionalInt next = incrementHostIfNecessary(pin);
instrumentation.receivedErrorStatus(pin, channel, response, next);
} else {
instrumentation.successfulResponse(channel.stableIndex());
}
}
@Override
public void onFailure(Throwable throwable) {
OptionalInt next = incrementHostIfNecessary(pin);
instrumentation.receivedThrowable(pin, channel, throwable, next);
}
});
return maybeResponse;
}
use of com.palantir.dialogue.Response in project dialogue by palantir.
the class QueuedChannel method scheduleNextTask.
/**
* Get the next call and attempt to execute it. If it is runnable, wire up the underlying future to the one
* previously returned to the caller. If it is not runnable, add it back into the queue. Returns true if more
* tasks may be able to be scheduled, and false otherwise.
*/
private boolean scheduleNextTask() {
DeferredCall queueHead = queuedCalls.poll();
if (queueHead == null) {
return false;
}
SettableFuture<Response> queuedResponse = queueHead.response();
// request will be quickly cancelled in that case.
if (queuedResponse.isDone()) {
decrementQueueSize();
queueHead.span().complete();
queueHead.timer().stop();
return true;
}
try (CloseableSpan ignored = queueHead.span().attach()) {
Endpoint endpoint = queueHead.endpoint();
Optional<ListenableFuture<Response>> maybeResponse = delegate.maybeExecute(endpoint, queueHead.request(), DO_NOT_SKIP_LIMITS);
if (maybeResponse.isPresent()) {
decrementQueueSize();
ListenableFuture<Response> response = maybeResponse.get();
queueHead.span().complete();
queueHead.timer().stop();
DialogueFutures.addDirectCallback(response, new ForwardAndSchedule(queuedResponse));
DialogueFutures.addDirectListener(queuedResponse, () -> {
if (queuedResponse.isCancelled()) {
// Currently cancel(false) will be converted to cancel(true)
if (!response.cancel(true) && log.isDebugEnabled()) {
log.debug("Failed to cancel delegate response, it should be reported by ForwardAndSchedule " + "logging", SafeArg.of("channel", channelName), SafeArg.of("service", endpoint.serviceName()), SafeArg.of("endpoint", endpoint.endpointName()));
}
}
});
return true;
} else {
if (!queuedCalls.offerFirst(queueHead)) {
// Should never happen, ConcurrentLinkedDeque has no maximum size
log.error("Failed to add an attempted call back to the deque", SafeArg.of("channel", channelName), SafeArg.of("service", endpoint.serviceName()), SafeArg.of("endpoint", endpoint.endpointName()));
decrementQueueSize();
queueHead.timer().stop();
if (!queuedResponse.setException(new SafeRuntimeException("Failed to req-queue request", SafeArg.of("channel", channelName), SafeArg.of("service", endpoint.serviceName()), SafeArg.of("endpoint", endpoint.endpointName())))) {
if (log.isDebugEnabled()) {
log.debug("Queued response has already been completed", SafeArg.of("channel", channelName), SafeArg.of("service", endpoint.serviceName()), SafeArg.of("endpoint", endpoint.endpointName()));
}
}
}
return false;
}
}
}
use of com.palantir.dialogue.Response in project dialogue by palantir.
the class QueuedChannel method maybeExecute.
/**
* Enqueues and tries to schedule as many queued tasks as possible.
*/
@VisibleForTesting
@SuppressWarnings("PreferJavaTimeOverload")
Optional<ListenableFuture<Response>> maybeExecute(Endpoint endpoint, Request request) {
// Queuing adds contention between threads and should be avoided unless we need to shed load.
if (queueSizeEstimate.get() <= 0) {
Optional<ListenableFuture<Response>> maybeResult = delegate.maybeExecute(endpoint, request, DO_NOT_SKIP_LIMITS);
if (maybeResult.isPresent()) {
ListenableFuture<Response> result = maybeResult.get();
DialogueFutures.addDirectListener(result, this::onCompletion);
// While the queue was avoid, this is equivalent to spending zero time on the queue.
if (shouldRecordQueueMetrics) {
queuedTime.update(0, TimeUnit.NANOSECONDS);
}
return maybeResult;
}
}
// maybeExecute may take sufficiently long that other requests could be queued.
if (queueSizeEstimate.get() >= maxQueueSize) {
return Optional.empty();
}
shouldRecordQueueMetrics = true;
DeferredCall components = DeferredCall.builder().endpoint(endpoint).request(request).response(SettableFuture.create()).span(DetachedSpan.start("Dialogue-request-enqueued")).timer(queuedTime.time()).build();
if (!queuedCalls.offer(components)) {
// Should never happen, ConcurrentLinkedDeque has no maximum size
return Optional.empty();
}
int newSize = incrementQueueSize();
if (log.isDebugEnabled()) {
log.debug("Request queued {} on channel {}", SafeArg.of("queueSize", newSize), SafeArg.of("channelName", channelName));
}
schedule();
return Optional.of(components.response());
}
Aggregations