10

On the client side javascript I have

 stomp.subscribe("/topic/path", function (message) { console.info("message received"); }); 

And on the server side

public class Controller { private final MessageSendingOperations<String> messagingTemplate; @Autowired public Controller(MessageSendingOperations<String> messagingTemplate) { this.messagingTemplate = messagingTemplate; } @SubscribeMapping("/topic/path") public void subscribe() { LOGGER.info("before send"); messagingTemplate.convertAndSend(/topic/path, "msg"); } } 

From this setup, I am occasionally (around once in 30 page refreshes) experiencing message dropping, which means I can see neither "message received" msg on the client side nor the websocket traffic from Chrome debugging tool.

"before send" is always logged on the server side.

This looks like that the MessageSendingOperations is not ready when I call it in the subscribe() method. (if I put Thread.sleep(50); before calling messagingTemplate.convertAndSend the problem would disappear (or much less likely to be reproduced))

I wonder if anyone experienced the same before and if there is an event that can tell me MessageSendingOperations is ready or not.

2
  • is stomp.subscribe executed after the dom is ready? Commented Mar 22, 2015 at 13:07
  • @ᴳᵁᴵᴰᴼ Yes. that's right. I can see the subscribe msg was sent from Chrome debugging for websocket network traffic. So I don't think its the client side problem. Commented Mar 22, 2015 at 13:10

3 Answers 3

4

The issue you are facing is laying in the nature of clientInboundChannel which is ExecutorSubscribableChannel by default.

It has 3 subscribers:

0 = {SimpleBrokerMessageHandler@5276} "SimpleBroker[DefaultSubscriptionRegistry[cache[0 destination(s)], registry[0 sessions]]]" 1 = {UserDestinationMessageHandler@5277} "UserDestinationMessageHandler[DefaultUserDestinationResolver[prefix=/user/]]" 2 = {SimpAnnotationMethodMessageHandler@5278} "SimpAnnotationMethodMessageHandler[prefixes=[/app/]]" 

which are invoked within taskExecutor, hence asynchronously.

The first one here (SimpleBrokerMessageHandler (or StompBrokerRelayMessageHandler) if you use broker-relay) is responsible to register subscription for the topic.

Your messagingTemplate.convertAndSend(/topic/path, "msg") operation may be performed before the subscription registration for that WebSocket session, because they are performed in the separate threads. Hence the Broker handler doesn't know you to send the message to the session.

The @SubscribeMapping can be configured on method with return, where the result of this method will be sent as a reply to that subscription function on the client.

HTH

Sign up to request clarification or add additional context in comments.

4 Comments

inside my subscribe method, I was asynchronously calling service layer like subscriableService.registerAndHandleWith(new Handler(){}). So that I can't return immediately in this method. What would you recommend in this scenario? Thanks.
Future.get() or CountDownLatch from that registerAndHandleWith. From other side you can access to the SimpleBrokerMessageHandler and populate some your custom impl of DefaultSubscriptionRegistry to override addSubscriptionInternal to raise some custom ApplicationEvent to listen to it from some another component to send that message to the topic, when subscription will be there already. It is for case, when you really need an async stuff and don't overload clientInboundChannel executor to wait for that Future.
Thanks again Artem, I assume that I can't use the existing SessionSubscribeEvent as it only tells me that the client has requested but doesn't mean the subscription registration is completed (it would be nice to have something like SessionSubscribedEvent).
Yes, you are correct: a SessionSubscribeEvent event is emitted before the real subscription is done. Feel free to raise a JIRA ticket for the SessionSubscribedEvent.
1

Here is my solution. It is along the same lines. Added a ExecutorChannelInterceptor and published a custom SubscriptionSubscribedEvent. The key is to publish the event after the message has been handled by AbstractBrokerMessageHandler which means the subscription has been registered with the broker.

@Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ExecutorChannelInterceptorAdapter() { @Override public void afterMessageHandled(Message<?> message, MessageChannel channel, MessageHandler handler, Exception ex) { SimpMessageHeaderAccessor accessor = SimpMessageHeaderAccessor.wrap(message); if (accessor.getMessageType() == SimpMessageType.SUBSCRIBE && handler instanceof AbstractBrokerMessageHandler) { /* * Publish a new session subscribed event AFTER the client * has been subscribed to the broker. Before spring was * publishing the event after receiving the message but not * necessarily after the subscription occurred. There was a * race condition because the subscription was being done on * a separate thread. */ applicationEventPublisher.publishEvent(new SessionSubscribedEvent(this, message)); } } }); } 

Comments

0

A little late but I thought I'd add my solution. I was having the same problem with the subscription not being registered before I was sending data through the messaging template. This issue happened rarely and unpredictable because of the race with the DefaultSubscriptionRegistry.

Unfortunately, I could not just use the return method of the @SubscriptionMapping because we were using a custom object mapper that changed dynamically based on the type of user (attribute filtering essentially).

I searched through the Spring code and found SubscriptionMethodReturnValueHandler was responsible for sending the return value of subscription mappings and had a different messagingTemplate than the autowired SimpMessagingTemplate of my async controller!!

So the solution was autowiring MessageChannel clientOutboundChannel into my async controller and using that to create a SimpMessagingTemplate. (You can't directly wire it in because you'll just get the template going to the broker).

In subscription methods, I then used the direct template while in other methods I used the template that went to the broker.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.