0

I'm using Spring reactive as a server to make an expensive generation and return the results in Flux one by one. This has the advantage of stopping the generation if the request is cancelled (in cas the constraints and are too tight for example). My code look like this:

    public Flux<Entity> generate(int nbrOfEntitiesToGenerate, Constaints constraints) {
        return Flux.range(0, nbrOfEntitiesToGenerate)
            .map(x -> Generator.expensiveGeneration(constraints)
//            .subscribeOn(Schedulers.parallel())
            ;
    }

This only does half of what I want, I doesn't make the next call expensiveGeneration when cancelled, but doesn't stop currently running expensive generation that might never finish if the constraints are too tight. How can I do that please.

Extra question if you know, how can I generate x entities in parralel to maximize the use of my threads, (of course without starting ALL the generations at once).

Thanks in advance.

user1928596
  • 1,503
  • 16
  • 21
  • I'm new with Rx Programming. I'm not sure that I get all your idea here. If you want to stop running the expensive generation method so you can use Timer to stop it if it runs in a long time. If you want to stop emit next value to the stream, you can call Disposable.dispose() on subscribe object – gianglaodai Mar 10 '20 at 20:04
  • I don't want that, I want to keep calculation/generating until the user chooses to stop, when he does I want to stop consuming resources – user1928596 Mar 11 '20 at 15:23
  • @bubbles I guess so... – user1928596 Mar 11 '20 at 16:20
  • My understanding is that you can use `ThreadPoolExecutor` or something like that with a `Scheduler`. If you do that then you should be able to use normal java `Thread` interrupt mechanisms to interrupt a `Thread`. – K.Nicholas Mar 11 '20 at 16:36
  • spring-webflux use Reator. You can call Disposable.dispose() in another event. Ex: you create a Publisher that will emit stop event, then when a stop event is emit, you subscribe it by call Disposable.dispose() – gianglaodai Mar 12 '20 at 04:43
  • @K.Nicholas I think that you are right, could you provide me with a working example please, I didn't manage to do it – user1928596 Mar 12 '20 at 06:58

2 Answers2

0

Creating a Scheduler from a ExecutorService is straight forward but you need to save the Future<?> callable if you want to cancel. I changed the Generator to hold it and wrap the cancel method, which gets called when the Flux handles doOnCancel.

public class FluxPlay {
    public static void main(String[] args) {
        new FluxPlay().run();
    }
    private void run() {
        Flux<LocalDateTime> f = generate(10);
        Disposable d = f.subscribeOn(Schedulers.single()).subscribe(System.out::println);
        try {
            Thread.sleep(4500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        d.dispose();
    }

    private Flux<LocalDateTime> generate(int nbrOfEntitiesToGenerate) {
        Generator generator = new Generator();
        return Flux.range(0, nbrOfEntitiesToGenerate)
        .map(x -> generator.expensiveGeneration())
        .doOnCancel(generator::cancel)
        .doFinally(generator::shutdown)
        .publishOn(Schedulers.fromExecutor(generator::submit));
    }
}

and:

public class Generator {
    Future<?> f;
    ExecutorService es = Executors.newSingleThreadExecutor();
    public void submit(Runnable command) {
      f = es.submit(command);
    }
    public void cancel() {
        f.cancel(true);
    }
    public void shutdown(SignalType st) {
        es.shutdown();
    }
    public LocalDateTime expensiveGeneration() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
        return LocalDateTime.now();
    }
}
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
0

I had the same problem with communication between Angular and Spring Flux app. My app execute huge calculations and send by get request. Sometimes client can stop the process if it doesn't need more information. I've found an advice on StackOverflow that it can be resolved by adding additional endpoint ("api/stop}"). This endpoint will stop the execution by Flux.merge or Flux.takeUntil(in my case).

public <T> Flux<DataNode<T>> wrapFluxResponse(Flux<DataNode<T>> response) {
    return Flux.defer(() -> response)
            .takeUntil(tDataNode -> expiredReactiveRequestService.remove(tDataNode.getRequestId()));
}

@PostMapping("/stop")
public void stopReactRequest(@RequestParam String requestId) {
    expiredReactiveRequestService.addRequestIdToRemoveList(requestId);
}