reactor.core.Exceptions$OverflowException: Can't deliver value due to lack of requests - project-reactor

We've got a pretty complicated reactive spring application that processes requests from SQS and we've recently run into the following error in production:
2020-11-05 13:01:07.268 ERROR [reactive-multitenant-bridge,,,] 6 --- [llEventLoop-4-1] c.c.multibridge.sqs.SqsGateway : error in sqs gateway foo:REGISTRATION
reactor.core.Exceptions$OverflowException: Can't deliver value due to lack of requests
at reactor.core.Exceptions.failWithOverflow(Exceptions.java:234) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.DirectProcessor$DirectInner.onNext(DirectProcessor.java:340) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.DirectProcessor.onNext(DirectProcessor.java:142) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.SerializedSubscriber.onNext(SerializedSubscriber.java:99) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:180) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:126) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:144) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:114) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:147) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:152) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.deferredComplete(FluxUsingWhen.java:402) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxUsingWhen$CommitInner.onComplete(FluxUsingWhen.java:536) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoIgnoreElements$IgnoreElementsSubscriber.onComplete(MonoIgnoreElements.java:81) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:816) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:600) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.drain(FluxFlatMap.java:580) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onComplete(FluxFlatMap.java:457) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxArray$ArraySubscription.slowPath(FluxArray.java:137) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxArray$ArraySubscription.request(FluxArray.java:99) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:363) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMerge.subscribe(FluxMerge.java:69) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.Mono.subscribe(Mono.java:4213) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onComplete(FluxUsingWhen.java:394) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.checkTerminated(FluxFlatMap.java:838) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.drainLoop(FluxFlatMap.java:600) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapMain.innerComplete(FluxFlatMap.java:909) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxFlatMap$FlatMapInner.onComplete(FluxFlatMap.java:1013) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:136) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$BaseSink.complete(FluxCreate.java:438) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$BufferAsyncSink.drain(FluxCreate.java:784) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$BufferAsyncSink.complete(FluxCreate.java:732) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.drainLoop(FluxCreate.java:239) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.drain(FluxCreate.java:205) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.FluxCreate$SerializedSink.complete(FluxCreate.java:196) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at io.lettuce.core.ScanStream$SubscriptionAdapter.chunkCompleted(ScanStream.java:375) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.ScanStream$ScanSubscriber.hookOnComplete(ScanStream.java:518) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at reactor.core.publisher.BaseSubscriber.onComplete(BaseSubscriber.java:197) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:96) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:77) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE]
at io.lettuce.core.RedisPublisher$ImmediateSubscriber.onNext(RedisPublisher.java:885) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.RedisPublisher$RedisSubscription.onNext(RedisPublisher.java:278) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.RedisPublisher$SubscriptionCommand.complete(RedisPublisher.java:753) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:59) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:654) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:614) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:565) ~[lettuce-core-5.2.2.RELEASE.jar!/:5.2.2.RELEASE]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378) ~[netty-transport-native-epoll-4.1.51.Final-linux-x86_64.jar!/:4.1.51.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.51.Final.jar!/:4.1.51.Final]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
This happens during a large spike of requests on a given request queue, but only sometimes, and not in any kind of repeatable way. I've been attempting to reproduce the error in a local development environment, and I've seen the error occur 3 times out of around 50 nearly identical tests. I've searched a bit and found some information indicating that this error occurs when using DirectProcessor when a publisher publishes n events and the consumer requests < n, due to DirectProcessor not supporting backpressure. This information doesn't really help me figure the issue out, since I'm not sure where DirectProcessor is used in my application, and the stacktrace doesn't help me identify which code is the publisher and which code is the consumer or how this problem is occurring.
High-level description of how the application is intended to work:
On startup, the application starts pollers for several thousand SQS request queues. As the pollers receive messages, they're dispatched to message handlers make various REST and SOAP calls based on the particular gateway (gateway refers to the poller and consumer and the SQS request and response queue pair). Before a message handler dispatches the REST or SOAP call, it increments a counter in a redis hash so we can track inflight request count, and after the call finishes, we track the duration of the call and maintain an EWMA running average response time for the gateway, as well as a worker pool count that can grow or shrink within bounds based on a response time setpoint. On each poller loop, we query redis for inflight request count and the worker pool size and calculate the number of messages to request from SQS. When the number of messages to request is 0, we block on a redis pubsub channel for an inflight request to finish. The entire application is non-blocking, including SQS calls, redis calls, and the REST and SOAP calls made by message handlers.
Main poller loop (SqsGateway.start):
public Flux<U> start() {
log.info("Starting gateway {}", gatewayId);
return Mono.defer(() -> getSqsGatewayConfiguration()
.flatMap(sqsGatewayConfiguration ->
Mono.defer(() -> loadGatewayRuntimeStats(sqsGatewayConfiguration.getGatewayConfiguration()))
.map(gatewayRuntimeStats -> Tuples
.of(sqsGatewayConfiguration, gatewayRuntimeStats,
calculateNumberOfMessagesToRequest(gatewayRuntimeStats)))
.retryWhen(Retry.from(companion ->
companion.flatMap(retrySignal -> {
Throwable t = retrySignal.failure();
if (t instanceof NoDemandException) {
return gatewayRuntimeStatsDao.waitForInflightRequestUpdate(gatewayId);
} else {
//noinspection ReactiveStreamsThrowInOperator
throw Exceptions.propagate(t);
}
})
))
)
.flatMap(tuple -> {
SqsGatewayConfiguration sqsGatewayConfiguration = tuple.getT1();
GatewayRuntimeStats gatewayRuntimeStats = tuple.getT2();
int numberOfMessagesToRequest = tuple.getT3();
return sqsService.receiveMessage(sqsGatewayConfiguration.getGatewayConfiguration()
.getRequestQueueName(), numberOfMessagesToRequest)
.map(receiveMessageResponse ->
Tuples.of(sqsGatewayConfiguration, gatewayRuntimeStats, receiveMessageResponse)
);
}))
.repeat()
.flatMap(
tuple -> {
SqsGatewayConfiguration sqsGatewayConfiguration = tuple.getT1();
GatewayRuntimeStats stats = tuple.getT2();
ReceiveMessageResponse receiveMessageResponse = tuple.getT3();
return Flux.fromIterable(receiveMessageResponse.messages())
.flatMap(m ->
handleMessage(sqsGatewayConfiguration, stats, m)
);
}
)
.doOnError(e ->
log.error("error in sqs gateway {}", gatewayId, e)
);
}
private int calculateNumberOfMessagesToRequest(GatewayRuntimeStats gatewayRuntimeStats) {
int numberOfMessagesToRequest =
gatewayRuntimeStats.getWorkerPoolSize() - gatewayRuntimeStats.getInflightRequestCount();
if (numberOfMessagesToRequest < 1) {
throw new NoDemandException();
} else {
return Math.min(10, numberOfMessagesToRequest);
}
}
message handling (SqsGateway.handleMessage):
private Mono<U> handleMessage(SqsGatewayConfiguration sqsGatewayConfiguration, GatewayRuntimeStats stats,
Message m) {
BridgeSettings bridgeSettings = sqsGatewayConfiguration.getBridgeSettings();
GatewayConfiguration gatewayConfiguration = sqsGatewayConfiguration.getGatewayConfiguration();
#SuppressWarnings("unchecked")
SqsMessageHandler<T, U> sqsMessageHandler = (SqsMessageHandler<T, U>) sqsMessageHandlerFactory
.getSqsMessageHandler(gatewayConfiguration.getType(), bridgeSettings);
try {
T request = unmarshalMessage(m);
try {
//noinspection BlockingMethodInNonBlockingContext
log.info("{}: message handler request {}", gatewayId, objectMapper.writeValueAsString(request));
} catch (IOException e) {
log.warn("{}: error logging message handler request", gatewayId, e);
}
String instanceId = instanceUtils.getInstanceId();
return gatewayRuntimeStatsDao.addInflightRequest(gatewayId, instanceId)
.then(
sqsMessageHandler.handleMessage(request)
.elapsed()
.flatMap(t -> {
try {
Object response = t.getT2();
try {
//noinspection BlockingMethodInNonBlockingContext
log.info("{}: message handler response: {}", gatewayId, objectMapper
.writeValueAsString(response));
} catch (IOException e) {
log.warn("{}: error logging message handler response", gatewayId, e);
}
//noinspection BlockingMethodInNonBlockingContext
return sqsService
.sendMessage(gatewayConfiguration
.getResponseQueueName(), objectMapper
.writeValueAsString(response)).thenReturn(t);
} catch (IOException e) {
return Mono.error(new RuntimeException(e));
}
})
.flatMap(t ->
sqsService.deleteMessage(gatewayConfiguration.getRequestQueueName(), m
.receiptHandle()).thenReturn(t)
)
.flatMap(t ->
gatewayRuntimeStatsDao.removeInflightRequest(gatewayId,
instanceId).thenReturn(t)
)
.flatMap(t -> {
long elapsed = t.getT1();
long nextRunningAverage = EwmaCalculator.getNextAverage(0.08,
stats.getRunningAverage(), elapsed);
stats.setRunningAverage(nextRunningAverage);
return gatewayRuntimeStatsDao.setRunningAverage(gatewayId,
nextRunningAverage)
.and(adjustWorkerPoolSize(sqsGatewayConfiguration, stats))
.and(publishResponseTimeStats(bridgeSettings,
elapsed))
.thenReturn(t.getT2());
})
);
} catch (Exception e) {
log.error("{}: Error handling message", gatewayId, e);
return sqsMessageHandler.getErrorResponse(getCorrelationId(m))
.flatMap(response -> {
try {
try {
//noinspection BlockingMethodInNonBlockingContext
log.info("{}: message handler response: {}", gatewayId, objectMapper
.writeValueAsString(response));
} catch (IOException innerException) {
log.warn("{}: error logging message handler response", gatewayId, innerException);
}
//noinspection BlockingMethodInNonBlockingContext
return sqsService
.sendMessage(gatewayConfiguration
.getResponseQueueName(), objectMapper
.writeValueAsString(response)).thenReturn(response);
} catch (IOException innerException) {
return Mono.error(new RuntimeException(innerException));
}
})
.flatMap(response ->
sqsService.deleteMessage(gatewayConfiguration.getRequestQueueName(), m
.receiptHandle()).thenReturn(response)
);
}
}
There's obviously a lot more code I'm leaving out of the post, but hopefully that's enough for someone to look at. We call subscribe on the flux returned from the gateway start method above and keep the disposables returned in a collection, and we have a scheduled task that periodically checks that the pollers that are expected to be running have not been disposed, as well as launch new ones that have been added via configuration while the application was running, and stop pollers that have been removed. When this issue occurs, the disposable remains and is not disposed, so the poller for that particular queue stops functioning and nothing is able to detect the problem and start it back up.
Due to the presence of at reactor.core.publisher.FluxRetryWhen$RetryWhenMainSubscriber.onError(FluxRetryWhen.java:180) ~[reactor-core-3.3.8.RELEASE.jar!/:3.3.8.RELEASE] in the stacktrace, I suspect the retryWhen in the SqsGateway.start method, but I've been testing like crazy and unable to figure out what the root cause could be. Any help here would be greatly appreciated!
Edit: someone mentioned in comments that a hot source may be involved in the root cause. I use Mono.just in quite a few places, here's an example in a message handler:
#Override
public Mono<RegistrationResponse> register(RegistrationRequest request) {
SendToStudentCart igRequest = new SendToStudentCart();
igRequest.setKEY(peoplesoftProperties.getAuthKey());
igRequest.setEMPLID(request.getUsername());
igRequest.setTERM(request.getTermCode());
igRequest.setACTION(Action.ENROLL);
try {
List<StudentCartItem> items = request.getRegNumberRequests().stream().map(r -> {
StudentCartItem item = new StudentCartItem();
if (r.getAction() == null) {
throw new IllegalArgumentException("RegNumberRequest action is required.");
}
item.setACTION(ItemAction.valueOf(r.getAction()));
item.setREGNUMBER(r.getRegNumber());
item.setACADCAREER(r.getAcademicCareerCode());
return item;
}).collect(Collectors.toList());
igRequest.setITEMS(new SendToStudentCart.ITEMS());
igRequest.getITEMS().getITEM().addAll(items);
} catch (Exception e) {
return Mono.error(e);
}
try {
StringResult stringResult = new StringResult();
marshaller.marshal(igRequest, stringResult);
log.info("raw integration gateway request: {}", stringResult);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
return Mono.create(sink -> dispatch.invokeAsync(igRequest, ReactorAsyncHandler.into(sink))).map(igResponse -> {
try {
StringResult stringResult = new StringResult();
marshaller.marshal(igResponse, stringResult);
log.info("raw integration gateway response: {}", stringResult);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
if (igResponse instanceof SendToStudentCartResponse) {
SendToStudentCartResponse sendToStudentCartResponse = (SendToStudentCartResponse) igResponse;
RegistrationResponse response = new RegistrationResponse();
response.setUsername(request.getUsername());
response.setSubdomain(request.getSubdomain());
response.setTermCode(request.getTermCode());
Map<String, RegNumberRequest> regNumberRequestMap =
request.getRegNumberRequests().stream().collect(Collectors.toMap(RegNumberRequest::getRegNumber,
r -> r));
Map<String, RegNumberResponse> regNumberResponseMap = new HashMap<>();
if (sendToStudentCartResponse.getEnrollResults() != null) {
Consumer<? super ClassResult> action = consumeRegisterResponses(regNumberResponseMap,
regNumberRequestMap);
for (ClassResult classResult : sendToStudentCartResponse.getEnrollResults().getClazz()) {
action.accept(classResult);
}
}
response.setRegNumberResponses(new ArrayList<>(regNumberResponseMap.values()));
if (sendToStudentCartResponse.getCartCourses() != null) {
response.setCartCourses(sendToStudentCartResponse.getCartCourses().getCartCourse().stream()
.map(this::convertCartCourse).collect(Collectors.toList()));
}
if (sendToStudentCartResponse.getCurrentScheduleSections() != null) {
response.setRegisteredCourses(
sendToStudentCartResponse.getCurrentScheduleSections().getCurrentScheduleSection().stream()
.map(this::convertRegisteredCourse).collect(Collectors.toList()));
}
return response;
} else if (igResponse instanceof Exceptions) {
Exceptions exceptions = (Exceptions) igResponse;
log.error("Got exception response from register psoft interface call");
if (exceptions.getException() != null) {
for (String s : exceptions.getException()) {
log.error(s);
}
}
RegistrationResponse response = new RegistrationResponse();
response.getErrors()
.add(new RegistrationError(RegistrationErrorType.UNKNOWN, "An unexpected error has occurred. " +
"Please contact your administrator."));
return response;
} else {
RegistrationResponse response = new RegistrationResponse();
response.getErrors().add(new RegistrationError(RegistrationErrorType.UNKNOWN, "unrecognized response"));
return response;
}
})
.onErrorResume(e -> e instanceof WebServiceException, e -> {
log.error("Registration Exception", e);
if (e.getCause() != null && (e.getCause() instanceof IOException)) {
RegistrationResponse response = new RegistrationResponse();
response.getErrors()
.add(new RegistrationError(RegistrationErrorType.SIS_UNAVAILABLE, "Error contacting " +
"peoplesoft registration service. Please contact your administrator."));
return Mono.just(response);
} else {
return Mono.error(e);
}
});
}
Note the onErrorResume call, if the JAX-WS dispatch client throws an IOException, I'm emitting an error response using Mono.just. Is that a problem?

Related

What kind of errors are returned by HttpServer stream in Dart

I'm going through the Dart server documentation. I see I can await for an HttpRequest like this:
import 'dart:io';
Future main() async {
var server = await HttpServer.bind(
InternetAddress.loopbackIPv4,
4040,
);
print('Listening on localhost:${server.port}');
await for (HttpRequest request in server) {
request.response.write('Hello, world!');
await request.response.close();
}
}
That's because HttpServer implements Stream. But since a stream can return either a value or an error, I should catch exceptions like this, right:
try {
await for (HttpRequest request in server) {
request.response.write('Hello, world!');
await request.response.close();
}
} catch (e) {
// ???
}
But I'm not sure what kind of exceptions can be caught. Do the exceptions arise from the request (and warrant a 400 level response) or from the server (and warrant a 500 level response)? Or both?
Error status codes
On exception, a BAD_REQUEST status code will be set:
} catch (e) {
// Try to send BAD_REQUEST response.
request.response.statusCode = HttpStatus.badRequest;
(see source)
That would be 400 (see badRequest).
Stream errors
In that same catch block, the exceptions will be rethrown, which means that you will still receive all the errors on your stream. This happens in processRequest, which processes all requests in bind.
And you get the errors on your stream because they are forwarded to the sink in bind.
Kinds of errors
I could only find a single explicit exception type:
if (disposition == null) {
throw const HttpException(
"Mime Multipart doesn't contain a Content-Disposition header value");
}
if (encoding != null &&
!_transparentEncodings.contains(encoding.value.toLowerCase())) {
// TODO(ajohnsen): Support BASE64, etc.
throw HttpException('Unsupported contentTransferEncoding: '
'${encoding.value}');
}
(see source)
These are both HttpExceptions.

No errors are being raised when unsuccessfully writing to Azure service bus

When writing a message to the Azure Service Bus (using Microsoft.Azure.ServiceBus standard library, not the .Net Framework version) it works fine. However, when switching networks to a network that blocks that traffic and running it again I would expect an error being raised by SendAsync yet no error is thrown, therefor the function considers the send successful even though it is not.
Am I missing some logic to make sure that errors do get raised and trapped, it seems to be inline with all the examples I have seen.
I have tried this possible solution ..
Trouble catching exception on Azure Service Bus SendAsync method
.ContinueWith(t =>
{
Console.WriteLine(t.Status + "," + t.IsFaulted + "," + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
.. and at no point does ContinueWith get hit.
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
public ActionResult<Boolean> Post(Contract<T> contract)
{
Task.Run(() => SendMessage(contract));
// Other stuff
}
private async Task<ActionResult<Boolean>> SendMessage(Contract<T> contract)
{
JObject json = JObject.FromObject(contract);
Message message = new Message();
message.MessageId = Guid.NewGuid().ToString();
message.ContentType = ObjectType;
message.PartitionKey = ObjectType;
message.Body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(contract));
foreach (KeyValuePair<String, String> route in DataRouting)
{
JToken jToken = json.SelectToken(route.Value);
if (jToken != null)
{
message.UserProperties[route.Key] = jToken.Value<String>();
}
else
{
String routeError = $"Could not find routing information in request for: {route.Key} in {route.Value}";
Logger.LogError(routeError);
return new UnprocessableEntityObjectResult(routeError);
}
}
// Send the message
try
{
await topicClient.SendAsync(message);
}
catch(Exception ex)
{
return new UnprocessableEntityObjectResult($"'Could not transmit message to service bus - {ex.Message}'");
}
return new OkObjectResult(true);
}
I expect that the error trap would be hit if the SendAsync fails to send the message. However it essentially fire and forgets, the message send is blocked by the firewall but is never reported to the caller by throwing an error.
Ok, found the answer, but I will leave this out there in case anyone else does this to themselves. It was down to my general muppetry when putting the MVC Controller together. Set async on the Post action and configure the await on the send. Obvious really but I missed it.
public virtual async Task<ActionResult<Boolean>> Post(Contract<T> contract){}
...
// Send the message
try
{
await topicClient.SendAsync(message).ConfigureAwait(false);
return new OkObjectResult(true); // Success if we got here
}
catch(Exception ex)
{
return new UnprocessableEntityObjectResult($"'Could not transmit message to service bus - {ex.Message}'");
}

How to better troubleshoot this 500 Error in MVC Web API

I have an MVC Web API project that I am working on. I created a controller with an action. I am able to hit the action properly using Postman, but when an external system tries to reach my controller, it gets a 500 error. The owner of the external service cannot give me any details beyond that, they can only retry the request.
Here is one of the log entries of their requests in IIS log
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2017-02-15 20:38:58 192.168.2.34 POST /Route/to/actionName 8002 - 192.168.2.37 Apache-HttpClient/4.5.2+(Java/1.8.0_102) - 500 0 0 146
First I thought may be the action is being hit, so I added an exception handler and added logging.
[Route("actionName")]
[HttpPost]
public IHttpActionResult actionName(MessageModel message)
{
try
{
// code to handle the action
}
catch (Exception e)
{
// Code to log exception in the log file
}
}
Tried above and saw nothing in the log, I have run tests for failed requests to make sure the above exception handler logs and it does.
So the next thing I decided to do was to handle application level errors in Global.asax and log exception there.
protected void Application_Error(object sender, EventArgs e)
{
if (Request.HttpMethod == "POST")
{
var request = SomeMethodToReadRequestContentsInString();
var service = new SomeExceptionLoggingService();
var exception = Server.GetLastError();
if (exception == null)
{
exception = new ApplicationException("Unknown error occurred");
}
service.LogException(exception, Request.UserHostAddress, Request.UserAgent, request);
}
}
And to my surprise, nothing in the log file.
So then I decided to log ALL Post requests and see if I register ANYTHING in the log.
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "POST")
{
var request = Helper.ReadStreamUnknownEncoding(Request.InputStream);
var service = new InterfaceTestingService();
var exception = Server.GetLastError();
if (exception == null)
{
exception = new ApplicationException("No Error in this request");
}
service.LogException(exception, Request.UserHostAddress, Request.UserAgent, request);
}
}
And again, nothing!
How do I catch this bug? My goal is to see the Content-Type, and contents.
I tried to add a Custom Field in IIS log settings to include `Content-Type', but the log files still don't have that.
I added a handler for Application_BeginRequest logging everything I did in Application_EndRequest. And it turns out, the content-length was zero, and there was no content. I also restarted IIS Web Server to get it to log custom fields too.
What's strange is that if I send empty content through Postman, I get the action code executed but for some reason when they do it, it doesn't.

DotNetOpenAuth Bad Request on ProcessUserAuthorization

I'm implementing the SSO process (OAuth 2.0) using DotNetOpenAuth example. The solution has 3 projects (Client Web, Authorization Server, and Resource Server) I got an issue in the step of processing user authorization response after Authorization Server returned the Authorization Code to client.
http://localhost/OAuthClient/SampleWcf2.aspx?code=xxx&state=L6SAxlXhlxwsBRcTCK3IAw
Exception is:
[WebException: The remote server returned an error: (400) Bad Request.]
System.Net.HttpWebRequest.GetResponse() +8765848
DotNetOpenAuth.Messaging.StandardWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) +271
[ProtocolException: Error occurred while sending a direct message or getting the response.]
DotNetOpenAuth.Messaging.StandardWebRequestHandler.GetResponse(HttpWebRequest request, DirectWebRequestOptions options) +2261
DotNetOpenAuth.Messaging.Channel.RequestCore(IDirectedProtocolMessage request) +516
DotNetOpenAuth.Messaging.Channel.Request(IDirectedProtocolMessage requestMessage) +138
DotNetOpenAuth.OAuth2.ClientBase.UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) +210
DotNetOpenAuth.OAuth2.WebServerClient.ProcessUserAuthorization(HttpRequestBase request) +904
OAuthClient.SampleWcf2.Page_Load(Object sender, EventArgs e) +118
System.Web.UI.Control.LoadRecursive() +71
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3178
Here's my code:
protected void Page_Load(object sender, EventArgs e) {
if (!IsPostBack) {
// Check to see if we're receiving a end user authorization response.
var authorization = Client.ProcessUserAuthorization();
//Temp
if (authorization != null)
{
// We are receiving an authorization response. Store it and associate it with this user.
Authorization = authorization;
Response.Redirect(Request.Path); // get rid of the /?code= parameter
}
}
if (Authorization != null) {
// Indicate to the user that we have already obtained authorization on some of these.
foreach (var li in this.scopeList.Items.OfType<ListItem>().Where(li => Authorization.Scope.Contains(li.Value))) {
li.Selected = true;
}
this.authorizationLabel.Text = "Authorization received!";
if (Authorization.AccessTokenExpirationUtc.HasValue) {
TimeSpan timeLeft = Authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
this.authorizationLabel.Text += string.Format(CultureInfo.CurrentCulture, " (access token expires in {0} minutes)", Math.Round(timeLeft.TotalMinutes, 1));
}
}
this.getNameButton.Enabled = this.getAgeButton.Enabled = this.getFavoriteSites.Enabled = Authorization != null;
}

Completer completeError

I'm trying to caught an error from a completer.
Here, my method to decode a token
Future<Map> decode(String token) {
var completer = new Completer();
new Future(() {
List<String> parts = token.split(".");
Map result = {};
try {
result["header"] = JSON.decode(new String.fromCharCodes(crypto.CryptoUtils.base64StringToBytes(parts[0])));
result["payload"] = JSON.decode(new String.fromCharCodes(crypto.CryptoUtils.base64StringToBytes(parts[1])));
} catch(e) {
completer.completeError("Bad token");
return;
}
encode(result["payload"]).then((v_token) {
if (v_token == token) {
completer.complete(result);
} else {
completer.completeError("Bad signature");
}
});
});
return completer.future;
}
}
The call:
var test = new JsonWebToken("topsecret");
test.encode({"field": "ok"}).then((token) {
print(token);
test.decode("bad.jwt.here")
..then((n_tok) => print(n_tok))
..catchError((e) => print(e));
});
And this is the output
dart server.dart
eyJ0eXAiOiJKV1QiLCJhbGciOiJTSEEyNTYifQ==.eyJsdSI6Im9rIn0=.E3TjGiPGSJOIVZFFECJ0OSr0jAWojIfF7MqFNTbFPmI=
Bad token
Unhandled exception:
Uncaught Error: Bad token
#0 _rootHandleUncaughtError.<anonymous closure> (dart:async/zone.dart:820)
#1 _asyncRunCallbackLoop (dart:async/schedule_microtask.dart:41)
#2 _asyncRunCallback (dart:async/schedule_microtask.dart:48)
#3 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:126)
I don't understand why we tell me that my error is uncaught while it's printed...
I think you misused .. instead of . for chaining future. See https://www.dartlang.org/docs/tutorials/futures/#handling-errors
instead of
test.decode("bad.jwt.here")
..then((n_tok) => print(n_tok))
..catchError((e) => print(e));
can you try
test.decode("bad.jwt.here")
.then((n_tok) => print(n_tok))
.catchError((e) => print(e));
Have a look at this document about how Futures work - https://www.dartlang.org/articles/futures-and-error-handling/.
In particular there is an example which says:
myFunc()
.then((value) {
doSomethingWith(value);
...
throw("some arbitrary error");
})
.catchError(handleError);
If myFunc()’s Future completes with an error, then()’s Future
completes with that error. The error is also handled by catchError().
Regardless of whether the error originated within myFunc() or within
then(), catchError() successfully handles it.
That is consistent with what you're seeing.

Resources