INT-4510: Test no memory leak in FluxMessageCh

JIRA: https://jira.spring.io/browse/INT-4510

Related to https://github.com/reactor/reactor-core/issues/1290

The `Flux.publish()` and subsequent `connect()` doesn't fuse a subscriber
for the hooks flow is interrupted (complete, or error, or disconnect).
In this case the `FluxMessageChannel.publishers` store is not cleared
from the finished publishers

* Add test-case to check the `FluxMessageChannel.publishers` store after
finishing the stream
* Add `hide()` operator with TODO to remove when an appropriate Reactor
version is ready

**Cherry-pick to 5.0.x**
This commit is contained in:
Artem Bilan
2018-07-26 20:12:12 -04:00
committed by Gary Russell
parent 8b13b2861d
commit 8c03d72e2d
2 changed files with 38 additions and 2 deletions

View File

@@ -84,6 +84,7 @@ public class FluxMessageChannel extends AbstractMessageChannel
.handle((message, sink) -> sink.next(send(message)))
.errorStrategyContinue()
.doOnComplete(() -> this.publishers.remove(publisher))
.hide() // TODO remove after upgrade to Reactor 3.1.9.RELEASE or later
.publish();
this.publishers.put(publisher, connectableFlux);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2016-2018 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.
@@ -26,8 +26,10 @@ import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +42,10 @@ import org.springframework.integration.channel.FluxMessageChannel;
import org.springframework.integration.channel.MessageChannelReactiveUtils;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.MessageChannels;
import org.springframework.integration.dsl.context.IntegrationFlowContext;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
@@ -53,6 +59,7 @@ import reactor.core.publisher.Flux;
/**
* @author Artem Bilan
*
* @since 5.0
*/
@RunWith(SpringRunner.class)
@@ -68,8 +75,11 @@ public class FluxMessageChannelTests {
@Autowired
private PollableChannel errorChannel;
@Autowired
private IntegrationFlowContext integrationFlowContext;
@Test
public void testFluxMessageChannel() throws InterruptedException {
public void testFluxMessageChannel() {
QueueChannel replyChannel = new QueueChannel();
for (int i = 0; i < 10; i++) {
@@ -106,6 +116,31 @@ public class FluxMessageChannelTests {
assertThat(results, contains("FOO", "BAR"));
}
@Test
public void testFluxMessageChannelCleanUp() throws InterruptedException {
FluxMessageChannel flux = MessageChannels.flux().get();
CountDownLatch finishLatch = new CountDownLatch(1);
IntegrationFlow testFlow = f -> f
.<String>split(__ -> Flux.fromStream(IntStream.range(0, 100).boxed()), null)
.channel(flux)
.aggregate(a -> a.releaseStrategy(m -> m.size() == 100))
.handle(__ -> finishLatch.countDown());
IntegrationFlowContext.IntegrationFlowRegistration flowRegistration =
this.integrationFlowContext.registration(testFlow)
.register();
flowRegistration.getInputChannel().send(new GenericMessage<>("foo"));
assertTrue(finishLatch.await(10, TimeUnit.SECONDS));
assertTrue(TestUtils.getPropertyValue(flux, "publishers", Map.class).isEmpty());
flowRegistration.destroy();
}
@Configuration
@EnableIntegration
public static class TestConfiguration {