Skip to content

Commit 25b4388

Browse files
authored
Merge pull request #588 from Kotlin/handle-exception-fix
Handle exceptions fix
2 parents a180ff7 + 541a9b6 commit 25b4388

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1409
-884
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
public abstract class kotlinx/coroutines/experimental/AbstractCoroutine : kotlin/coroutines/experimental/Continuation, kotlinx/coroutines/experimental/CoroutineScope, kotlinx/coroutines/experimental/Job {
2+
protected final field parentContext Lkotlin/coroutines/experimental/CoroutineContext;
23
public fun <init> (Lkotlin/coroutines/experimental/CoroutineContext;Z)V
34
public synthetic fun <init> (Lkotlin/coroutines/experimental/CoroutineContext;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
45
public final fun getContext ()Lkotlin/coroutines/experimental/CoroutineContext;
@@ -392,7 +393,7 @@ public abstract interface annotation class kotlinx/coroutines/experimental/Inter
392393

393394
public abstract interface class kotlinx/coroutines/experimental/Job : kotlin/coroutines/experimental/CoroutineContext$Element {
394395
public static final field Key Lkotlinx/coroutines/experimental/Job$Key;
395-
public abstract fun attachChild (Lkotlinx/coroutines/experimental/Job;)Lkotlinx/coroutines/experimental/DisposableHandle;
396+
public abstract fun attachChild (Lkotlinx/coroutines/experimental/ChildJob;)Lkotlinx/coroutines/experimental/ChildHandle;
396397
public abstract fun cancel ()Z
397398
public abstract fun cancel (Ljava/lang/Throwable;)Z
398399
public abstract synthetic fun cancelChildren (Ljava/lang/Throwable;)V
@@ -467,7 +468,7 @@ public final class kotlinx/coroutines/experimental/LazyDeferredKt {
467468

468469
public final class kotlinx/coroutines/experimental/NonCancellable : kotlin/coroutines/experimental/AbstractCoroutineContextElement, kotlinx/coroutines/experimental/Job {
469470
public static final field INSTANCE Lkotlinx/coroutines/experimental/NonCancellable;
470-
public fun attachChild (Lkotlinx/coroutines/experimental/Job;)Lkotlinx/coroutines/experimental/DisposableHandle;
471+
public fun attachChild (Lkotlinx/coroutines/experimental/ChildJob;)Lkotlinx/coroutines/experimental/ChildHandle;
471472
public fun cancel ()Z
472473
public fun cancel (Ljava/lang/Throwable;)Z
473474
public synthetic fun cancelChildren (Ljava/lang/Throwable;)V
@@ -487,8 +488,9 @@ public final class kotlinx/coroutines/experimental/NonCancellable : kotlin/corou
487488
public fun start ()Z
488489
}
489490

490-
public final class kotlinx/coroutines/experimental/NonDisposableHandle : kotlinx/coroutines/experimental/DisposableHandle {
491+
public final class kotlinx/coroutines/experimental/NonDisposableHandle : kotlinx/coroutines/experimental/ChildHandle, kotlinx/coroutines/experimental/DisposableHandle {
491492
public static final field INSTANCE Lkotlinx/coroutines/experimental/NonDisposableHandle;
493+
public fun childCancelled (Ljava/lang/Throwable;)Z
492494
public fun dispose ()V
493495
public fun toString ()Ljava/lang/String;
494496
}

common/kotlinx-coroutines-core-common/src/AbstractCoroutine.kt

+20-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package kotlinx.coroutines.experimental
66

77
import kotlinx.coroutines.experimental.CoroutineStart.*
8+
import kotlinx.coroutines.experimental.internal.*
89
import kotlinx.coroutines.experimental.intrinsics.*
910
import kotlin.coroutines.experimental.*
1011

@@ -14,12 +15,12 @@ import kotlin.coroutines.experimental.*
1415
* This class implements completion [Continuation], [Job], and [CoroutineScope] interfaces.
1516
* It stores the result of continuation in the state of the job.
1617
* This coroutine waits for children coroutines to finish before completing and
17-
* is cancelled through an intermediate _cancelling_ state.
18+
* fails through an intermediate _failing_ state.
1819
*
1920
* The following methods are available for override:
2021
*
2122
* * [onStart] is invoked when coroutine is create in not active state and is [started][Job.start].
22-
* * [onCancellation] is invoked as soon as coroutine is [cancelled][cancel] (becomes _cancelling_)
23+
* * [onCancellation] is invoked as soon as coroutine is _failing_, or is cancelled,
2324
* or when it completes for any reason.
2425
* * [onCompleted] is invoked when coroutine completes with a value.
2526
* * [onCompletedExceptionally] in invoked when coroutines completes with exception.
@@ -33,12 +34,22 @@ import kotlin.coroutines.experimental.*
3334
@Suppress("EXPOSED_SUPER_CLASS")
3435
@InternalCoroutinesApi
3536
public abstract class AbstractCoroutine<in T>(
36-
private val parentContext: CoroutineContext,
37+
/**
38+
* Context of the parent coroutine.
39+
*/
40+
@JvmField
41+
protected val parentContext: CoroutineContext,
3742
active: Boolean = true
3843
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
44+
/**
45+
* Context of this coroutine that includes this coroutine as a [Job].
46+
*/
3947
@Suppress("LeakingThis")
4048
public final override val context: CoroutineContext = parentContext + this
41-
@Deprecated("Replaced with context", replaceWith = ReplaceWith("context"))
49+
50+
/**
51+
* Context of this scope which is the same as the [context] of this coroutine.
52+
*/
4253
public override val coroutineContext: CoroutineContext get() = context
4354

4455
override val isActive: Boolean get() = super<JobSupport>.isActive
@@ -66,20 +77,16 @@ public abstract class AbstractCoroutine<in T>(
6677
}
6778

6879
/**
69-
* This function is invoked once when this coroutine is cancelled or is completed,
80+
* This function is invoked once when this coroutine is cancelled
7081
* similarly to [invokeOnCompletion] with `onCancelling` set to `true`.
7182
*
7283
* The meaning of [cause] parameter:
7384
* * Cause is `null` when job has completed normally.
7485
* * Cause is an instance of [CancellationException] when job was cancelled _normally_.
7586
* **It should not be treated as an error**. In particular, it should not be reported to error logs.
76-
* * Otherwise, the job had _failed_.
87+
* * Otherwise, the job had been cancelled or failed with exception.
7788
*/
78-
protected open fun onCancellation(cause: Throwable?) {}
79-
80-
internal override fun onCancellationInternal(exceptionally: CompletedExceptionally?) {
81-
onCancellation(exceptionally?.cause)
82-
}
89+
protected override fun onCancellation(cause: Throwable?) {}
8390

8491
/**
8592
* This function is invoked once when job is completed normally with the specified [value].
@@ -89,10 +96,11 @@ public abstract class AbstractCoroutine<in T>(
8996
/**
9097
* This function is invoked once when job is completed exceptionally with the specified [exception].
9198
*/
99+
// todo: rename to onCancelled
92100
protected open fun onCompletedExceptionally(exception: Throwable) {}
93101

94102
@Suppress("UNCHECKED_CAST")
95-
internal override fun onCompletionInternal(state: Any?, mode: Int) {
103+
internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) {
96104
if (state is CompletedExceptionally)
97105
onCompletedExceptionally(state.cause)
98106
else

common/kotlinx-coroutines-core-common/src/Builders.common.kt

+3-12
Original file line numberDiff line numberDiff line change
@@ -281,20 +281,11 @@ public suspend fun <T> run(context: CoroutineContext, block: suspend () -> T): T
281281
// --------------- implementation ---------------
282282

283283
private open class StandaloneCoroutine(
284-
private val parentContext: CoroutineContext,
284+
parentContext: CoroutineContext,
285285
active: Boolean
286286
) : AbstractCoroutine<Unit>(parentContext, active) {
287-
override fun hasOnFinishingHandler(update: Any?) = update is CompletedExceptionally
288-
289-
override fun handleJobException(exception: Throwable) {
290-
handleCoroutineException(parentContext, exception, this)
291-
}
292-
293-
override fun onFinishingInternal(update: Any?) {
294-
if (update is CompletedExceptionally && update.cause !is CancellationException) {
295-
parentContext[Job]?.cancel(update.cause)
296-
}
297-
}
287+
override val cancelsParent: Boolean get() = true
288+
override fun handleJobException(exception: Throwable) = handleExceptionViaHandler(parentContext, exception)
298289
}
299290

300291
private class LazyStandaloneCoroutine(

common/kotlinx-coroutines-core-common/src/CompletableDeferred.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ package kotlinx.coroutines.experimental
77
import kotlinx.coroutines.experimental.selects.*
88

99
/**
10-
* A [Deferred] that can be completed via public functions
11-
* [complete], [completeExceptionally], and [cancel].
10+
* A [Deferred] that can be completed via public functions [complete] or [cancel][Job.cancel].
1211
*
13-
* Completion functions return `false` when this deferred value is already complete or completing.
12+
* Note, that [complete] functions returns `false` when this deferred value is already complete or completing,
13+
* while [cancel][Job.cancel] returns `true` as long the deferred is still _cancelling_ and the corresponding
14+
* exception is incorporated into the final [completion exception][getCompletionExceptionOrNull].
1415
*
1516
* An instance of completable deferred can be created by `CompletableDeferred()` function in _active_ state.
1617
*
@@ -32,6 +33,7 @@ public interface CompletableDeferred<T> : Deferred<T> {
3233
*
3334
* Repeated invocations of this function have no effect and always produce `false`.
3435
*/
36+
@Deprecated(message = "Use cancel", replaceWith = ReplaceWith("cancel(exception)"))
3537
public fun completeExceptionally(exception: Throwable): Boolean
3638
}
3739

@@ -61,7 +63,7 @@ private class CompletableDeferredImpl<T>(
6163
parent: Job?
6264
) : JobSupport(true), CompletableDeferred<T>, SelectClause1<T> {
6365
init { initParentJobInternal(parent) }
64-
override val onCancelMode: Int get() = ON_CANCEL_MAKE_COMPLETING
66+
override val onCancelComplete get() = true
6567
override fun getCompleted(): T = getCompletedInternal() as T
6668
override suspend fun await(): T = awaitInternal() as T
6769
override val onAwait: SelectClause1<T> get() = this

common/kotlinx-coroutines-core-common/src/CompletedExceptionally.kt

+3-16
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,22 @@ import kotlinx.coroutines.experimental.internal.*
88
import kotlin.coroutines.experimental.*
99

1010
/**
11-
* Class for an internal state of a job that had completed exceptionally, including cancellation.
11+
* Class for an internal state of a job that was cancelled (completed exceptionally).
1212
*
1313
* **Note: This class cannot be used outside of internal coroutines framework**.
14-
* **Note: cannot be internal until we get rid of MutableDelegateContinuation in IO**
14+
* **Note: cannot be internal and renamed until we get rid of MutableDelegateContinuation in IO**
1515
*
1616
* @param cause the exceptional completion cause. It's either original exceptional cause
1717
* or artificial [CancellationException] if no cause was provided
1818
* @suppress **This is unstable API and it is subject to change.**
1919
*/
20+
// todo: rename to Cancelled
2021
open class CompletedExceptionally(
2122
@JvmField public val cause: Throwable
2223
) {
2324
override fun toString(): String = "$classSimpleName[$cause]"
2425
}
2526

26-
/**
27-
* A specific subclass of [CompletedExceptionally] for cancelled jobs.
28-
*
29-
* **Note: This class cannot be used outside of internal coroutines framework**.
30-
*
31-
* @param job the job that was cancelled.
32-
* @param cause the exceptional completion cause. If `cause` is null, then a [CancellationException] is created.
33-
* @suppress **This is unstable API and it is subject to change.**
34-
*/
35-
internal class Cancelled(
36-
job: Job,
37-
cause: Throwable?
38-
) : CompletedExceptionally(cause ?: JobCancellationException("Job was cancelled normally", null, job))
39-
4027
/**
4128
* A specific subclass of [CompletedExceptionally] for cancelled [AbstractContinuation].
4229
*

common/kotlinx-coroutines-core-common/src/CompletionHandler.common.kt

+2
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@ internal expect val CancelHandlerBase.asHandler: CompletionHandler
4343
// :KLUDGE: We have to invoke a handler in platform-specific way via `invokeIt` extension,
4444
// because we play type tricks on Kotlin/JS and handler is not necessarily a function there
4545
internal expect fun CompletionHandler.invokeIt(cause: Throwable?)
46+
47+
internal inline fun <reified T> CompletionHandler.isHandlerOf(): Boolean = this is T

common/kotlinx-coroutines-core-common/src/CoroutineExceptionHandler.kt

+26-23
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,41 @@ internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exce
1818
*
1919
* If there is a [Job] in the context and it's not a [caller], then [Job.cancel] is invoked.
2020
* If invocation returned `true`, method terminates: now [Job] is responsible for handling an exception.
21-
* Otherwise, If there is [CoroutineExceptionHandler] in the context, it is used.
22-
* Otherwise all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and [Thread.uncaughtExceptionHandler] are invoked
21+
* Otherwise, If there is [CoroutineExceptionHandler] in the context, it is used. If it throws an exception during handling
22+
* or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and [Thread.uncaughtExceptionHandler] are invoked
2323
*/
2424
@JvmOverloads // binary compatibility
2525
@InternalCoroutinesApi
2626
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable, caller: Job? = null) {
27-
// if exception handling fails, make sure the original exception is not lost
27+
// Ignore CancellationException (they are normal ways to terminate a coroutine)
28+
if (exception is CancellationException) return // nothing to do
29+
// Try propagate exception to parent
30+
val job = context[Job]
31+
if (job !== null && job !== caller && job.cancel(exception)) return // handle by parent
32+
// otherwise -- use exception handlers
33+
handleExceptionViaHandler(context, exception)
34+
}
35+
36+
internal fun handleExceptionViaHandler(context: CoroutineContext, exception: Throwable) {
37+
// Invoke exception handler from the context if present
2838
try {
29-
// Ignore CancellationException (they are normal ways to terminate a coroutine)
30-
if (exception is CancellationException) {
31-
return
32-
}
33-
// If parent is successfully cancelled, we're done, it is now its responsibility to handle the exception
34-
val parent = context[Job]
35-
// E.g. actor registers itself in the context, in that case we should invoke handler
36-
if (parent !== null && parent !== caller && parent.cancel(exception)) {
37-
return
38-
}
39-
// If not, invoke exception handler from the context
4039
context[CoroutineExceptionHandler]?.let {
4140
it.handleException(context, exception)
4241
return
4342
}
44-
// If handler is not present in the context, fallback to the global handler
45-
handleCoroutineExceptionImpl(context, exception)
46-
} catch (handlerException: Throwable) {
47-
// simply rethrow if handler threw the original exception
48-
if (handlerException === exception) throw exception
49-
// handler itself crashed for some other reason -- that is bad -- keep both
50-
throw RuntimeException("Exception while trying to handle coroutine exception", exception).apply {
51-
addSuppressedThrowable(handlerException)
52-
}
43+
} catch (t: Throwable) {
44+
handleCoroutineExceptionImpl(context, handlerException(exception, t))
45+
return
46+
}
47+
48+
// If handler is not present in the context or exception was thrown, fallback to the global handler
49+
handleCoroutineExceptionImpl(context, exception)
50+
}
51+
52+
internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable {
53+
if (originalException === thrownException) return originalException
54+
return RuntimeException("Exception while trying to handle coroutine exception", thrownException).apply {
55+
addSuppressedThrowable(originalException)
5356
}
5457
}
5558

common/kotlinx-coroutines-core-common/src/CoroutineScope.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public interface CoroutineScope {
7171
get() = coroutineContext[Job]?.isActive ?: true
7272

7373
/**
74-
* Returns the context of this scope.
74+
* Context of this scope.
7575
*/
7676
public val coroutineContext: CoroutineContext
7777
}
@@ -175,7 +175,7 @@ object GlobalScope : CoroutineScope {
175175
*/
176176
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
177177
// todo: optimize implementation to a single allocated object
178-
val owner = ScopeOwnerCoroutine<R>(coroutineContext)
178+
val owner = ScopeCoroutine<R>(coroutineContext)
179179
owner.start(CoroutineStart.UNDISPATCHED, owner, block)
180180
owner.join()
181181
if (owner.isCancelled) {

0 commit comments

Comments
 (0)