Is there a way to ensure that all transformation steps for a single Mono that created from a future are executed on the thread that subscribes and blocks?
For instance, the following code
public static void main(String[] args) {
var future = new CompletableFuture<String>();
var res = Mono.fromFuture(future).map(val -> {
System.out.println("Thread: " + Thread.currentThread().getName());
return val + "1";
});
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
future.complete("completed");
}, "completer").start();
res.block();
}
prints Thread: completer because the future is completed from the "completer" thread. I'm trying to figure out if there is a way to make it always print Thread: main.
No. When the main thread is blocked via .block(), that thread is specifically waiting for the onNext, onComplete, or onError signals of the stream (after all the upstream operators have executed). It does not somehow regain control before the upstream operators are invoked in order to execute the operators.
The closest thing you can do would be to ensure that:
the subscription is executed on a specific Scheduler (via .subscribeOn) AND
the future's completion value is published on the same Scheduler (via .publishOn).
For example:
Scheduler scheduler = Schedulers.parallel();
var res = Mono.fromFuture(future)
.doFirst(() -> { // Note: doFirst added in 3.2.10.RELEASE
// prints a thread in the parallel Scheduler (specified by subscribeOn below)
System.out.println("Subscribe Thread: " + Thread.currentThread().getName());
})
// specifies the Scheduler on which the the completion value
// from above is published for downstream operators
.publishOn(scheduler)
.map(val -> {
// prints a thread in the parallel Scheduler (specified by publishOn above)
System.out.println("Operator Thread: " + Thread.currentThread().getName());
return val + "1";
})
// specifies the Scheduler on which upstream operators are subscribed
.subscribeOn(scheduler);
However, note the following:
The subscribe occurs on a thread in the Scheduler, not the blocked main thread.
This approach just ensures that the same Scheduler is used, not the same Thread within the Scheduler. You could theoretically force the same Thread by using a single-threaded scheduler (e.g. Schedulers.newParallel("single-threaded", 1))
The .publishOn does not enforce that all operators operate on that Scheduler. It just affects downstream operators until the next .publishOn, or until the next async operator (such as .flatMap) which might utilize a different Scheduler.
As a very non-optimised proof of concept, this can be achieved in the following way:
Let's create an executor which is able to execute tasks "on demand" in a controlled way.
private static class SelfEventLoopExecutor implements Executor {
private final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
#Override
public void execute(Runnable command) {
boolean added = queue.add(command);
assert added;
}
public void drainQueue() {
Runnable r;
while ((r = queue.poll()) != null) {
r.run();
}
}
}
Next, create a subscriber which is able to use the executor to execute the tasks while waiting for the result instead of completely blocking the thread.
public static class LazyBlockingSubscriber<T> implements Subscriber<T> {
private final SelfEventLoopExecutor selfExec;
private volatile boolean completed = false;
private volatile T value;
private volatile Throwable ex;
public LazyBlockingSubscriber(SelfEventLoopExecutor selfExec) {
this.selfExec = selfExec;
}
#Override
public void onSubscribe(Subscription s) {
s.request(1);
}
#Override
public void onNext(T t) {
value = t;
completed = true;
}
#Override
public void onError(Throwable t) {
ex = t;
completed = true;
}
#Override
public void onComplete() {
completed = true;
}
public T block() throws Throwable {
while (!completed) {
selfExec.drainQueue();
}
if (ex != null) {
throw ex;
}
return value;
}
}
Now, we can modify the code in the following way
public static void main(String[] args) throws Throwable {
var future = new CompletableFuture<String>();
var selfExec = new SelfEventLoopExecutor(); // our new executor
var res = Mono.fromFuture(future)
.publishOn(Schedulers.fromExecutor(selfExec)) // schedule on the new executor
.map(val -> {
System.out.println("Thread: " + Thread.currentThread().getName());
return val + "1";
});
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
future.complete("completed");
}, "completer").start();
var subs = new LazyBlockingSubscriber<String>(selfExec); // lazy subscribe
res.subscribeWith(subs);
subs.block(); // spin wait
}
As a result, the code prints Thread: main.
I am learning RxJava and am testing a scenario where I read data from a DB and then post it to a Queue. I just made a sample mock of the whole process but I don't seem to find the Observable working as I wanted it to ie. asynchronously.
This is my code:
package rxJava;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Observer;
import rx.functions.Action1;
public class TestClass {
public static void main(String[] args) {
TestClass test = new TestClass();
System.out.println("---START---");
test.getFromDB().subscribe(new Observer<String>() {
#Override
public void onCompleted() {
System.out.println("Publish complete.");
}
#Override
public void onError(Throwable t) {
System.out.println(t.getMessage());
}
#Override
public void onNext(String s) {
test.publishToQueue(s).subscribe(new Observer<Boolean>() {
#Override
public void onNext(Boolean b) {
if (b) {
System.out.println("Successfully published.");
}
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable arg0) {
}
});
};
});
System.out.println("---END---");
}
public Observable<String> getFromDB() {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 30; i++) {
list.add(Integer.toString(i));
}
return Observable.from(list).doOnNext(new Action1<String>() {
#Override
public void call(String temp) {
if (temp.contains("2")) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
public Observable<Boolean> publishToQueue(String s) {
return Observable.defer(() -> {
try {
if (s.contains("7")) {
Thread.sleep(700);
}
System.out.println("Published:: " + s);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Observable.just(true);
});
}
}
Suppose I get a list from the DB asynchronously and want to post it to the queue,. I have used an Observable returned from getFromDB and have subscribed to it which mimics the data I get from DB. Every time I get the data from DB , I want to push it to a queue using publishToQueue which also returns an Observable. I wanted to make the queue call also asynchronous. Now on positive acknowledgement from the queue such as the Boolean which I am returning (Observable<Boolean>), I want to print something.
So basically I just want both the processes to be asynchronous. For every data from DB, I push it to the Queue asynchronously.
I have added Thread.sleep() in both the methods, db call and queue so as to mimic a delay and to test the asynchronous operations. I think this is what causing the problem. But I also tried Obseravable.delay() but that doesn't even produce any output.
Please help me understand how this works and how I can make it work as I want it to.
You have to specified subscribeOn value.
Observable.just("one", "two", "three", "four", "five")
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(/* an Observer */);
http://reactivex.io/documentation/operators/subscribeon.html
By default, RxJava is synchronous. It means that everything will be perform in the same thread (and the current thread), by default. You can perform tasks in another thread thanks to observeOn / subscribeOn methods, or using some operators that perform tasks in another job (because it use another scheduler, like delay, interval, ...)
In your example, you have to explitly set in which scheduler the subscription will pe performed. (here, in which thread Observable.from will emit your list)
test.getFromDb()
.subscribeOn(Schedulers.io())
.subscribe();
Then you can use the flatMap operator and calling your publishToQueue method. This method will be executed in the previous scheduler, but you can force it to use another scheduler, thanks to observeOn method. Everything after the observeOn method will be executed in another thread.
test.fromDb()
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.flatMap(l -> test.publishToqueue(l))
.subscribe();
So I have a list of Track Ids that for each track Id I need to execute a network request to get the track details, I am using a for loop to launch all the requests and a latch to wait for all the requests to be completed. After they are completed then the callback is sent with the List of Tracks that have already populated.
I would like to know if there is any better way to do this, maybe with RxJava ?
I am using Retrofit 2.0 in Android.
public IBaseRequest batchTracksById(final TrackIdList trackIdListPayload, final IRequestListener<TracksList> listener) {
final TracksList tracks = new TracksList();
final Track[] trackArray = newrack[trackIdListPayload.getTrackIds().length];
tracks.setTrack(trackArray);
final CountDownLatch latch = new CountDownLatch(trackArray.length);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
try {
latch.await();
handler.post(new Runnable() {
#Override
public void run() {
listener.onRequestUpdate(null, tracks, null, true);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
for (String id : trackIdListPayload.getTrackIds()) {
getTrackById(id, new IRequestListener<Track>() {
#Override
public void onRequestFailure(IBaseRequest request, Exception exception) {
latch.countDown();
}
#Override
public void onRequestUpdate(IBaseRequest request, Track track, RequestState state, boolean requestComplete) {
//iterate through the tracks and update the ones in the thing
int i = 0;
for (String s : trackIdListPayload.getTrackIds()) {
if (s.equals(track.getTrackId())) {
trackArray[i] = track;
// don't break here, as we may have a case where we have multiple instances of the same trackId (although
// at the moment a request will be made for each anyway...
}
i++;
}
latch.countDown();
}
});
}
return null;
}
If you want to make all the requests asynchronously and wait for them to return you can do this (lambdas for brevity and readability):
tracks.flatMap(track -> getTrackDetails(track.id)
.subscribeOn(Schedulers.io()))
.toList()
.doOnNext(list -> processTrackList())
...
If you require that the results are returned in the order of tracks but are still requested asynchronously then in soon to be released rxjava 1.0.15 you will be able to do this
tracks.concatMapEager(track -> getTrackDetails(track.id)
.subscribeOn(Schedulers.io())
.toList()
.doOnNext(list -> processTrackList())
...
If I understand correctly, you have a list of tracks as input and you want a list of webservice results. Here's a simple way to do that with RxJava if you can make your network call synchronous (rxjava will handle the background processing for you).
Observable.from(trackList)
.map(new Func1<Track, Response>() {
#Override
public Response call(Track track) {
return makeRequestSynchronously(track.id());
}
})
.toList()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Response>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(List<Response> responses) {
}
});
Edit: You can change Retrofit to return observables from webservice, if you do that you will need to change map to the following
.flatMap(new Func1<Track, Observable<Response>>() {
#Override
public Observable<Response> call(Track track) {
return makeRequestThatReturnsObservable(track.id());
}
})
I'm experimenting with RxJava and Java 8's CompletableFuture class
and do not quite get how to handle timeout conditions.
import static net.javacrumbs.futureconverter.java8rx.FutureConverter.toObservable;
// ...
Observable<String> doSomethingSlowly() {
CompletableFuture<PaymentResult> task = CompletableFuture.supplyAsync(() -> {
// this call may be very slow - if it takes too long,
// we want to time out and cancel it.
return processor.slowExternalCall();
});
return toObservable(task);
}
// ...
doSomethingSlowly()
.single()
.timeout(3, TimeUnit.SECONDS, Observable.just("timeout"));
This basically works (if the timeout of three seconds is reached, "timeout" is published). I would however additionally want to cancel the future task that I've wrapped in an Observable - is that possible with an RxJava-centric approach?
I know that one option would be to handle the timeout myself, using task.get(3, TimeUnit.SECONDS), but I wonder if it's possible to do all task handling stuff in RxJava.
Yes, you can do this. You would add a Subscription to the Subscriber.
This allows you to listen in on unsubscriptions, which will happen if you explicitly call subscribe().unsubscribe() or if the Observable completes successfully or with an error.
If you see an unsubscription before the future has completed, you can assume it's because of either an explicit unsubscribe or a timeout.
public class FutureTest {
public static void main(String[] args) throws IOException {
doSomethingSlowly()
.timeout(1, TimeUnit.SECONDS, Observable.just("timeout"))
.subscribe(System.out::println);
System.in.read(); // keep process alive
}
private static Observable<String> doSomethingSlowly() {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return "Something";
});
return toObservable(future);
}
private static <T> Observable<T> toObservable(CompletableFuture<T> future) {
return Observable.create(subscriber -> {
subscriber.add(new Subscription() {
private boolean unsubscribed = false;
#Override
public void unsubscribe() {
if (!future.isDone()){
future.cancel(true);
}
unsubscribed = true;
}
#Override
public boolean isUnsubscribed() {
return unsubscribed;
}
});
future.thenAccept(value -> {
if (!subscriber.isUnsubscribed()){
subscriber.onNext(value);
subscriber.onCompleted();
}
}).exceptionally(throwable -> {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(throwable);
}
return null;
});
});
}
}
I am using rxjava in my Android app to handle network requests asynchronously. Now I would like to retry a failed network request only after a certain time has passed.
Is there any way to use retry() on an Observable but to retry only after a certain delay?
Is there a way to let the Observable know that is is currently being retried (as opposed to tried for the first time)?
I had a look at debounce()/throttleWithTimeout() but they seem to be doing something different.
Edit:
I think I found one way to do it, but I'd be interested in either confirmation that this is the correct way to do it or for other, better ways.
What I am doing is this: In the call() method of my Observable.OnSubscribe, before I call the Subscribers onError() method, I simply let the Thread sleep for the desired amount of time. So, to retry every 1000 milliseconds, I do something like this:
#Override
public void call(Subscriber<? super List<ProductNode>> subscriber) {
try {
Log.d(TAG, "trying to load all products with pid: " + pid);
subscriber.onNext(productClient.getProductNodesForParentId(pid));
subscriber.onCompleted();
} catch (Exception e) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e.printStackTrace();
}
subscriber.onError(e);
}
}
Since this method is running on an IO thread anyway it does not block the UI. The only problem I can see is that even the first error is reported with delay so the delay is there even if there's no retry(). I'd like it better if the delay wasn't applied after an error but instead before a retry (but not before the first try, obviously).
You can use the retryWhen() operator to add retry logic to any Observable.
The following class contains the retry logic:
RxJava 2.x
public class RetryWithDelay implements Function<Observable<? extends Throwable>, Observable<?>> {
private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
#Override
public Observable<?> apply(final Observable<? extends Throwable> attempts) {
return attempts
.flatMap(new Function<Throwable, Observable<?>>() {
#Override
public Observable<?> apply(final Throwable throwable) {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Observable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Observable.error(throwable);
}
});
}
}
RxJava 1.x
public class RetryWithDelay implements
Func1<Observable<? extends Throwable>, Observable<?>> {
private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
#Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts
.flatMap(new Func1<Throwable, Observable<?>>() {
#Override
public Observable<?> call(Throwable throwable) {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Observable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Observable.error(throwable);
}
});
}
}
Usage:
// Add retry logic to existing observable.
// Retry max of 3 times with a delay of 2 seconds.
observable
.retryWhen(new RetryWithDelay(3, 2000));
Inspired by Paul's answer, and if you are not concerned with retryWhen problems stated by Abhijit Sarkar, the simplest way to delay resubscription with rxJava2 unconditionnaly is :
source.retryWhen(throwables -> throwables.delay(1, TimeUnit.SECONDS))
You may want to see more samples and explanations on retryWhen and repeatWhen.
This example works with jxjava 2.2.2:
Retry without delay:
Single.just(somePaylodData)
.map(data -> someConnection.send(data))
.retry(5)
.doOnSuccess(status -> log.info("Yay! {}", status);
Retry with delay:
Single.just(somePaylodData)
.map(data -> someConnection.send(data))
.retryWhen((Flowable<Throwable> f) -> f.take(5).delay(300, TimeUnit.MILLISECONDS))
.doOnSuccess(status -> log.info("Yay! {}", status)
.doOnError((Throwable error)
-> log.error("I tried five times with a 300ms break"
+ " delay in between. But it was in vain."));
Our source single fails if someConnection.send() fails.
When that happens, the observable of failures inside retryWhen emits the error.
We delay that emission by 300ms and send it back to signal a retry.
take(5) guarantees that our signaling observable will terminate after we receive five errors.
retryWhen sees the termination and doesn't retry after the fifth failure.
This is a solution based on Ben Christensen's snippets I saw, RetryWhen Example, and RetryWhenTestsConditional (I had to change n.getThrowable() to n for it to work). I used evant/gradle-retrolambda to make the lambda notation work on Android, but you don't have to use lambdas (although it's highly recommended). For the delay I implemented exponential back-off, but you can plug in what ever backoff logic you want there. For completeness I added the subscribeOn and observeOn operators. I'm using ReactiveX/RxAndroid for the AndroidSchedulers.mainThread().
int ATTEMPT_COUNT = 10;
public class Tuple<X, Y> {
public final X x;
public final Y y;
public Tuple(X x, Y y) {
this.x = x;
this.y = y;
}
}
observable
.subscribeOn(Schedulers.io())
.retryWhen(
attempts -> {
return attempts.zipWith(Observable.range(1, ATTEMPT_COUNT + 1), (n, i) -> new Tuple<Throwable, Integer>(n, i))
.flatMap(
ni -> {
if (ni.y > ATTEMPT_COUNT)
return Observable.error(ni.x);
return Observable.timer((long) Math.pow(2, ni.y), TimeUnit.SECONDS);
});
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(subscriber);
instead of using MyRequestObservable.retry I use a wrapper function retryObservable(MyRequestObservable, retrycount, seconds) which return a new Observable that handle the indirection for the delay so I can do
retryObservable(restApi.getObservableStuff(), 3, 30)
.subscribe(new Action1<BonusIndividualList>(){
#Override
public void call(BonusIndividualList arg0)
{
//success!
}
},
new Action1<Throwable>(){
#Override
public void call(Throwable arg0) {
// failed after the 3 retries !
}});
// wrapper code
private static <T> Observable<T> retryObservable(
final Observable<T> requestObservable, final int nbRetry,
final long seconds) {
return Observable.create(new Observable.OnSubscribe<T>() {
#Override
public void call(final Subscriber<? super T> subscriber) {
requestObservable.subscribe(new Action1<T>() {
#Override
public void call(T arg0) {
subscriber.onNext(arg0);
subscriber.onCompleted();
}
},
new Action1<Throwable>() {
#Override
public void call(Throwable error) {
if (nbRetry > 0) {
Observable.just(requestObservable)
.delay(seconds, TimeUnit.SECONDS)
.observeOn(mainThread())
.subscribe(new Action1<Observable<T>>(){
#Override
public void call(Observable<T> observable){
retryObservable(observable,
nbRetry - 1, seconds)
.subscribe(subscriber);
}
});
} else {
// still fail after retries
subscriber.onError(error);
}
}
});
}
});
}
Based on kjones answer here is Kotlin version of RxJava 2.x retry with a delay as an extension. Replace Observable to create the same extension for Flowable.
fun <T> Observable<T>.retryWithDelay(maxRetries: Int, retryDelayMillis: Int): Observable<T> {
var retryCount = 0
return retryWhen { thObservable ->
thObservable.flatMap { throwable ->
if (++retryCount < maxRetries) {
Observable.timer(retryDelayMillis.toLong(), TimeUnit.MILLISECONDS)
} else {
Observable.error(throwable)
}
}
}
}
Then just use it on observable observable.retryWithDelay(3, 1000)
retryWhen is a complicated, perhaps even buggy, operator. The official doc and at least one answer here use range operator, which will fail if there are no retries to be made. See my discussion with ReactiveX member David Karnok.
I improved upon kjones' answer by changing flatMap to concatMap and by adding a RetryDelayStrategy class. flatMap doesn't preserve order of emission while concatMap does, which is important for delays with back-off. The RetryDelayStrategy, as the name indicates, let's the user choose from various modes of generating retry delays, including back-off.
The code is available on my GitHub complete with the following test cases:
Succeeds on 1st attempt (no retries)
Fails after 1 retry
Attempts to retry 3 times but succeeds on 2nd hence doesn't retry 3rd time
Succeeds on 3rd retry
See setRandomJokes method.
Same answer as from kjones but updated to latest version
For RxJava 2.x version: ('io.reactivex.rxjava2:rxjava:2.1.3')
public class RetryWithDelay implements Function<Flowable<Throwable>, Publisher<?>> {
private final int maxRetries;
private final long retryDelayMillis;
private int retryCount;
public RetryWithDelay(final int maxRetries, final int retryDelayMillis) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
}
#Override
public Publisher<?> apply(Flowable<Throwable> throwableFlowable) throws Exception {
return throwableFlowable.flatMap(new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(Throwable throwable) throws Exception {
if (++retryCount < maxRetries) {
// When this Observable calls onNext, the original
// Observable will be retried (i.e. re-subscribed).
return Flowable.timer(retryDelayMillis,
TimeUnit.MILLISECONDS);
}
// Max retries hit. Just pass the error along.
return Flowable.error(throwable);
}
});
}
}
Usage:
// Add retry logic to existing observable.
// Retry max of 3 times with a delay of 2 seconds.
observable
.retryWhen(new RetryWithDelay(3, 2000));
Now with RxJava version 1.0+ you can use zipWith to achieve retry with delay.
Adding modifications to kjones answer.
Modified
public class RetryWithDelay implements
Func1<Observable<? extends Throwable>, Observable<?>> {
private final int MAX_RETRIES;
private final int DELAY_DURATION;
private final int START_RETRY;
/**
* Provide number of retries and seconds to be delayed between retry.
*
* #param maxRetries Number of retries.
* #param delayDurationInSeconds Seconds to be delays in each retry.
*/
public RetryWithDelay(int maxRetries, int delayDurationInSeconds) {
MAX_RETRIES = maxRetries;
DELAY_DURATION = delayDurationInSeconds;
START_RETRY = 1;
}
#Override
public Observable<?> call(Observable<? extends Throwable> observable) {
return observable
.delay(DELAY_DURATION, TimeUnit.SECONDS)
.zipWith(Observable.range(START_RETRY, MAX_RETRIES),
new Func2<Throwable, Integer, Integer>() {
#Override
public Integer call(Throwable throwable, Integer attempt) {
return attempt;
}
});
}
}
You can add a delay in the Observable returned in the retryWhen Operator
/**
* Here we can see how onErrorResumeNext works and emit an item in case that an error occur in the pipeline and an exception is propagated
*/
#Test
public void observableOnErrorResumeNext() {
Subscription subscription = Observable.just(null)
.map(Object::toString)
.doOnError(failure -> System.out.println("Error:" + failure.getCause()))
.retryWhen(errors -> errors.doOnNext(o -> count++)
.flatMap(t -> count > 3 ? Observable.error(t) : Observable.just(null).delay(100, TimeUnit.MILLISECONDS)),
Schedulers.newThread())
.onErrorResumeNext(t -> {
System.out.println("Error after all retries:" + t.getCause());
return Observable.just("I save the world for extinction!");
})
.subscribe(s -> System.out.println(s));
new TestSubscriber((Observer) subscription).awaitTerminalEvent(500, TimeUnit.MILLISECONDS);
}
You can see more examples here. https://github.com/politrons/reactive
Worked from me with
//retry with retryCount time after 1 sec of delay
observable.retryWhen(throwableFlowable -> {
return throwableFlowable.take(retryCount).delay(1, TimeUnit.SECONDS);
});
Simply do it like this:
Observable.just("")
.delay(2, TimeUnit.SECONDS) //delay
.flatMap(new Func1<String, Observable<File>>() {
#Override
public Observable<File> call(String s) {
L.from(TAG).d("postAvatar=");
File file = PhotoPickUtil.getTempFile();
if (file.length() <= 0) {
throw new NullPointerException();
}
return Observable.just(file);
}
})
.retry(6)
.subscribe(new Action1<File>() {
#Override
public void call(File file) {
postAvatar(file);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
}
});
For Kotlin & RxJava1 version
class RetryWithDelay(private val MAX_RETRIES: Int, private val DELAY_DURATION_IN_SECONDS: Long)
: Function1<Observable<out Throwable>, Observable<*>> {
private val START_RETRY: Int = 1
override fun invoke(observable: Observable<out Throwable>): Observable<*> {
return observable.delay(DELAY_DURATION_IN_SECONDS, TimeUnit.SECONDS)
.zipWith(Observable.range(START_RETRY, MAX_RETRIES),
object : Function2<Throwable, Int, Int> {
override fun invoke(throwable: Throwable, attempt: Int): Int {
return attempt
}
})
}
}
(Kotlin) I little bit improved code with exponential backoff and applied defense emitting of Observable.range():
fun testOnRetryWithDelayExponentialBackoff() {
val interval = 1
val maxCount = 3
val ai = AtomicInteger(1);
val source = Observable.create<Unit> { emitter ->
val attempt = ai.getAndIncrement()
println("Subscribe ${attempt}")
if (attempt >= maxCount) {
emitter.onNext(Unit)
emitter.onComplete()
}
emitter.onError(RuntimeException("Test $attempt"))
}
// Below implementation of "retryWhen" function, remove all "println()" for real code.
val sourceWithRetry: Observable<Unit> = source.retryWhen { throwableRx ->
throwableRx.doOnNext({ println("Error: $it") })
.zipWith(Observable.range(1, maxCount)
.concatMap { Observable.just(it).delay(0, TimeUnit.MILLISECONDS) },
BiFunction { t1: Throwable, t2: Int -> t1 to t2 }
)
.flatMap { pair ->
if (pair.second >= maxCount) {
Observable.error(pair.first)
} else {
val delay = interval * 2F.pow(pair.second)
println("retry delay: $delay")
Observable.timer(delay.toLong(), TimeUnit.SECONDS)
}
}
}
//Code to print the result in terminal.
sourceWithRetry
.doOnComplete { println("Complete") }
.doOnError({ println("Final Error: $it") })
.blockingForEach { println("$it") }
}
in the event when you need to print out the retry count,
you can use the example provided in Rxjava's wiki page https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
observable.retryWhen(errors ->
// Count and increment the number of errors.
errors.map(error -> 1).scan((i, j) -> i + j)
.doOnNext(errorCount -> System.out.println(" -> query errors #: " + errorCount))
// Limit the maximum number of retries.
.takeWhile(errorCount -> errorCount < retryCounts)
// Signal resubscribe event after some delay.
.flatMapSingle(errorCount -> Single.timer(errorCount, TimeUnit.SECONDS));
Use retryWhen
/**
* Retry Handler Support
* #param errors
* #param predicate filter error
* #param maxTry
* #param periodStrategy
* #param timeUnit
* #return
*/
private Flowable<?> retrySupport(Flowable<Throwable> errors, Predicate<? super Throwable> predicate , Integer maxTry , Function<Long, Long> periodStrategy , TimeUnit timeUnit )
{
LongAdder errorCount = new LongAdder();
return errors
.doOnNext(e -> {
errorCount.increment();
long currentCount = errorCount.longValue();
boolean tryContinue = predicate.test(e) && currentCount < maxTry;
Logger.i("No. of errors: %d , %s", currentCount,
tryContinue ? String.format("please wait %d %s.", periodStrategy.apply(currentCount), timeUnit.name()) : "skip and throw");
if(!tryContinue)
throw e;
} )
.flatMapSingle(e -> Single.timer( periodStrategy.apply(errorCount.longValue()), timeUnit));
}
Sample
private Single<DeviceInfo> getErrorToken( String device)
{
return Single.error( new IOException( "network is disconnect!" ) );
}
//only retry when emit IOExpcetion
//delay 1s,2s,4s,8s,16s
this.getErrorToken( this.deviceCode )
.retryWhen( error -> retrySupport( error,
e-> e instanceof IOException,
5 ,
count-> (long)Math.pow(2,count-1),TimeUnit.SECONDS ) )
.subscribe( deviceInfo1 -> Logger.i( "----Get Device Info---" ) ,
e -> Logger.e( e, "On Error" ) ,
() -> Logger.i("<<<<<no more>>>>>"));
I'm a bit too late for this one, but just in case this could still be useful for someone, I created a Kotlin extension function for RxJava 2 that will retry with an exponential backoff strategy:
private fun <T> Observable<T>.retryWithExponentialBackoff(): Observable<T> {
val retriesSubject = BehaviorSubject.createDefault(0)
return doOnNext { retriesSubject.onNext(0) }
.retryWhen {
it.withLatestFrom(retriesSubject) { _, retryCount ->
retriesSubject.onNext(retryCount + 1)
retryCount
}.flatMap { retryCount ->
when (retryCount) {
MAX_RETRY_COUNT -> Observable.error(RuntimeException("Max number of retries reached"))
else -> Observable.timer(2.0.pow(retryCount).toLong(), SECONDS)
}
}
}
}