How to manage access to shared resources using Project Reactor?
Given an imaginary critical component that can execute only operation at the time (file store, expensive remote service, etc), how could one orchestrate in reactive manner access to this component if there are multiple points of access to this component (multiple API methods, subscribers...)? If the resource is free to execute the operation it should execute it right away, if some other operation is already in progress, add my operation to the queue and complete my Mono once my operation is completed.
My idea is to add tasks to the flux queue which executes tasks one by one and return a Mono which will be complete once the task in the queue is completed, without blocking.
class CriticalResource {
private final Sinks.Many<Mono<?>> taskExecutor = Sinks.many()
.unicast()
.onBackpressureBuffer();
private final Disposable taskExecutorDisposable = taskExecutor.asFlux()
.concatMap(Function.identity()) //this executes actions in sequential order
.subscribe();
public Mono<Void> resourceOperation1() {
doSomething();
.as(this::sequential);
}
public Mono<Void> resourceOperation2() {
doSomethingElse();
.as(this::sequential);
}
public Mono<Void> resourceOperation3() {
doSomething();
.then(somethingElse())
.as(this::sequential);
}
private <T> Mono<T> sequential(Mono<T> action) {
return Mono.defer(() -> {
Sinks.One<T> actionResult = Sinks.one(); //create a new mono which should complete when our action mono completes.
//Since task executor subscribes to action mono, we are subscribing on action result mono
while (taskExecutor.tryEmitNext(action.doOnError(t -> actionResult.emitError(t,
Sinks.EmitFailureHandler.FAIL_FAST))
.doOnSuccess(next -> actionResult.emitValue(next,
Sinks.EmitFailureHandler.FAIL_FAST)))
!= Sinks.EmitResult.OK) {
}
return actionResult.asMono();
});
}
}
This is not a full example, as more work is needed to propagate backpressure correctly, transfer context, etc... But I wonder if there is a better way to achieve this, supported by Project Reactor?