-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Exception handling with structured concurrency (rx & async) #691
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
Comments
This is does so by design. The basic idea is that by default exceptions should always be thrown into the outer scope to immediately cancel all the other activities that might have been going on in this scope.
This is the default behavior. You can override this behavior in one of two ways:
Does it help? |
I can see how the default behavior would make sense for something like In my example the execution of the code inside Regarding the Rx example, I can't see a situation where I'd actually prefer the default behavior over the The main purpose of a Rx Completable is to signal if an operation has completed successfully or not, and I feel this should happen regardless of the scope the builder was executed on. My main concern at the moment is that people that are not very familiar with the way scopes work will just start adding |
@viorgu
Now consider a case when If you don't need to do multiple concurrent things, then you should not be using |
I've given this issue some thought and I think I can understand the reasoning behind this design decision. However, I recommend updating the documentation to somehow highlight the following facts (assuming I understand them correctly):
runBlocking {
val deferred = async {
error("error in async")
}
yield() // will evaluate the async block and throw the exception
deferred.await() // doesn't reach this line
}
runBlocking {
// this will still throw an exception and cancel the scope
rxCompletable { error("error") }.onErrorComplete().await()
}
runBlocking {
// the exception will be passed to the coroutine builder and handled by onErrorComplete()
rxCompletable(SupervisorJob()) { error("error") }.onErrorComplete().await()
} While I understand this is not the ideal type of code people should be writing using coroutine, I still think the behavior should be clearly documented for when someone inevitably runs into similar issues. Sorry for being so insistent on this issue but it really caught me by surprise when some tests failed after upgrading them to the structured concurrency model and I'd like to help reduce the chance that it confuses others in the future. |
Related: #753 I think... |
This makes sense, but maybe...
... should require some explicit syntax on the developer's part? If you look at #753 this kind of automatic "bubble up / cancel / kill everything" is very unexpected - and is also wrong (I think) when there is a coroutine (somewhere in the work tree) that does a catch / ignore / retry / workaround for an exception from one of its children? The way things are now - if a coroutine launches some children that it knows can fail, and has code to catch and handle any potential exception (if any is thrown), it will still get killed / cancelled, even though the developer has explicitly and intentionally added code (try / catch) in his/her coroutine to catch and do whatever is appropriate. And yet, the current design assumes that it knows better - that any exception anywhere in the tree is fatal and everything needs to be cancelled / killed. Isn't this too "arrogant"? :) Second, the problem is made worse by Kotlin's decision to not have checked exceptions (I'm not trying to discuss that, just pointing it out as a fact) - which means that any catastrophic failure scenarios like this could lurk in the code for a long time / not caught by testing / during beta releases etc. |
I love it personally. I'm always sure to not leak coroutines/resources without the need to add any boilerplate code. If something goes wrong everything will be cancelled and I can still catch errors to do something relevant like reporting and/or retry.
Not at all. The developer may choose to use a |
Hi, I've been playing around with the new structured concurrency model and I noticed some unexpected behavior in how exceptions are handled for some coroutine builders.
In this case I would expect the exception that occurred in the
rxCompletable
to be passed to rx'sonError
and be handled byonErrorComplete()
but at the moment the exception is thrown and the current scope is canceledHere I would have expected the exception thrown in the async block to be handled by the try-catch, and while that does seem to happen, the exception is still thrown after the scope completes it's execution, so I'm seeing something like:
Both of these issues are solved if I execute the builder on the
GlobalScope
or if I wrap them in asupervisorScope
but the default behavior feels a bit strange and I'm not sure if it's intendedKotlin version: 1.3.0-rc-131
kotlinx-coroutines version: 0.30.2-eap13
The text was updated successfully, but these errors were encountered: