-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rxSingle/Completable/Maybe in kotlin-coroutines-rx2 use Default instead of Unconfined as default dispatcher #2925
Comments
I like this proposal, however I worry that this can potentially break a lot of existing usages. FWIW, on the project I work on, we always use our own injectable Then I wrote an internal custom Detekt rule that prohibits omitting the |
I think this is the same issue as #2485.
|
Yes, it is. I disagree with the outcome of that discussion, however.
This behavior is exactly what we expect in RxJava, though. RxJava does not defend against context switches in dependent code. If we want to be sure about what thread we are on, we must program in our own defences by adding in additional |
I probably didn't illustrate my point well enough, so I'll try again. val single: Single<Int> = rxSingle(Dispatchers.Unconfined) {
println("In `rxSingle`, the current thread is ${Thread.currentThread().name}")
withContext(Dispatchers.Default) {
println("Inside `withContext`, the thread is ${Thread.currentThread().name}")
}
println("Outside `withContext`, the thread is ${Thread.currentThread().name}")
0
}
single.observeOn(Schedulers.trampoline()).await() prints:
As you can see, after Because of this, I don't see what could be accomplished by using What I think would help is a dispatcher with which the result of the code above would be In `rxSingle`, the current thread is Test worker @coroutine#2
Inside `withContext`, the thread is DefaultDispatcher-worker-1 @coroutine#2
Outside `withContext`, the thread is Test worker @coroutine#2 This is the dispatcher that I was proposing. |
I see. I still disagree, however. Let's call the proposed dispatcher This can cause problems in production code. A real-world example can illustrate the problem. Here's an RxJava presenter, similar to many we have in our codebase: class Presenter(
val appService: AppService,
val params: ServiceParams,
val secureStore: SecureStore
) {
fun present(events: Observable<ViewEvent>): Observable<ViewModel> {
return events.flatMap { event ->
when (event) {
is SubmitClicked -> appService.submit(params)
.toObservable()
.map { result ->
secureStore.write(result)
ViewModel("Success!")
}
.startWith(ViewModel("loading"))
else -> Observable.empty()
}
}
.startWith(ViewModel("I await your service"))
}
} In this example, two operations need to be run on an IO thread: Now, let's incrementally migrate class Presenter(
val appService: AppService,
val params: ServiceParams,
val secureStore: SecureStore
) {
fun present(events: Observable<ViewEvent>): Observable<ViewModel> {
return events.flatMap { event ->
when (event) {
is SubmitClicked -> rxSingle { appService.submit(params) }
.toObservable()
.map { result ->
secureStore.write(result)
ViewModel("Success!")
}
.startWith(ViewModel("loading"))
else -> Observable.empty()
}
}
.startWith(ViewModel("I await your service"))
}
} In this case, the production So here is the problem before us: What thread should What about the proposed dispatcher? (Let's call it This is unexpected to the RxJava engineer performing this work: in RxJava, if What about So there are the choices:
|
@jingibus Hi, you're saying that I'm trying to figure out whether I need to change my |
Assuming my comment was correct at the time I wrote it, then I continue to be extremely dissatisfied at the lack of understanding of RxJava patterns shown in this design decision. We have a huge body of unit tests against asynchronous RxJava-based code; the choice of default here breaks all of them, because RxJava isn't structured concurrent and never will be. We have an obvious workaround, thankfully (deny list the default), but having put in a significant amount of work into brightening the path for folks attempting to make this paradigm shift, it's disappointing to see such stubbornness about this particular snake in the bush. |
Our team has started using kotlin-coroutines-rx2 to incrementally migrate our codebase from RxJava2 to coroutines. In so doing, we encounter a lot of call sites like the following:
The most natural choice that
kotlin-coroutines-rx2
presents us with is the following:This would be how the problem would be solved with
rxObservable
in an analogous situation....but this is not correct.
rxCompletable
usesDispatchers.Default
if no other dispatcher is specified. The resulting observable will be multithreaded, even ifSchedulers.trampoline()
were the injected value forioScheduler
. This is also inconsistent withrxObservable
, which also usesDispatchers.Unconfined
.Our RxJava2 unit testing codebase relies upon injecting
Schedulers.trampoline()
to validate reactive code on a single thread. The presence of a strayDispatchers.Default
will break any one of these tests.It would be less surprising and more consistent to use
Dispatchers.Unconfined
as the default dispatcher instead ofDispatchers.Default
.The text was updated successfully, but these errors were encountered: