@@ -2,7 +2,6 @@ package kotlinx.coroutines.experimental
2
2
3
3
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListHead
4
4
import kotlinx.coroutines.experimental.internal.LockFreeLinkedListNode
5
- import java.util.concurrent.CancellationException
6
5
import java.util.concurrent.Future
7
6
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
8
7
import kotlin.coroutines.AbstractCoroutineContextElement
@@ -28,7 +27,7 @@ public interface Job : CoroutineContext.Element {
28
27
/* *
29
28
* Creates new job object. It is optionally a child of a [parent] job.
30
29
*/
31
- public operator fun invoke (parent : Job ? = null): Job = JobSupport (parent)
30
+ public operator fun invoke (parent : Job ? = null): Job = JobImpl (parent)
32
31
}
33
32
34
33
/* *
@@ -67,9 +66,12 @@ public interface Job : CoroutineContext.Element {
67
66
}
68
67
}
69
68
70
- typealias CompletionHandler = (Throwable ? ) -> Unit
69
+ public typealias CompletionHandler = (Throwable ? ) -> Unit
71
70
72
- typealias CancellationException = CancellationException
71
+ /* *
72
+ * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled while it is suspending.
73
+ */
74
+ public typealias CancellationException = java.util.concurrent.CancellationException
73
75
74
76
/* *
75
77
* Unregisters a specified [registration] when this job is complete.
@@ -118,30 +120,39 @@ public suspend fun Job.join() {
118
120
* state and mare store addition state information for completed jobs, like their result values.
119
121
*/
120
122
@Suppress(" LeakingThis" )
121
- public open class JobSupport (
122
- parent : Job ? = null
123
- ) : AbstractCoroutineContextElement(Job ), Job {
123
+ public open class JobSupport : AbstractCoroutineContextElement (Job ), Job {
124
124
// keeps a stack of cancel listeners or a special CANCELLED, other values denote completed scope
125
125
@Volatile
126
126
private var state: Any? = ActiveList () // will drop the list on cancel
127
127
128
- // directly pass HandlerNode to parent scope to optimize one closure object (see makeNode)
129
- private val registration: Job .Registration ? = parent?.onCompletion( CancelOnCompletion (parent, this ))
128
+ @Volatile
129
+ private var registration: Job .Registration ? = null
130
130
131
131
protected companion object {
132
132
@JvmStatic
133
133
private val STATE : AtomicReferenceFieldUpdater <JobSupport , Any ?> =
134
134
AtomicReferenceFieldUpdater .newUpdater(JobSupport ::class .java, Any ::class .java, " state" )
135
135
}
136
136
137
+ // invoke at most once after construction after all other initialization
138
+ protected fun initParentJob (parent : Job ? ) {
139
+ if (parent == null ) return
140
+ check(registration == null )
141
+ // directly pass HandlerNode to parent scope to optimize one closure object (see makeNode)
142
+ val newRegistration = parent.onCompletion(CancelOnCompletion (parent, this ))
143
+ registration = newRegistration
144
+ // now check our state _after_ registering (see updateState order of actions)
145
+ if (state !is Active ) newRegistration.unregister()
146
+ }
147
+
137
148
protected fun getState (): Any? = state
138
149
139
150
protected fun updateState (expect : Any , update : Any? ): Boolean {
140
151
expect as ActiveList // assert type
141
152
require(update !is Active ) // only active -> inactive transition is allowed
142
153
if (! STATE .compareAndSet(this , expect, update)) return false
143
154
// #1. Unregister from parent job
144
- registration?.unregister()
155
+ registration?.unregister() // volatile read registration _after_ state was updated
145
156
// #2 Invoke completion handlers
146
157
val reason = (update as ? CompletedExceptionally )?.cancelReason
147
158
var completionException: Throwable ? = null
@@ -202,21 +213,26 @@ public open class JobSupport(
202
213
private class ActiveList : LockFreeLinkedListHead (), Active
203
214
204
215
protected abstract class CompletedExceptionally {
205
- abstract val cancelReason: Throwable ?
206
- abstract val exception: Throwable
216
+ abstract val cancelReason: Throwable // original reason or fresh CancellationException
217
+ abstract val exception: Throwable // the exception to be thrown in continuation
207
218
}
208
219
209
- protected class Cancelled (override val cancelReason : Throwable ? ) : CompletedExceptionally() {
220
+ protected class Cancelled (specifiedReason : Throwable ? ) : CompletedExceptionally() {
221
+ @Volatile
222
+ private var _cancelReason = specifiedReason // materialize CancellationException on first need
223
+
224
+ override val cancelReason: Throwable get() =
225
+ _cancelReason ? : // atomic read volatile var or else create new
226
+ CancellationException ().also { _cancelReason = it }
227
+
210
228
@Volatile
211
229
private var _exception : Throwable ? = null // convert reason to CancellationException on first need
230
+
212
231
override val exception: Throwable get() =
213
- _exception ? : // atomic read volatile var or else
214
- run {
215
- val result = cancelReason as ? CancellationException ? :
216
- CancellationException ().apply { if (cancelReason != null ) initCause(cancelReason) }
217
- _exception = result
218
- result
219
- }
232
+ _exception ? : // atomic read volatile var or else build new
233
+ (cancelReason as ? CancellationException ? :
234
+ CancellationException (cancelReason.message).apply { initCause(cancelReason) })
235
+ .also { _exception = it }
220
236
}
221
237
222
238
protected class Failed (override val exception : Throwable ) : CompletedExceptionally() {
@@ -288,3 +304,7 @@ private class RemoveOnCompletion(
288
304
override fun invoke (reason : Throwable ? ) { node.remove() }
289
305
override fun toString () = " RemoveOnCompletion[$node ]"
290
306
}
307
+
308
+ private class JobImpl (parent : Job ? = null ) : JobSupport() {
309
+ init { initParentJob(parent) }
310
+ }
0 commit comments