Rob Winch opened SPR-16481 and commented
Currently there is no equivalent of reactive ApplicationEventPublisher
which means the only way to publish events is blocking and cannot be performed in a WebFlux
application. This impacts Spring Security applications which currently cannot publish authentication events. See https://github.com/spring-projects/spring-security/issues/4961
Affects: 5.0.3
Issue Links: - #21831 Support for non-blocking event listener methods
1 votes, 8 watchers
Comment From: spring-projects-issues
Juergen Hoeller commented
The use of ApplicationEventPublisher
isn't necessarily blocking, and in particular not dependent on I/O. Depending on the ApplicationEventMulticaster
setup (e.g. SimpleApplicationEventMulticaster.setTaskExecutor
), events may even be spun off to other threads immediately. But even if they're published on the same thread (as in the default setup): As long as the listeners don't perform blocking I/O, a publishEvent
call shouldn't really be considered as blocking either, should it?
So I don't think we need a Reactive Streams based variant of publishEvent
here. All we need is either an async ApplicationEventMulticaster
setup with immediate publishEvent
hand-off (which we could arrange in Boot 2.0 by simply configuring SimpleApplicationEventMulticaster.setTaskExecutor
), or simply documentation that the listener implementations need to avoid blocking I/O like anything else does in a reactive stack.
Comment From: spring-projects-issues
Rob Winch commented
The use of ApplicationEventPublisher isn't necessarily blocking
This seems like it is relying on implementation specific details
As long as the listeners don't perform blocking I/O, a publishEvent call shouldn't really be considered as blocking either, should it?
Again it seems like this would be relying on implementation specific details. If they need to do blocking IO, how would it be done? For example, a listener that writes audit events to a data store for authentication success / failure could not be used.
Comment From: spring-projects-issues
Juergen Hoeller commented
Well, I would argue that ApplicationEventPublisher
is at worst agnostic in that respect, and at best expects the listeners to not block. The whole point of event publication is quick hand-off for the publisher and no guarantees for immediate listener execution. If a listener really needs to do blocking I/O, we can either assume that it'll only be called by callers who don't care (that's an implementation detail then but by the listeners, not by the multicaster) or it'll perform the blocking tasks in an asynchronous fashion (using a TaskExecutor
or even using Reactor itself). Alternatively, in order to protect against potentially misaligned listener implementations, we also allow for async hand-off right there in the ApplicationEventMulticaster
setup. I believe that between those two approaches we have a proper solution available.
After all, reactive applications need some thorough setup and strict implementation rules everywhere in any case. Bean impls can't do blocking I/O in their init callbacks either for example, otherwise BeanFactory.getBean
calls might block. Enforcing listener implementations to comply here sounds quite straightforward to me, much more so since we're talking about events to begin with. To be clear, I don't want to go to the extent of providing a org.reactivestreams.Publisher
based variant of ApplicationEventPublisher.publishEvent
: This would suggest that you can wait on listener completion... and that would be against the grain of the event model, arguably relying on implementation-specific behavior much more than the other way round. Maybe we should officially clarify that in publishEvent
's javadoc.
Comment From: jhoeller
I've finally revised the javadoc on publishEvent
accordingly, suggesting that no specific semantics are implied beyond a hand-off to the multicaster - and that event listeners are encouraged to individually use asynchronous execution for longer-running and potentially blocking operations. The latter has always been possible programmatically or with the use of @Async
, and is now more sophisticated in 5.2 with reactive event listener methods (#21831).
For reactive event publication purposes, the best you can do to integrate the hand-off step into a reactive pipeline is Mono.fromRunnable(() -> context.publishEvent(...))
, I suppose. Since we are not propagating anything other than the event itself between an event publisher and an event listener (as per the idea of decoupled events), we are currently unable to propagate a Reactor context as well, so a publishEvent
variant with a Mono
return type would arguably send a misleading signal at this point.
I'm moving this to the backlog for revisiting a fully Reactor-based multicaster with tighter publisher-listener interaction at a later point. For the time being, anything that the Reactor context could contain will have to be included in the event instance itself, including contextual transaction and security holders, so that a listener can independently operate on the complete specific context that it needs.
Comment From: codependent
I found this issue after opening an ticket for failing to use a @TransactionalEventListener
in a Webflux application.
At the moment it seems it can't be done. Are there any plans to provide a Reactor-based ApplicationEventMulticaster
that would allow the transactional context to be propagated?
Regarding the suggested workaround:
For the time being, anything that the Reactor context could contain will have to be included in the event instance itself, including contextual transaction and security holders, so that a listener can independently operate on the complete specific context that it needs.
Could you clear up how this could be done?
Comment From: jhoeller
As of 6.1, @TransactionalEventListener
can be triggered with reactive transactions through adding the transaction context as the event source: https://github.com/spring-projects/spring-framework/issues/27515#issuecomment-1660934318