GH-3785: Close stream for persistent collection (#3786)

* GH-3785: Close stream for persistent collection

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

* Fix `CollectionArgumentResolver` and `PayloadsArgumentResolver` to
close the `Stream` of message after its usage
* Rework `AbstractKeyValueMessageStore.removeMessagesFromGroup()`
to iterate input collection of messages not its stream to avoid
the mentioned problem

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

* * Add `JdbcMessageStoreTests.testMessageGroupStreamNoConnectionPoolLeak()`
to ensure that no leaks in the connection pool anymore.
* Improve `MessageGroupStore.streamMessagesForGroup()` JavaDocs about
requirements to close the `Stream` from persistent message store impls
This commit is contained in:
Artem Bilan
2022-04-25 10:18:34 -04:00
committed by GitHub
parent 860f9fea3f
commit e454f59180
5 changed files with 95 additions and 37 deletions

View File

@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
@@ -71,17 +72,16 @@ public class CollectionArgumentResolver extends AbstractExpressionEvaluator
if (this.canProcessMessageList) {
Assert.state(value instanceof Collection,
"This Argument Resolver only supports messages with a payload of Collection<Message<?>>, "
+ "payload is: " + value.getClass());
+ "payload is: " + value.getClass());
Collection<Message<?>> messages = (Collection<Message<?>>) value;
if (Message.class.isAssignableFrom(parameter.nested().getNestedParameterType())) {
value = messages;
}
else {
value = messages.stream()
.map(Message::getPayload)
.collect(Collectors.toList());
if (!Message.class.isAssignableFrom(parameter.nested().getNestedParameterType())) {
try (Stream<Message<?>> messageStream = messages.stream()) {
value = messageStream
.map(Message::getPayload)
.collect(Collectors.toList());
}
}
}

View File

@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
@@ -78,15 +79,17 @@ public class PayloadsArgumentResolver extends AbstractExpressionEvaluator
return evaluateExpression(expression, messages, parameter.getParameterType());
}
else {
List<?> payloads = messages.stream()
.map(Message::getPayload)
.collect(Collectors.toList());
try (Stream<Message<?>> messageStream = messages.stream()) {
List<?> payloads = messageStream
.map(Message::getPayload)
.collect(Collectors.toList());
return getEvaluationContext()
.getTypeConverter()
.convertValue(payloads,
TypeDescriptor.forObject(payloads),
TypeDescriptor.valueOf(parameter.getParameterType()));
}
return getEvaluationContext()
.getTypeConverter()
.convertValue(payloads,
TypeDescriptor.forObject(payloads),
TypeDescriptor.valueOf(parameter.getParameterType()));
}
}

View File

@@ -244,17 +244,17 @@ public abstract class AbstractKeyValueMessageStore extends AbstractMessageGroupS
Assert.isInstanceOf(MessageGroupMetadata.class, mgm);
MessageGroupMetadata messageGroupMetadata = (MessageGroupMetadata) mgm;
List<UUID> ids =
messages.stream()
.map(messageToRemove -> messageToRemove.getHeaders().getId())
.collect(Collectors.toList());
List<UUID> ids = new ArrayList<>();
for (Message<?> messageToRemove : messages) {
ids.add(messageToRemove.getHeaders().getId());
}
messageGroupMetadata.removeAll(ids);
List<Object> messageIds =
ids.stream()
.map(id -> this.messagePrefix + id)
.collect(Collectors.toList());
List<Object> messageIds = new ArrayList<>();
for (UUID id : ids) {
messageIds.add(this.messagePrefix + id);
}
doRemoveAll(messageIds);

View File

@@ -156,6 +156,9 @@ public interface MessageGroupStore extends BasicMessageGroupStore {
/**
* Return a stream for messages stored in the provided group.
* The persistent implementations return a Stream which has
* to be closed once fully processed (e.g. through a try-with-resources clause).
* By default, it streams a result of {@link #getMessagesForGroup(Object)}.
* @param groupId the group id to retrieve messages.
* @return the {@link Stream} for messages in this group.
* @since 5.5