After using project Reactor for a while, I'm quite thirsty and would really like a beer.
To get this beer I contact two restaurants in parallel, as I am in a rush the first one that returns me a menu with beer is the one I want to drink at; I no longer care about the response from the other restaurant so I'd like to interrupt that call.
It could however be that the menu does not contain beer and that neither restaurant serves beer.
To simulate I have the following Mono's:
public static Mono<String> contactSosDiner() {
return Mono.fromCallable(() -> {
Thread.sleep(1000);
return "Beer!";
});
}
public static Mono<String> contactBurgerKing() {
return Mono.fromCallable(() -> {
Thread.sleep(1500);
return "No Beer, only whopper";
});
}
The sleep timeouts should be seen as random. This results in the following scenario's:
- StackOverflow's Diner returns before Burger King with Beer --> cancel the Burger King call and continue
- StackOverflow's Diner returns before Burger King without Beer --> continue waiting on the Burger King call hoping they do serve beer
and vice versa for the Burger King calls. Whenever one returns with "Beer!" I'd like to interrupt the other Mono instead of waiting on the result.
My current implementation (also MCVE) achieves this but I feel like this is an overly complex solution.
public class DemoApplication {
// allows usage in lambda combined with later initalisation
static final AtomicReference<Disposable> bkDisposableRef = new AtomicReference<>();
static final AtomicReference<Disposable> soDisposableRef = new AtomicReference<>();
static final AtomicReference<String> result = new AtomicReference<>();
public static void main(String[] args) throws Exception {
Disposable soDisposable = sosDiner()
.doOnCancel(() -> System.out.println("Sos Diner got cancelled"))
.subscribeOn(Schedulers.boundedElastic())
.subscribe(value -> {
var disposable = bkDisposableRef.get();
if(disposable != null) {
System.out.println("Disposed burger king disposable");
disposable.dispose();
}
result.set("Getting a beer at SOs Diner");
});
soDisposableRef.set(soDisposable);
Disposable bkDisposable = burgerKing()
.doOnCancel(() -> System.out.println("Burger King got cancelled"))
.subscribeOn(Schedulers.boundedElastic())
.subscribe(value -> {
var disp = soDisposableRef.get();
if(disp != null) {
System.out.println("Disposed Sos Diner Mono");
disp.dispose();
}
result.set("Getting a beer at Burger King!");
});
bkDisposableRef.set(bkDisposable);
int count = 0;
while(!soDisposable.isDisposed() && !bkDisposable.isDisposed() && count < 100) {
Thread.sleep(200);
count++;
}
System.out.println("Done: " + result.get());
}
public static Mono<String> sosDiner() throws Exception {
return Mono.fromCallable(() -> {
Thread.sleep(1000);
return "Beer!";
});
}
public static Mono<String> burgerKing() {
return Mono.fromCallable(() -> {
Thread.sleep(1500);
return "No beer, only whopper";
});
}
}
This results in the following logs:
Disposed burger king disposable
Burger King got cancelled
14:33:53.605 [boundedElastic-2] ERROR reactor.core.publisher.Operators - Operator called default onErrorDropped
java.lang.InterruptedException: uninteresting stacktrace
Done: Getting a beer at SOs Diner
so as you can see it works as expected. It feels overly complex and I feel like there should be a more elegant solution. Can you think of any?
I can't seem to figure out how to f.e. implement a .firstWithValue with conditions based on the value (if there is any value at all as both restaurants don't have to serve beer).