GH-3452: Warn Publisher.onError in NullChannel (#3453)

* GH-3452: Warn Publisher.onError in NullChannel

Fixes https://github.com/spring-projects/spring-integration/issues/3452

The `NullChannel` can subscribe to the `Publisher` payload fully ignoring
the possible data according the `NullChannel` nature.
However an error thrown from the reactive stream processing is also ignored.

* Log WARN message from the `Subscriber.onError()` when `NullChannel`
subscribes to the `Publisher` produced to this channel.
* Mention the logic in the `NullChannel` docs; point to the
`ReactiveRequestHandlerAdvice` for further possible error handling in
the target application

**Cherry-pick to `5.4.x`**

* * Remove unused imports

* * Fix `PseudoTransactionalMessageSourceTests` for `LogAccessor`
from `NullChannel`
This commit is contained in:
Artem Bilan
2021-01-05 15:08:30 -05:00
committed by Gary Russell
parent 5ed8c11c58
commit ebe79aedfb
3 changed files with 21 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,12 @@ package org.springframework.integration.channel;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.log.LogAccessor;
import org.springframework.integration.IntegrationPattern;
import org.springframework.integration.IntegrationPatternType;
import org.springframework.integration.support.management.IntegrationManagedResource;
@@ -43,6 +42,8 @@ import org.springframework.messaging.PollableChannel;
* Unless the payload of a sent message is a {@link Publisher} implementation, in
* which case the {@link Publisher#subscribe(Subscriber)} is called to initiate
* the reactive stream, although the data is discarded by this channel.
* An error thrown from a reactive stream processing (see {@link Subscriber#onError(Throwable)})
* is logged under the {@code warn} level.
* Note however that the invocations are logged at debug-level.
*
* @author Mark Fisher
@@ -53,7 +54,7 @@ import org.springframework.messaging.PollableChannel;
public class NullChannel implements PollableChannel,
BeanNameAware, IntegrationManagement, IntegrationPattern {
private final Log logger = LogFactory.getLog(getClass());
private static final LogAccessor LOG = new LogAccessor(NullChannel.class);
private final ManagementOverrides managementOverrides = new ManagementOverrides();
@@ -122,8 +123,8 @@ public class NullChannel implements PollableChannel,
@Override
public boolean send(Message<?> message) {
if (this.loggingEnabled && this.logger.isDebugEnabled()) {
this.logger.debug("message sent to null channel: " + message);
if (this.loggingEnabled) {
LOG.debug(() -> "message sent to null channel: " + message);
}
Object payload = message.getPayload();
@@ -135,12 +136,12 @@ public class NullChannel implements PollableChannel,
subscription.request(Long.MAX_VALUE);
}
@Override public void onNext(Object o) {
@Override public void onNext(Object value) {
}
@Override public void onError(Throwable t) {
@Override public void onError(Throwable ex) {
LOG.warn(ex, "An error happened in a reactive stream processing");
}
@Override public void onComplete() {
@@ -173,7 +174,7 @@ public class NullChannel implements PollableChannel,
@Override
public Message<?> receive() {
if (this.loggingEnabled) {
this.logger.debug("receive called on null channel");
LOG.debug("receive called on null channel");
}
incrementReceiveCounter();
return null;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogAccessor;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.channel.QueueChannel;
@@ -97,14 +98,16 @@ public class PseudoTransactionalMessageSourceTests {
MessageChannel afterCommitChannel = new NullChannel();
syncProcessor.setAfterCommitChannel(afterCommitChannel);
Log logger = TestUtils.getPropertyValue(afterCommitChannel, "logger", Log.class);
LogAccessor logAccessor = TestUtils.getPropertyValue(afterCommitChannel, "LOG", LogAccessor.class);
Log logger = logAccessor.getLog();
logger = Mockito.spy(logger);
Mockito.when(logger.isDebugEnabled()).thenReturn(true);
DirectFieldAccessor dfa = new DirectFieldAccessor(afterCommitChannel);
dfa.setPropertyValue("logger", logger);
DirectFieldAccessor dfa = new DirectFieldAccessor(logAccessor);
dfa.setPropertyValue("log", logger);
TransactionSynchronizationManager.initSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(true);
@@ -115,7 +118,7 @@ public class PseudoTransactionalMessageSourceTests {
assertThat(beforeCommitMessage).isNotNull();
assertThat(beforeCommitMessage.getPayload()).isEqualTo("qox");
Mockito.verify(logger).debug(Mockito.anyString());
Mockito.verify(logger).debug(Mockito.any(CharSequence.class));
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
TransactionSynchronizationManager.clearSynchronization();

View File

@@ -1145,6 +1145,8 @@ For example, you can use this technique to configure a test case to verify messa
Two special channels are defined within the application context by default: `errorChannel` and `nullChannel`.
The 'nullChannel' (an instance of `NullChannel`) acts like `/dev/null`, logging any message sent to it at the `DEBUG` level and returning immediately.
The special treatment is applied for an `org.reactivestreams.Publisher` payload of a sent message: it is subscribed to in this channel immediately, to initiate reactive stream processing, although the data is discarded.
An error thrown from a reactive stream processing (see `Subscriber.onError(Throwable)`) is logged under the warn level for possible investigation.
If there is need to do anything with such an error, the `<<./handler-advice.adoc#reactive-advice,ReactiveRequestHandlerAdvice>>` with a `Mono.doOnError()` customization can be applied to the message handler producing `Mono` reply into this `nullChannel`.
Any time you face channel resolution errors for a reply that you do not care about, you can set the affected component's `output-channel` attribute to 'nullChannel' (the name, 'nullChannel', is reserved within the application context).
The 'errorChannel' is used internally for sending error messages and may be overridden with a custom configuration.