I have been trying to test on retryWhen
operator on RxSwift
and I have encountered the Reentrancy Anomaly
issue, here's the code:
Observable<Int>.create { observer in
observer.onNext(1)
observer.onNext(2)
observer.onNext(3)
observer.onNext(4)
observer.onError(RequestError.dataError)
return Disposables.create()
}
.retryWhen { error in
return error.enumerated().flatMap { (index, error) -> Observable<Int> in
let maxRetry = 1
print("index: \(index)")
return index < maxRetry ? Observable.timer(1, scheduler: MainScheduler.instance) : Observable.error(RequestError.tooMany)
}
}
.subscribe(onNext: { value in
print("This: \(value)")
}, onError: { error in
print("ERRRRRRR: \(error)")
})
.disposed(by: disposeBag)
With the code above it gives:
This: 1
This: 2
This: 3
This: 4
index: 0
This: 1
This: 2
This: 3
This: 4
index: 1
⚠️ Reentrancy anomaly was detected.
> Debugging: To debug this issue you can set a breakpoint in /Users/tony.lin/Documents/Snippet/MaterialiseTest/Pods/RxSwift/RxSwift/Rx.swift:97 and observe the call stack.
> Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`
This behavior breaks the grammar because there is overlapping between sequence events.
Observable sequence is trying to send an event before sending of previous event has finished.
> Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,
or that the system is not behaving in the expected way.
> Remedy: If this is the expected behavior this message can be suppressed by adding `.observeOn(MainScheduler.asyncInstance)`
or by enqueing sequence events in some other way.
⚠️ Reentrancy anomaly was detected.
> Debugging: To debug this issue you can set a breakpoint in /Users/tony.lin/Documents/Snippet/MaterialiseTest/Pods/RxSwift/RxSwift/Rx.swift:97 and observe the call stack.
> Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`
This behavior breaks the grammar because there is overlapping between sequence events.
Observable sequence is trying to send an event before sending of previous event has finished.
> Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,
or that the system is not behaving in the expected way.
> Remedy: If this is the expected behavior this message can be suppressed by adding `.observeOn(MainScheduler.asyncInstance)`
or by enqueing sequence events in some other way.
ERRRRRRR: tooMany
Just wondering if anyone knows the cause of this issue?
As the console comment explains, this warning can be suppressed using .observeOn(MainScheduler.asyncInstance)
as in:
Observable<Int>.from([1, 2, 3, 4]).concat(Observable.error(RequestError.dataError))
.observeOn(MainScheduler.asyncInstance) // this is the magic that makes it work.
.retryWhen { error in
return error.enumerated().flatMap { (index, error) -> Observable<Int> in
let maxRetry = 1
print("Index:", index)
guard index < maxRetry else { throw RequestError.tooMany }
return Observable.timer(1, scheduler: MainScheduler.instance)
}
}
.subscribe(onNext: { value in
print("This: \(value)")
}, onError: { error in
print("ERRRRRRR: \(error)")
})
I took the liberty of making a few minor adjustments to your example code to show an alternate way to write what you have.
You asked to explain (a) why adding the ObserveOn works and (b) why it is needed.
What .observeOn(MainScheduler.asyncInstance)
does is route the request to an alternate thread where the event can finish and then emit the event again on the main thread. In other words, it's like doing this:
.observeOn(backgroundScheduler).observeOn(MainScheduler.instance)
Where backgroundScheduler
is defined like:
let backgroundScheduler = SerialDispatchQueueScheduler(qos: .default)
At least that's my understanding.
As for why it's needed, I can't say. You might have found a bug in the library because using a 1 second delay works fine without the observeOn.