Teodor Mysko
Teodor Mysko

Reputation: 170

WebSocketMessageBroker is not working as expected after upgrade to Spring Boot 3.3.5

I have an application that exposes WebSocket endpoint with following config

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  public static final String WS_ENDPOINT = "/notifications/v1";
  public static final String EVENTS_CHANNEL = "/events";
  public static final String ERRORS_CHANNEL = "/errors";

  @Autowired
  private StompMessageChannelInterceptor stompMessageChannelInterceptor;

  @Override
  public void registerStompEndpoints(final StompEndpointRegistry registry) {
    registry.addEndpoint(WS_ENDPOINT)
            .setAllowedOrigins("*")
            .addInterceptors(new WebsocketHandshakeInterceptor())
            .setHandshakeHandler(new WebsocketHandshakeInterceptor());
  }

  @Override
  public void configureMessageBroker(final MessageBrokerRegistry registry) {
    registry.enableSimpleBroker(EVENTS_CHANNEL, ERRORS_CHANNEL);
  }

  @Override
  public void configureClientInboundChannel(final ChannelRegistration registration) {
    registration.interceptors(stompMessageChannelInterceptor);
  }

  @Override
  public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
    registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
      @Override
      public WebSocketHandler decorate(final WebSocketHandler webSocketHandler) {
          return new WebSocketSessionCapturingHandlerDecorator(webSocketHandler);
      }
    });
  }
}

That config worked fine with Spring Boot 2.7.18. But after migrating to Spring Boot 3.3.5 the WebSocketMessagingAutoConfiguration sets the AsyncTaskExecutor. Auto configuration (in WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration) picks up our custom implementation of AsyncTaskExecutor, which is unsuitable for WebSocket config (it adds some additional logic to the runnable task). Below is the implementation of AsyncTaskExecutor that is picked up by auto configuration

public class ApiPoolExecutor extends ThreadPoolTaskExecutor {

  private static final long serialVersionUID = 1L;

  @Override
  public Future<?> submit(final Runnable task) {
    return super.submit(new SomeLogicForRunnable(task));
  }


  @Override
  public ListenableFuture<?> submitListenable(final Runnable task) {
    return super.submitListenable(new SomeLogicForRunnable(task));
  }

  @Override
  public <T> ListenableFuture<T> submitListenable(final Callable<T> task) {
    return super.submitListenable(new SomeLogicForRunnable(task));
  }

  @Override
  public void execute(final Runnable task) {
    super.execute(new SomeLogicForRunnable(task));
  }
}

I tried creating an another implementation of AsyncTaskExecutor without any additional logic around runnable task but Autoconfiguration still sees only ApiPoolExecutor implementation. I also tried setting the executor directly on the ChannelRegistration in my WebSocketConfig

  @Override
  public void configureClientInboundChannel(final ChannelRegistration registration) {
    registration.interceptors(stompMessageChannelInterceptor);
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setMaxPoolSize(100);
    executor.setCorePoolSize(100);
    executor.setBeanName("ws-pool-exec");
    executor.initialize();
    registration.executor(new WebSocketTaskPoolExecutor());
  }

but later auto configuration overrides that executor with ApiPoolExecutor. How to make auto configuration does not override the executor on ChannelRegistration? Or how to disable it at all?

Upvotes: 0

Views: 44

Answers (1)

Teodor Mysko
Teodor Mysko

Reputation: 170

I fixed it by creating my own ThreadPoolTaskExecutor as named bean. Bean name should correspond to the name that is expected by the determineAsyncTaskExecutor() method in WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration class

  @Bean (name = TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
  public ThreadPoolTaskExecutor getApplicationTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setMaxPoolSize(100);
    executor.setCorePoolSize(100);
    executor.setBeanName("ws-pool-exec");
    executor.initialize();
    return executor;
  }

Upvotes: 0

Related Questions