|
7 | 7 | package kotlinx.coroutines.android
|
8 | 8 |
|
9 | 9 | import android.os.*
|
10 |
| -import androidx.annotation.* |
11 | 10 | import android.view.*
|
| 11 | +import androidx.annotation.* |
12 | 12 | import kotlinx.coroutines.*
|
13 | 13 | import kotlinx.coroutines.internal.*
|
14 | 14 | import java.lang.reflect.*
|
@@ -54,15 +54,22 @@ internal class AndroidDispatcherFactory : MainDispatcherFactory {
|
54 | 54 | override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
|
55 | 55 | HandlerContext(Looper.getMainLooper().asHandler(async = true))
|
56 | 56 |
|
57 |
| - override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used" |
| 57 | + override fun hintOnError(): String = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used" |
58 | 58 |
|
59 | 59 | override val loadPriority: Int
|
60 | 60 | get() = Int.MAX_VALUE / 2
|
61 | 61 | }
|
62 | 62 |
|
63 | 63 | /**
|
64 |
| - * Represents an arbitrary [Handler] as a implementation of [CoroutineDispatcher] |
| 64 | + * Represents an arbitrary [Handler] as an implementation of [CoroutineDispatcher] |
65 | 65 | * with an optional [name] for nicer debugging
|
| 66 | + * |
| 67 | + * ## Rejected execution |
| 68 | + * |
| 69 | + * If the underlying handler is closed and its post-related methods start to return `false` on |
| 70 | + * an attempt to submit a continuation task to the resulting dispatcher, |
| 71 | + * then the [Job] of the affected task is [cancelled][Job.cancel] and the task is submitted to the |
| 72 | + * [Dispatchers.IO], so that the affected coroutine can cleanup its resources and promptly complete. |
66 | 73 | */
|
67 | 74 | @JvmName("from") // this is for a nice Java API, see issue #255
|
68 | 75 | @JvmOverloads
|
@@ -129,24 +136,33 @@ internal class HandlerContext private constructor(
|
129 | 136 | }
|
130 | 137 |
|
131 | 138 | override fun dispatch(context: CoroutineContext, block: Runnable) {
|
132 |
| - handler.post(block) |
| 139 | + if (!handler.post(block)) { |
| 140 | + cancelOnRejection(context, block) |
| 141 | + } |
133 | 142 | }
|
134 | 143 |
|
135 | 144 | override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
|
136 | 145 | val block = Runnable {
|
137 | 146 | with(continuation) { resumeUndispatched(Unit) }
|
138 | 147 | }
|
139 |
| - handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY)) |
140 |
| - continuation.invokeOnCancellation { handler.removeCallbacks(block) } |
| 148 | + if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) { |
| 149 | + continuation.invokeOnCancellation { handler.removeCallbacks(block) } |
| 150 | + } else { |
| 151 | + cancelOnRejection(continuation.context, block) |
| 152 | + } |
141 | 153 | }
|
142 | 154 |
|
143 | 155 | override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
|
144 |
| - handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY)) |
145 |
| - return object : DisposableHandle { |
146 |
| - override fun dispose() { |
147 |
| - handler.removeCallbacks(block) |
148 |
| - } |
| 156 | + if (handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))) { |
| 157 | + return DisposableHandle { handler.removeCallbacks(block) } |
149 | 158 | }
|
| 159 | + cancelOnRejection(context, block) |
| 160 | + return NonDisposableHandle |
| 161 | + } |
| 162 | + |
| 163 | + private fun cancelOnRejection(context: CoroutineContext, block: Runnable) { |
| 164 | + context.cancel(CancellationException("The task was rejected")) |
| 165 | + Dispatchers.IO.dispatch(context, block) |
150 | 166 | }
|
151 | 167 |
|
152 | 168 | override fun toString(): String = toStringInternalImpl() ?: run {
|
|
0 commit comments