Repository navigation
Description
Setup
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws");
registry.setPreserveReceiveOrder(true);
}A user connected with two STOMP sessions (e.g. two browser tabs), subscribed to /user/queue/notifications:
messagingTemplate.convertAndSendToUser("user", "/queue/notifications", payload);Actual
Only one session receives the message. For the other one, UserDestinationMessageHandler logs:
ERROR o.s.m.simp.user.UserDestinationMessageHandler -- Failed to send GenericMessage [payload=A, headers={simpNextMessageTask=..., simpDestination=/queue/notifications-users2}]
java.lang.IllegalArgumentException: Expected mutable SimpMessageHeaderAccessor
at org.springframework.util.Assert.isTrue(Assert.java:117)
at org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator.setTaskHeader(OrderedMessageChannelDecorator.java:134)
at org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator.sendNextMessage(OrderedMessageChannelDecorator.java:96)
at org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator.trySend(OrderedMessageChannelDecorator.java:87)
at org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator.send(OrderedMessageChannelDecorator.java:76)
at org.springframework.messaging.simp.broker.OrderedMessageChannelDecorator.send(OrderedMessageChannelDecorator.java:70)
at org.springframework.messaging.simp.SimpMessagingTemplate.sendInternal(SimpMessagingTemplate.java:185)
at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:177)
at org.springframework.messaging.simp.SimpMessagingTemplate.doSend(SimpMessagingTemplate.java:48)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:108)
at org.springframework.messaging.simp.user.UserDestinationMessageHandler$SendHelper.send(UserDestinationMessageHandler.java:282)
at org.springframework.messaging.simp.user.UserDestinationMessageHandler.handleMessage(UserDestinationMessageHandler.java:239)
...
It happens on every send, without any concurrency.
Expected
Every session of the user receives the message, as it does without setPreserveReceiveOrder(true).
Cause (as far as I can tell)
With preserveReceiveOrder, UserDestinationMessageHandler.SendHelper sends to each target session through its own SimpMessagingTemplate(new OrderedMessageChannelDecorator(brokerChannel)), and passes the same message instance to all of them.
- First session: the message is still mutable (
handleMessagecallsaccessor.setLeaveMutable(true)), soSimpMessagingTemplate.doSendsends it as is.OrderedMessageChannelDecoratorsets its task header and sends it to the broker channel, whoseImmutableMessageChannelInterceptormakes it immutable. - Second session: the message is now immutable, so
SimpMessagingTemplate.doSendtakes the other branch:getMutableAccessor(message)+MessageBuilder.createMessage(payload, simpAccessor.getMessageHeaders()).getMessageHeaders()makes the new accessor immutable, becauseleaveMutableis not set on it. OrderedMessageChannelDecorator.setTaskHeaderthen requires a mutable accessor and fails. The message is removed from the queue and never sent.
Possible fixes: send a separate mutable copy per session in SendHelper.send, or setLeaveMutable(true) in the copy branch of SimpMessagingTemplate.doSend (as handleMessage already does).
Reproducer
Plain JUnit test with the real Spring classes, wired as AbstractMessageBrokerConfiguration does with preserveReceiveOrder (attached / below): convertAndSendToUserReachesEverySession fails; the same without preserveReceiveOrder passes, and so does the workaround.
Workaround
Send to each session separately, with its session id in the headers, so that every send has a single target and the message stays mutable:
SimpUser simpUser = userRegistry.getUser(user);
if (simpUser != null)
for (SimpSession session : simpUser.getSessions()) {
SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
accessor.setSessionId(session.getId());
accessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(user, destination, payload, accessor.getMessageHeaders());
}
Activity