RxJava Observable minimum execution time - java

I have an Observable (which obtains data from network).
The problem is that observable can be fast or slow depending on network conditions.
I show progress widget, when observable is executing, and hide it when observable completes. When the network is fast - progress flikers (appears and disappears). I want to set minimum execution time of observable to 1 second. How can I do that?
"Delay" operator is not an option because it will delay even for slow network.

You can use Observable.zip() for that. Given
Observable<Response> network = ...
One can do
Observable<Integer> readyNotification = Observable.just(42).delay(1, TimeUnit.SECONDS);
Observable delayedNetwork = network.zipWith(readyNotification,
(response, notUsed) -> response);

Use Observable.concatEager()
It allows you to force one stream to complete after another (concat operator), but also kick off the network request immediately without having to wait for the first argument observable to complete (concatEager):
Observable<Response> responseObservable = ...;
Observable<Response> responseWithMinDelay = Observable.concatEager(
Observable.timer(1, TimeUnit.SECONDS).ignoreElements(),
responseObservable
).cast(Response.class);

It looked like Observable.zip would be a reasonable approach, and it seemed to work well until there was an error emitted; then it didn't wait for the expected time.
This seemed to work well for me:
Observable.mergeDelayError(
useCase.execute(), // can return Unit or throw error
Observable.timer(1, TimeUnit.SECONDS)
)
.reduce { _, _ -> Unit }
.doOnError { /* will wait at least 1 second */ }
.subscribe { /* will wait at least 1 second */ }

Related

Detect timeouts in a CompletableFuture chain

Is there any possible safe way to detect timeouts in a CompletableFuture chain?
O someValue = CompletableFuture.supplyAsync(() -> {
...
// API Call
...
}).thenApply(o -> {
...
}).thenApply(o -> {
// If the chain has timed out, I still have 'o' ready here
// So at least cache it here, so it's available for the next request
// Even though the current request will return with a 'null'
...
}).get(10, TimeUnit.SECONDS);
// cache 'someValue'
return someValue;
It completes successfully without a timeout, I can use 'someValue' and do whatever with it
If it times out, it throws a TimeoutException and I have lost the value, even though it's still being processed in the background
The idea is that even if it times out and since the API call in the thread still completes in the background and returns the response, I can use that value, let's say, for caching
Not at least in the way you show. When the exception is thrown, you lose any chance of getting your hands on the results of the API call even if it finishes. Your only chances of caching in a chain like that would be something like the following, which would not help with the time-outing API call itself
.thenApplyAsync(o -> {
cache = o;
// do something
}).thenApplyAsync(o -> {
cache = o;
// do something more
}).get(10, TimeUnit.SECONDS);
However reading through this gave me an idea, that what if you did something like the following
SynchronousQueue<Result> q = new SynchronousQueue<>();
CompletableFuture.supplyAsync(() -> {
// API call
}.thenAccept(result -> {
cache.put(result); // cache the value
q.offer(result); // offer value to main thread, if still there
}
);
// Main thread waits 10 seconds for a value to be asynchronously offered into the queue
// In case of timeout, null is returned, but any operations done
// before q.offer(result) are still performed
return queue.poll(10, TimeUnit.SECONDS);
An API call that doesn't finish in 10 seconds is still processed into cache as it is asynchronously accepted and the timeout happens in the main thread and not the CompletableFuture chain, even though the original request won't get the results (and I guess has to deal with it gracefully).

Mono/Flux: how to suspend thread and wait for event or timeout

I would like to ask advice how to solve with Mono/Flux suspending and reactivating by delay or timeout.
The task is: application will receive HTTP request and should provide response.
When request is received using async sockets message should be sent. And we need to wait for specific answer, but no more than 30 seconds.
So I need to suspend thread until some Runnable will be called, or other option is each 0.2 sec query some variable and if it set to relevant value continue the process.
Could you please give me some suggestions?
Thank you
I think you need to use the timeout method from Mono/Flux, to set that behaviour. Example:
yourMonoOrFlux.timeout(Duration.ofSeconds(30))
.onErrorResume(yourFallbackMethod)
... //some other chained operations
It's also possible to set a fallback method when something goes wrong, using the onErrorResume method.
But if you need to really block the thread within those 30 secs, you should use block method rather than timeout. Example:
yourMonoOrFlux.block(Duration.ofSeconds(30))
... //other chained operations
Reference on the official reactor documentation
Finally I found the solution.
Maybe not most elegant but works, using recursion
This code querying variable state to get positive response, but not more than 10 seconds timeout.
val delayDuration = Duration.ofMillis(200)
val maximumAttempts = 50
fun createDelayedMono(counter : Int) : Mono<BigInteger> {
val mono = Mono.delay(delayDuration).flatMap {
it ->
if (counter < maximumAttempts && reactorHelper.isEventCompleted(rrn)) {
reactorHelper.removeEvent(rrn)
return#flatMap Mono.just(BigInteger.ZERO)
} else {
return#flatMap createDelayedMono(counter + 1)
}
}
return mono
}

How to let my rx stream react if my subscription is disposed?

I have a Flowable stream that concatenates multiple streams together:
Flowable
.empty()
.concatWith(longOperationA())
.concatWith(longOperationB())
.onErrorResumeNext(throwable -> {
// some cleanup tasks
return Flowable.error(throwable);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(mySubscriber);
Both longOperationA() and longOperationB() emit items.
Regarding what circumstances arise (an error occurs or mySubscriber gets
disposed), I want to let my stream act differently. The error case is covered
by the onErrorResumeNext() callback, but not the case when mySubscriber
becomes disposed.
How can I change my stream to do another task when the subscriber is disposed of?
To give more context about this, I have tried doOnCancel():
Flowable
.concatWith(longOperationA())
.concatWith(longOperationB())
.doOnCancel(() -> {
// some cleanup tasks
})
.onErrorResumeNext(throwable -> { ...
However, doOnCancel() doesn't only get called when mySubscription becomes disposed,
but also when longOperationA() (and `longOperationB() respectively) is finished.
Is there any other way to let my stream react to the dispose event?
You can use doOnDispose for this:
http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Observable.html#doOnDispose-io.reactivex.functions.Action-

Mono.count block indefinitely

I ran this:
Mono<Void> mono = Mono.empty();
System.out.println("mono.block: " + mono.block());
and it produces:
mono.block: null
as expected. In other words, calling block will return immediately if the Mono already completed.
Another example, resembling the real-world scenario. I have a source flux, e.g.:
Flux<Integer> ints = Flux.range(0, 2);
I make a connectable flux that I will use to allow multiple subscribers:
ConnectableFlux<Integer> publish = ints.publish();
For this example, let's say there's a single real-work subscriber:
publish
.doOnComplete(() -> System.out.println("publish completed"))
.subscribe();
and another subscriber that just produces the element count:
Mono<Long> countMono = publish
.doOnComplete(() -> System.out.println("countMono completed"))
.count();
countMono.subscribe();
I connect the connectable flux and print the element count:
publish.connect();
System.out.println("block");
long count = countMono.block();
System.out.println("count: " + count);
This prints:
publish completed
countMono completed
block
In other words, both subscribers subscribe successfully and complete, but then countMono.block() blocks indefinitely.
Why is that and how do I make this work? My end goal is to get the count of the elements.
You can get this to work by using autoConnect or refCount instead of manually calling connect().
For example:
Flux<Integer> ints = Flux.range(0, 2);
Flux<Integer> publish = ints.publish()
.autoConnect(2); // new
publish
.doOnComplete(() -> System.out.println("publish completed"))
.subscribe();
Mono<Long> countMono = publish
.doOnComplete(() -> System.out.println("countMono completed"))
.count();
// countMono.subscribe();
long count = countMono.block();
System.out.println("count: " + count);
Why does your example not work?
Here is what I think is happening in your example... but this is based on my limited knowledge, and I'm not 100% sure it is correct.
.publish() turns the upstream source into a hot stream
You then subscribe twice (but these don't start the flow yet, since the connectable flux is not connected to the upstream yet)
.connect() subscribes to the upstream, and starts the flow
The upstream, and the two subscriptions that were registered before connect() complete (since this is all happening in the main thread)
At this point the ConnectableFlux is no longer connected to the upstream, because the upstream has completed (The reactor docs are light on details on what happens to a ConnectableFlux when new subscriptions arrive after the upstream source completes, so this is what I'm not 100% certain about.)
block() creates a new subscription.
But since the ConnectableFlux is no longer connected, no data is flowing
If you were to call connect() again (from another thread, since the main thread is blocked), data would flow again, and the block() would complete. However, this would be a new sequence (not the original sequence that completed in step 4)
Why does my example work?
Only two subscriptions are created (instead of 3 in your example), one from a .subscribe() call, and one from .block(). The ConnectableFlux auto connects after 2 subscriptions, and therefore the block() subscription completes. Both subscriptions share the same upstream sequence.

RxJava - Emit items like in a queue

I have an observable that:
emits data after few seconds.
can be triggered several times.
the operation can't be executed in parallel. So we need a buffer.
I understand that this isn't clear so let me explain with example:
Observable<IPing> pingObservable = Observable.defer(() ->
new PingCommand(account, folders)
.post()
.asObservable()
);
this is the main feature. It shouldn't be called again while a previous one is executing, but it should remember that user requests it again. So I created close buffer as PublishSubject
closeBuffer = PublishSubject.create();
now I'm wondering how to merge it.
I have tried this:
Observable.defer(() -> new PingCommand(account, folders)
.post()
.asObservable()
.buffer(() -> closeBuffer)
.flatMap(Observable::from)
.first()
);
but it is not working as I want.
Edit:
I will try to explain that better:
I'm sending POST to the server - We can wait for a response several MINUTES (because it is Exchange ActiveSync PUSH). I cannot ping again while one request is sending. So I have to wait until one request is done. I don't need to buffer those observables - just information if an user is requesting ping - and send request after a first one is done. I'm just learning reactive so I don't know how to really use complicated functions like backpressure.
This is how I want this problem to be solved (pseudo code)
??????<Result> request
= ????.???()
.doOnNext( result -> { … })
.doOnSubscribe(() -> { … })
.doOnCompleted(() -> { … })
.…
//__________________________________________________________
Observable<Result> doAsyncWork(Data data) { … } // this is API function
//__________________________________________________________
// api usage example
Subscription s1 = doAsyncWork(someData).subscribe() // start observing async work; executed doOnSubscribe
Subscription s2 = doAsyncWork(someData).subscribe() // wait for async work result …
//__________________________________________________________
// after some time pass, maybe from other thread
Subscription s1 = doAsyncWork(someData).subscribe() // wait for async work result …
//__________________________________________________________
// async work completes, all subscribers obtain the same result; executed doOnCompleted
//__________________________________________________________
// again
Subscription s1 = doAsyncWork(someData).subscribe() // start observing async work; executed doOnSubscribe
// async work completes, subscriber obtains result; executed doOnCompleted
Obviously, I can use if instead but I want to know how to do it in a proper way.

Categories

Resources