|
17 | 17 | package kotlinx.coroutines.experimental
|
18 | 18 |
|
19 | 19 | import kotlinx.atomicfu.atomic
|
| 20 | +import kotlinx.coroutines.experimental.internalAnnotations.Volatile |
20 | 21 |
|
21 | 22 | /**
|
22 | 23 | * Awaits for completion of given deferred values without blocking a thread and resumes normally with the list of values
|
@@ -70,29 +71,56 @@ private class AwaitAll<T>(private val deferreds: Array<out Deferred<T>>) {
|
70 | 71 | private val notCompletedCount = atomic(deferreds.size)
|
71 | 72 |
|
72 | 73 | suspend fun await(): List<T> = suspendCancellableCoroutine { cont ->
|
73 |
| - val handlers = deferreds.map { |
74 |
| - it.start() // To properly await lazily started deferreds |
75 |
| - it.invokeOnCompletion(AwaitAllNode(cont, it).asHandler) |
| 74 | + // Intricate dance here |
| 75 | + // Step 1: Create nodes and install them as completion handlers, they may fire! |
| 76 | + val nodes = Array<AwaitAllNode>(deferreds.size) { i -> |
| 77 | + val deferred = deferreds[i] |
| 78 | + deferred.start() // To properly await lazily started deferreds |
| 79 | + AwaitAllNode(cont, deferred).apply { |
| 80 | + handle = deferred.invokeOnCompletion(asHandler) |
| 81 | + } |
| 82 | + } |
| 83 | + val disposer = DisposeHandlersOnCancel(nodes) |
| 84 | + // Step 2: Set disposer to each node |
| 85 | + nodes.forEach { it.disposer = disposer } |
| 86 | + // Here we know that if any code the nodes complete, it will dipsose the rest |
| 87 | + // Step 3: Now we can check if continuation is complete |
| 88 | + if (cont.isCompleted) { |
| 89 | + // it is already complete while handlers were being installed -- dispose them all |
| 90 | + disposer.disposeAll() |
| 91 | + } else { |
| 92 | + cont.invokeOnCancellation(handler = disposer.asHandler) |
76 | 93 | }
|
77 |
| - cont.invokeOnCancellation(handler = DisposeHandlersOnCancel(handlers).asHandler) |
78 | 94 | }
|
79 | 95 |
|
80 |
| - private class DisposeHandlersOnCancel(private val handlers: List<DisposableHandle>) : CancelHandler() { |
81 |
| - override fun invoke(cause: Throwable?) { |
82 |
| - handlers.forEach { it.dispose() } |
| 96 | + private inner class DisposeHandlersOnCancel(private val nodes: Array<AwaitAllNode>) : CancelHandler() { |
| 97 | + fun disposeAll() { |
| 98 | + nodes.forEach { it.handle.dispose() } |
83 | 99 | }
|
84 |
| - override fun toString(): String = "DisposeHandlersOnCancel[$handlers]" |
| 100 | + |
| 101 | + override fun invoke(cause: Throwable?) { disposeAll() } |
| 102 | + override fun toString(): String = "DisposeHandlersOnCancel[$nodes]" |
85 | 103 | }
|
86 | 104 |
|
87 | 105 | private inner class AwaitAllNode(private val continuation: CancellableContinuation<List<T>>, job: Job) : JobNode<Job>(job) {
|
| 106 | + lateinit var handle: DisposableHandle |
| 107 | + |
| 108 | + @Volatile |
| 109 | + var disposer: DisposeHandlersOnCancel? = null |
| 110 | + |
88 | 111 | override fun invoke(cause: Throwable?) {
|
89 | 112 | if (cause != null) {
|
90 | 113 | val token = continuation.tryResumeWithException(cause)
|
91 | 114 | if (token != null) {
|
92 | 115 | continuation.completeResume(token)
|
| 116 | + // volatile read of disposer AFTER continuation is complete |
| 117 | + val disposer = this.disposer |
| 118 | + // and if disposer was already set (all handlers where already installed, then dispose them all) |
| 119 | + if (disposer != null) disposer.disposeAll() |
93 | 120 | }
|
94 | 121 | } else if (notCompletedCount.decrementAndGet() == 0) {
|
95 | 122 | continuation.resume(deferreds.map { it.getCompleted() })
|
| 123 | + // Note, that all deferreds are complete here, so we don't need to dispose their nodes |
96 | 124 | }
|
97 | 125 | }
|
98 | 126 | }
|
|
0 commit comments