3

I know a Publisher must not publish concurrently, but if I use Flux#create(FluxSink), can I safely call FluxSink#next concurrently?

In other words, does Spring have internal magic that ensures proper serial publishing of events even if FluxSink#next is called concurrently?

public class FluxTest {

    private final Map<String, FluxSink<Item>> sinks = new ConcurrentHashMap<>();

    // Store a new sink for the given ID
    public void start(String id) {
        Flux.create(sink -> sinks.put(id, sink));
    }

    // Called from different threads
    public void publish(String id, Item item) {
        sinks.get(id).next(item); //<----------- Is this safe??
    }
}

It sounds to me like this paragraph in the official guide indicates that the above is indeed safe, but I'm not very confident in my understanding.

create is a more advanced form of programmatic creation of a Flux which is suitable for multiple emissions per round, even from multiple threads.

kaqqao
  • 12,984
  • 10
  • 64
  • 118

1 Answers1

8

Yes, Flux.create produces a SerializedSink that is safe to use from multiple threads for next calls

kaqqao
  • 12,984
  • 10
  • 64
  • 118
Simon Baslé
  • 27,105
  • 5
  • 69
  • 70
  • I read from multiple answers on stackoverflow that escaping the sink from the lambda is not recommended (for example https://stackoverflow.com/a/56063933 and its comments) and that you must use processors instead. Do you have info on this ? thanks – jonenst Sep 02 '20 at 10:51
  • 2
    I think that comment was more about the fact that these `FluxSink` are created _per `Subscription`_, so capturing one in a field can be misleading in case of multiple subscriptions to the `Flux.create(...)`. Note that in 3.4 we're introducing a new flavor of `Sinks` that is more tailored towards this "standalone in a field with multiple possible subscription" usage pattern. The thread safety comment still stands, eg. it is possible to spawn multiple threads from the `Flux.create` and have each feed data to the same `FluxSink`. – Simon Baslé Sep 03 '20 at 12:33
  • Awesome thanks, I was wondering about the difference between this example and the one from the docs where an event listener is created and captures the sink in its method. In both cases, the sink "escapes". I think you're right, the real difference is having multiple objects in case of multiple subscriptions. Or you could add the sinks to a list in a field instead. Thanks for the heads up for the 3.4. – jonenst Sep 04 '20 at 13:17
  • @SimonBaslé Regarding the new Sinks class in 3.4, is it possible to use `StandaloneFluxSink sink = Sinks.multicast()` that sink from multiple threads? Or is the only way to create a `SerailizedSink` through `Flux.create`? My use case is from incoming http requests (possible on different threads?) I push data to a service which does some calculations. – Dachstein Jan 15 '21 at 07:20
  • 3
    @Dachstein (StandaloneFluxSink is from an outdated Milestone or RC) `Sinks.Many` by default are _detecting_ multi-threaded usage and failing. They have a `tryEmitNext` API that will immediately return an enum, one of which represents the fact that parallel emission was attempted. you can then choose to optimistically retry/busy loop, or consider that an error. – Simon Baslé Jan 15 '21 at 09:09
  • @SimonBaslé thanks for the explanation. Can I just ask you one more? `SerializedSink` can only be created through `Flux.create` or is there a way to get it also through calling methods in the Sinks class? – Dachstein Jan 15 '21 at 10:20
  • 2
    only from Flux create. it is intended to be created one sink per subscription – Simon Baslé Jan 15 '21 at 10:59