Skip to content

Commit ab12b6b

Browse files
artembilangaryrussell
authored andcommitted
Fix maxMessagesPerPoll for reactive poll endpoint
The current `Flux.take()` doesn't allow an arg `< 0` treating it as an unbound request. * Change `take()` to `limitRequest()` according strict `MessageSource.receive()` producing expectations * Treat `maxMessagesPerPoll < 0` as a `Long.MAX_VALUE` for unbound requests; `0` is treated in the `limitRequest()` as "no more requests - cancel" * Revise `AbstractPollingEndpoint` for `LogAccessor` usage * Add `AbstractPollingEndpoint` class JavaDocs * Fix tests according `AbstractPollingEndpoint` changes **Cherry-pick to `5.4.x`**
1 parent d3ce5b5 commit ab12b6b

File tree

3 files changed

+36
-31
lines changed

3 files changed

+36
-31
lines changed

spring-integration-core/src/main/java/org/springframework/integration/endpoint/AbstractPollingEndpoint.java

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
2424
import java.util.concurrent.Callable;
2525
import java.util.concurrent.Executor;
2626
import java.util.concurrent.ScheduledFuture;
27+
import java.util.function.Consumer;
2728
import java.util.stream.Collectors;
2829

2930
import org.aopalliance.aop.Advice;
@@ -48,6 +49,7 @@
4849
import org.springframework.messaging.Message;
4950
import org.springframework.messaging.MessageChannel;
5051
import org.springframework.messaging.MessagingException;
52+
import org.springframework.scheduling.TaskScheduler;
5153
import org.springframework.scheduling.Trigger;
5254
import org.springframework.scheduling.support.PeriodicTrigger;
5355
import org.springframework.scheduling.support.SimpleTriggerContext;
@@ -65,6 +67,13 @@
6567
import reactor.core.scheduler.Schedulers;
6668

6769
/**
70+
* An {@link AbstractEndpoint} extension for Polling Consumer pattern basics.
71+
* The standard polling logic is based on a periodic task scheduling according the provided
72+
* {@link Trigger}.
73+
* When this endpoint is treated as {@link #isReactive()}, a polling logic is turned into a
74+
* {@link Flux#generate(Consumer)} and {@link Mono#delay(Duration)} combination based on the
75+
* {@link SimpleTriggerContext} state.
76+
*
6877
* @author Mark Fisher
6978
* @author Oleg Zhurakousky
7079
* @author Gary Russell
@@ -90,8 +99,6 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
9099

91100
private Trigger trigger = new PeriodicTrigger(DEFAULT_POLLING_PERIOD);
92101

93-
private long maxMessagesPerPoll = -1;
94-
95102
private ErrorHandler errorHandler;
96103

97104
private boolean errorHandlerIsDefault;
@@ -100,6 +107,8 @@ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implement
100107

101108
private TransactionSynchronizationFactory transactionSynchronizationFactory;
102109

110+
private volatile long maxMessagesPerPoll = -1;
111+
103112
private volatile Callable<Message<?>> pollingTask;
104113

105114
private volatile Flux<Message<?>> pollingFlux;
@@ -204,8 +213,8 @@ protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) {
204213
}
205214
this.appliedAdvices.clear();
206215
this.appliedAdvices.addAll(chain);
207-
if (!(isSyncExecutor()) && logger.isWarnEnabled()) {
208-
logger.warn(getComponentName() + ": A task executor is supplied and " + chain.size()
216+
if (!(isSyncExecutor())) {
217+
logger.warn(() -> getComponentName() + ": A task executor is supplied and " + chain.size()
209218
+ "ReceiveMessageAdvice(s) is/are provided. If an advice mutates the source, such "
210219
+ "mutations are not thread safe and could cause unexpected results, especially with "
211220
+ "high frequency pollers. Consider using a downstream ExecutorChannel instead of "
@@ -261,8 +270,8 @@ protected void onInit() {
261270
try {
262271
super.onInit();
263272
}
264-
catch (Exception e) {
265-
throw new BeanInitializationException("Cannot initialize: " + this, e);
273+
catch (Exception ex) {
274+
throw new BeanInitializationException("Cannot initialize: " + this, ex);
266275
}
267276
}
268277

@@ -280,11 +289,9 @@ protected void doStart() {
280289
this.pollingFlux = createFluxGenerator();
281290
}
282291
else {
283-
Assert.state(getTaskScheduler() != null, "unable to start polling, no taskScheduler available");
284-
285-
this.runningTask =
286-
getTaskScheduler()
287-
.schedule(createPoller(), this.trigger);
292+
TaskScheduler taskScheduler = getTaskScheduler();
293+
Assert.state(taskScheduler != null, "unable to start polling, no taskScheduler available");
294+
this.runningTask = taskScheduler.schedule(createPoller(), this.trigger);
288295
}
289296
}
290297

@@ -360,7 +367,10 @@ private Flux<Message<?>> createFluxGenerator() {
360367
fluxSink.complete();
361368
}
362369
})
363-
.take(this.maxMessagesPerPoll)
370+
.limitRequest(
371+
this.maxMessagesPerPoll < 0
372+
? Long.MAX_VALUE
373+
: this.maxMessagesPerPoll)
364374
.subscribeOn(Schedulers.fromExecutor(this.taskExecutor))
365375
.doOnComplete(() ->
366376
triggerContext
@@ -407,22 +417,18 @@ private Message<?> doPoll() {
407417
try {
408418
message = receiveMessage();
409419
}
410-
catch (Exception e) {
420+
catch (Exception ex) {
411421
if (Thread.interrupted()) {
412-
if (logger.isDebugEnabled()) {
413-
logger.debug("Poll interrupted - during stop()? : " + e.getMessage());
414-
}
422+
logger.debug(() -> "Poll interrupted - during stop()? : " + ex.getMessage());
415423
return null;
416424
}
417425
else {
418-
ReflectionUtils.rethrowRuntimeException(e);
426+
ReflectionUtils.rethrowRuntimeException(ex);
419427
}
420428
}
421429

422430
if (message == null) {
423-
if (this.logger.isDebugEnabled()) {
424-
this.logger.debug("Received no Message during the poll, returning 'false'");
425-
}
431+
this.logger.debug("Received no Message during the poll, returning 'false'");
426432
return null;
427433
}
428434
else {
@@ -433,9 +439,7 @@ private Message<?> doPoll() {
433439
}
434440

435441
private void messageReceived(IntegrationResourceHolder holder, Message<?> message) {
436-
if (this.logger.isDebugEnabled()) {
437-
this.logger.debug("Poll resulted in Message: " + message);
438-
}
442+
this.logger.debug(() -> "Poll resulted in Message: " + message);
439443
if (holder != null) {
440444
holder.setMessage(message);
441445
}

spring-integration-core/src/test/java/org/springframework/integration/config/SourcePollingChannelAdapterFactoryBeanTests.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.mockito.ArgumentMatchers.any;
21-
import static org.mockito.ArgumentMatchers.contains;
2221
import static org.mockito.BDDMockito.willAnswer;
2322
import static org.mockito.Mockito.mock;
2423
import static org.mockito.Mockito.spy;
@@ -32,12 +31,14 @@
3231
import java.util.concurrent.TimeUnit;
3332
import java.util.concurrent.atomic.AtomicBoolean;
3433
import java.util.concurrent.atomic.AtomicInteger;
34+
import java.util.function.Supplier;
3535

3636
import org.aopalliance.aop.Advice;
3737
import org.aopalliance.intercept.MethodInterceptor;
3838
import org.aopalliance.intercept.MethodInvocation;
3939
import org.apache.commons.logging.Log;
4040
import org.junit.jupiter.api.Test;
41+
import org.mockito.ArgumentMatchers;
4142

4243
import org.springframework.beans.DirectFieldAccessor;
4344
import org.springframework.beans.factory.BeanFactory;
@@ -188,7 +189,9 @@ public void testInterrupted() throws Exception {
188189
taskScheduler.shutdown();
189190

190191
verifyNoInteractions(errorHandlerLogger);
191-
verify(adapterLogger).debug(contains("Poll interrupted - during stop()?"));
192+
verify(adapterLogger)
193+
.debug(ArgumentMatchers.<Supplier<String>>argThat(logMessage ->
194+
logMessage.get().contains("Poll interrupted - during stop()?")));
192195
}
193196

194197
@Test

spring-integration-core/src/test/java/org/springframework/integration/endpoint/ReactiveInboundChannelAdapterTests.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2020 the original author or authors.
2+
* Copyright 2018-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,7 +36,6 @@
3636
import org.springframework.integration.annotation.Poller;
3737
import org.springframework.integration.channel.FluxMessageChannel;
3838
import org.springframework.integration.config.EnableIntegration;
39-
import org.springframework.integration.test.condition.LongRunningTest;
4039
import org.springframework.messaging.Message;
4140
import org.springframework.messaging.MessageChannel;
4241
import org.springframework.test.annotation.DirtiesContext;
@@ -52,7 +51,6 @@
5251
*/
5352
@SpringJUnitConfig
5453
@DirtiesContext
55-
@LongRunningTest
5654
public class ReactiveInboundChannelAdapterTests {
5755

5856
@Autowired
@@ -111,7 +109,7 @@ public TaskExecutor taskExecutor() {
111109

112110
@Bean
113111
@InboundChannelAdapter(value = "fluxChannel",
114-
poller = @Poller(fixedDelay = "100", maxMessagesPerPoll = "3", taskExecutor = "taskExecutor"))
112+
poller = @Poller(fixedDelay = "100", maxMessagesPerPoll = "-1", taskExecutor = "taskExecutor"))
115113
public Supplier<Integer> counterMessageSupplier() {
116114
return () -> {
117115
int i = counter().incrementAndGet();

0 commit comments

Comments
 (0)