1

I have a multicast sink with backpressure buffer with the default size. To this sink I emit events asynchronously every 25ms from a dedicated thread. There are 3 subscribers A, B and C where B pretends to do some work through thread sleep. A and C use subscribeOn and B uses publishOn on dedicated schedulers.

I have 5 questions about the behavior of the backpressure along with the schedulers and publishOn vs subscribeOn.

  1. why subscribeOn for A and C doesn't have any effect and elements are published on the emitting thread?
  2. A and C stop receiving events at 257 where B is already at ~15, shouldn't A and C stop at around ~256+15=271 ?
  3. after i=512 emissions start to fail on FAIL_OVERFLOW; is it because both buffers (B's publishOn 256 a sink's 256) are full? it is not true as B at this point already processed ~29 events
  4. A and C start receiving events back again after B=192, why 192? also, they receive the events on the B's scheduler, why?
  5. changing B's publishOn to subscribeOn pushes back the emitting thread respecting the slowest consumer (B). subscribeOn has again no effect, why?

Here is the runnable code. Reactor version 3.4.14.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Sinks;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import static java.util.concurrent.Executors.newScheduledThreadPool;
import static reactor.core.scheduler.Schedulers.fromExecutorService;

public class SinkBackpressureBuffer {

    private static final Logger LOGGER = LoggerFactory.getLogger("SinkLogger");
    
    private static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        final Sinks.Many<Integer> sink = Sinks.many().multicast().onBackpressureBuffer();

        new Thread(() -> {
            sleep(1000);
            for (int i = 1; i <= 550; i++) { // has to be bigger than 256 + 256
                final Sinks.EmitResult emitResult = sink.tryEmitNext(i);
                if (emitResult != Sinks.EmitResult.OK) {
                    LOGGER.error("Emit for i={}, res={}", i, emitResult);
                } else {
                    LOGGER.info("Emit for i={}, res={}", i, emitResult);
                }
                sleep(25);
            }
        }).start();

        sink.asFlux()
                .subscribeOn(fromExecutorService(newScheduledThreadPool(5, new NamedThreadFactory("AAA")))) // has no effect
                .subscribe(i -> {
                    LOGGER.info("A: {}", i);
                });

        sink.asFlux()
                .publishOn(fromExecutorService(newScheduledThreadPool(5, new NamedThreadFactory("BBB"))))
                .subscribe(i -> {
                    sleep(500);
                    LOGGER.info("B: {}", i);
                });

        sink.asFlux()
                .subscribeOn(fromExecutorService(newScheduledThreadPool(5, new NamedThreadFactory("CCC")))) // has no effect
                .subscribe(i -> {
                    LOGGER.info("C: {}", i);
                });

        LOGGER.info("done");
        sleep(500000);
    }

    public static class NamedThreadFactory implements ThreadFactory {
        private final AtomicInteger sequence = new AtomicInteger(1);
        private final String prefix;

        public NamedThreadFactory(String prefix) {
            this.prefix = prefix;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            int seq = sequence.getAndIncrement();
            thread.setName(prefix + (seq > 1 ? "-" + seq : ""));
            thread.setDaemon(true);
            return thread;
        }
    }

}
Tomask
  • 2,344
  • 3
  • 27
  • 37

0 Answers0