use of com.nike.riposte.server.error.exception.NonblockingEndpointCompletableFutureTimedOut in project riposte by Nike-Inc.
the class NonblockingEndpointExecutionHandler method doChannelRead.
@Override
public PipelineContinuationBehavior doChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
HttpProcessingState state = ChannelAttributes.getHttpProcessingStateForChannel(ctx).get();
Endpoint<?> endpoint = state.getEndpointForExecution();
if (shouldHandleDoChannelReadMessage(msg, endpoint)) {
// We only do something when the last chunk of content has arrived.
if (msg instanceof LastHttpContent) {
NonblockingEndpoint nonblockingEndpoint = ((NonblockingEndpoint) endpoint);
// We're supposed to execute the endpoint. There may be pre-endpoint-execution validation logic or
// other work that needs to happen before the endpoint is executed, so set up the
// CompletableFuture for the endpoint call to only execute if the pre-endpoint-execution
// validation/work chain is successful.
RequestInfo<?> requestInfo = state.getRequestInfo();
@SuppressWarnings("unchecked") CompletableFuture<ResponseInfo<?>> responseFuture = state.getPreEndpointExecutionWorkChain().thenCompose(functionWithTracingAndMdc(aVoid -> (CompletableFuture<ResponseInfo<?>>) nonblockingEndpoint.execute(requestInfo, longRunningTaskExecutor, ctx), ctx));
// Register an on-completion callback so we can be notified when the CompletableFuture finishes.
responseFuture.whenComplete((responseInfo, throwable) -> {
if (throwable != null)
asyncErrorCallback(ctx, throwable);
else
asyncCallback(ctx, responseInfo);
});
// Also schedule a timeout check with our Netty event loop to make sure we kill the
// CompletableFuture if it goes on too long.
long timeoutValueToUse = (nonblockingEndpoint.completableFutureTimeoutOverrideMillis() == null) ? defaultCompletableFutureTimeoutMillis : nonblockingEndpoint.completableFutureTimeoutOverrideMillis();
ScheduledFuture<?> responseTimeoutScheduledFuture = ctx.channel().eventLoop().schedule(() -> {
if (!responseFuture.isDone()) {
runnableWithTracingAndMdc(() -> logger.error("A non-blocking endpoint's CompletableFuture did not finish within " + "the allotted timeout ({} milliseconds). Forcibly cancelling it.", timeoutValueToUse), ctx).run();
@SuppressWarnings("unchecked") Throwable errorToUse = nonblockingEndpoint.getCustomTimeoutExceptionCause(requestInfo, ctx);
if (errorToUse == null)
errorToUse = new NonblockingEndpointCompletableFutureTimedOut(timeoutValueToUse);
responseFuture.completeExceptionally(errorToUse);
}
}, timeoutValueToUse, TimeUnit.MILLISECONDS);
/*
The problem with the scheduled timeout check is that it holds on to the RequestInfo,
ChannelHandlerContext, and a bunch of other stuff that *should* become garbage the instant the
request finishes, but because of the timeout check it has to wait until the check executes
before the garbage is collectable. In high volume servers the default 60 second timeout is way
too long and acts like a memory leak and results in garbage collection thrashing if the
available memory can be filled within the 60 second timeout. To combat this we cancel the
timeout future when the endpoint future finishes. Netty will remove the cancelled timeout future
from its scheduled list within a short time, thus letting the garbage be collected.
*/
responseFuture.whenComplete((responseInfo, throwable) -> {
if (!responseTimeoutScheduledFuture.isDone())
responseTimeoutScheduledFuture.cancel(false);
});
}
// completes (see asyncCallback() and asyncErrorCallback()).
return PipelineContinuationBehavior.DO_NOT_FIRE_CONTINUE_EVENT;
}
// error to be returned to the client.
return PipelineContinuationBehavior.CONTINUE;
}
use of com.nike.riposte.server.error.exception.NonblockingEndpointCompletableFutureTimedOut in project riposte by Nike-Inc.
the class BackstopperRiposteFrameworkErrorHandlerListener method shouldHandleException.
@Override
public ApiExceptionHandlerListenerResult shouldHandleException(Throwable ex) {
if (ex instanceof CircuitBreakerException) {
CircuitBreakerException cbe = ((CircuitBreakerException) ex);
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(getApiErrorForCircuitBreakerException(cbe)), singletonList(Pair.of("circuit_breaker_id", String.valueOf(cbe.circuitBreakerId))));
}
if (ex instanceof NonblockingEndpointCompletableFutureTimedOut) {
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()), singletonList(Pair.of("completable_future_timeout_value_millis", String.valueOf(((NonblockingEndpointCompletableFutureTimedOut) ex).timeoutValueMillis))));
}
if (ex instanceof DownstreamIdleChannelTimeoutException) {
DownstreamIdleChannelTimeoutException idleEx = (DownstreamIdleChannelTimeoutException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()), Arrays.asList(Pair.of("async_downstream_call_timeout_value_millis", String.valueOf(idleEx.timeoutValueMillis)), Pair.of("idle_channel_id", String.valueOf(idleEx.channelId))));
}
if (ex instanceof DownstreamChannelClosedUnexpectedlyException) {
DownstreamChannelClosedUnexpectedlyException dsClosedEx = (DownstreamChannelClosedUnexpectedlyException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()), singletonList(Pair.of("closed_channel_id", String.valueOf(dsClosedEx.channelId))));
}
if (ex instanceof DecoderException) {
// TODO: TooLongFrameException should result in a 413 Payload Too Large error instead of generic 400 malformed request.
// For now, we can at least let the caller know why it failed via error metadata.
ApiError errorToUse = (ex instanceof TooLongFrameException) ? new ApiErrorWithMetadata(projectApiErrors.getMalformedRequestApiError(), Pair.of("cause", "The request exceeded the maximum payload size allowed")) : projectApiErrors.getMalformedRequestApiError();
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(errorToUse), Arrays.asList(Pair.of("decoder_exception", "true"), Pair.of("decoder_exception_message", ex.getMessage())));
}
if (ex instanceof HostnameResolutionException) {
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()));
}
if (ex instanceof NativeIoExceptionWrapper) {
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()));
}
if (ex instanceof RequestContentDeserializationException) {
RequestContentDeserializationException theEx = (RequestContentDeserializationException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getMalformedRequestApiError()), Arrays.asList(Pair.of("method", theEx.httpMethod), Pair.of("request_path", theEx.requestPath), Pair.of("desired_object_type", theEx.desiredObjectType.getType().toString())));
}
if (ex instanceof PathNotFound404Exception) {
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getNotFoundApiError()));
}
if (ex instanceof MethodNotAllowed405Exception) {
MethodNotAllowed405Exception theEx = (MethodNotAllowed405Exception) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getMethodNotAllowedApiError()), Arrays.asList(Pair.of("incoming_request_path", theEx.requestPath), Pair.of("incoming_request_method", theEx.requestMethod)));
}
if (ex instanceof Unauthorized401Exception) {
Unauthorized401Exception theEx = (Unauthorized401Exception) ex;
List<Pair<String, String>> extraDetails = new ArrayList<>();
extraDetails.add(Pair.of("message", ex.getMessage()));
extraDetails.add(Pair.of("incoming_request_path", theEx.requestPath));
extraDetails.add(Pair.of("authorization_header", theEx.authorizationHeader));
extraDetails.addAll((theEx).extraDetailsForLogging);
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getUnauthorizedApiError()), extraDetails);
}
if (ex instanceof Forbidden403Exception) {
Forbidden403Exception theEx = (Forbidden403Exception) ex;
List<Pair<String, String>> extraDetails = new ArrayList<>();
extraDetails.add(Pair.of("message", ex.getMessage()));
extraDetails.add(Pair.of("incoming_request_path", theEx.requestPath));
extraDetails.add(Pair.of("authorization_header", theEx.authorizationHeader));
extraDetails.addAll((theEx).extraDetailsForLogging);
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getForbiddenApiError()), extraDetails);
}
if (ex instanceof MultipleMatchingEndpointsException) {
MultipleMatchingEndpointsException theEx = (MultipleMatchingEndpointsException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getGenericServiceError()), Arrays.asList(Pair.of("incoming_request_path", theEx.requestPath), Pair.of("incoming_request_method", theEx.requestMethod), Pair.of("matching_endpoints", StringUtils.join(theEx.matchingEndpointsDetails, ","))));
}
if (ex instanceof PathParameterMatchingException) {
PathParameterMatchingException theEx = (PathParameterMatchingException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getGenericServiceError()), Arrays.asList(Pair.of("path_template", theEx.pathTemplate), Pair.of("non_matching_uri_path", theEx.nonMatchingUriPath)));
}
if (ex instanceof InvalidCharsetInContentTypeHeaderException) {
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getUnsupportedMediaTypeApiError()), singletonList(Pair.of("invalid_content_type_header", ((InvalidCharsetInContentTypeHeaderException) ex).invalidContentTypeHeader)));
}
if (ex instanceof TooManyOpenChannelsException) {
TooManyOpenChannelsException theEx = (TooManyOpenChannelsException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(projectApiErrors.getTemporaryServiceProblemApiError()), Arrays.asList(Pair.of("num_current_open_channels", String.valueOf(theEx.actualOpenChannelsCount)), Pair.of("max_open_channels_limit", String.valueOf(theEx.maxOpenChannelsLimit))));
}
if (ex instanceof IncompleteHttpCallTimeoutException) {
IncompleteHttpCallTimeoutException theEx = (IncompleteHttpCallTimeoutException) ex;
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(new ApiErrorWithMetadata(projectApiErrors.getMalformedRequestApiError(), Pair.of("cause", "Unfinished/invalid HTTP request"))), Arrays.asList(Pair.of("incomplete_http_call_timeout_millis", String.valueOf(theEx.timeoutMillis)), Pair.of("exception_message", theEx.getMessage())));
}
if (ex instanceof InvalidHttpRequestException) {
InvalidHttpRequestException theEx = (InvalidHttpRequestException) ex;
Throwable cause = theEx.getCause();
String causeAsString = cause == null ? "null" : cause.toString();
return ApiExceptionHandlerListenerResult.handleResponse(singletonError(new ApiErrorWithMetadata(projectApiErrors.getMalformedRequestApiError(), Pair.of("cause", "Invalid HTTP request"))), Arrays.asList(Pair.of("exception_message", theEx.getMessage()), Pair.of("cause_details", causeAsString)));
}
return ApiExceptionHandlerListenerResult.ignoreResponse();
}
Aggregations