@@ -11,27 +11,22 @@ import kotlin.coroutines.*
1111 * Calls the specified [block] with a given coroutine context in a interruptible manner.
1212 * The blocking code block will be interrupted and this function will throw [CancellationException]
1313 * if the coroutine is cancelled.
14- * The specified [coroutineContext] directly translates into [withContext] argument.
1514 *
1615 * Example:
16+ *
1717 * ```
18- * val blockingJob = launch {
19- * // This function will throw CancellationException
20- * runInterruptible(Dispatchers.IO) {
21- * // This blocking procedure will be interrupted when this coroutine is canceled
22- * doSomethingElseUsefulInterruptible()
18+ * withTimeout(500L) { // Cancels coroutine on timeout
19+ * runInterruptible { // Throws CancellationException if interrupted
20+ * doSomethingBlocking() // Interrupted on coroutines cancellation
2321 * }
2422 * }
25- *
26- * delay(500L)
27- * blockingJob.cancel() // Interrupt blocking call
28- * }
2923 * ```
3024 *
31- * There is also an optional context parameter to this function to enable single-call conversion of
32- * interruptible Java methods into suspending functions like this:
25+ * There is an optional [context] parameter to this function working just like [withContext].
26+ * It enables single-call conversion of interruptible Java methods into suspending functions.
27+ * With one call here we are moving the call to [Dispatchers.IO] and supporting interruption:
28+ *
3329 * ```
34- * // With one call here we are moving the call to Dispatchers.IO and supporting interruption.
3530 * suspend fun <T> BlockingQueue<T>.awaitTake(): T =
3631 * runInterruptible(Dispatchers.IO) { queue.take() }
3732 * ```
@@ -40,14 +35,14 @@ public suspend fun <T> runInterruptible(
4035 context : CoroutineContext = EmptyCoroutineContext ,
4136 block : () -> T
4237): T = withContext(context) {
43- runInterruptibleInExpectedContext(block)
38+ runInterruptibleInExpectedContext(coroutineContext, block)
4439}
4540
46- private suspend fun <T > runInterruptibleInExpectedContext (block : () -> T ): T {
41+ private fun <T > runInterruptibleInExpectedContext (coroutineContext : CoroutineContext , block : () -> T ): T {
4742 try {
48- // No job -> no cancellation
49- val job = coroutineContext[Job ] ? : return block()
43+ val job = coroutineContext[Job ]!! // withContext always creates a job
5044 val threadState = ThreadState (job)
45+ threadState.setup()
5146 try {
5247 return block()
5348 } finally {
@@ -63,7 +58,7 @@ private const val FINISHED = 1
6358private const val INTERRUPTING = 2
6459private const val INTERRUPTED = 3
6560
66- private class ThreadState : CompletionHandler {
61+ private class ThreadState ( private val job : Job ) : CompletionHandler {
6762 /*
6863 === States ===
6964
@@ -90,28 +85,25 @@ private class ThreadState : CompletionHandler {
9085 | |
9186 V V
9287 +---------------+ +-------------------------+
93- | INTERRUPTED | | FINISHED |
88+ | INTERRUPTED | | FINISHED |
9489 +---------------+ +-------------------------+
9590 */
96- private val state : AtomicRef < State > = atomic(State ( WORKING , null ) )
91+ private val _state = atomic(WORKING )
9792 private val targetThread = Thread .currentThread()
9893
99- private data class State (@JvmField val state : Int , @JvmField val cancelHandle : DisposableHandle ? )
94+ // Registered cancellation handler
95+ private var cancelHandle: DisposableHandle ? = null
10096
101- // We're using a non-primary constructor instead of init block of a primary constructor here, because
102- // we need to `return`.
103- constructor (job: Job ) {
104- // Register cancellation handler
105- val cancelHandle =
106- job.invokeOnCompletion(onCancelling = true , invokeImmediately = true , handler = this )
97+ fun setup () {
98+ cancelHandle = job.invokeOnCompletion(onCancelling = true , invokeImmediately = true , handler = this )
10799 // Either we successfully stored it or it was immediately cancelled
108- state .loop { s ->
109- when (s. state) {
100+ _state .loop { state ->
101+ when (state) {
110102 // Happy-path, move forward
111- WORKING -> if (state .compareAndSet(s, State ( WORKING , cancelHandle) )) return
103+ WORKING -> if (_state .compareAndSet(state, WORKING )) return
112104 // Immediately cancelled, just continue
113105 INTERRUPTING , INTERRUPTED -> return
114- else -> throw IllegalStateException ( " Illegal state $s " )
106+ else -> invalidState( state)
115107 }
116108 }
117109 }
@@ -120,10 +112,10 @@ private class ThreadState : CompletionHandler {
120112 /*
121113 * Do not allow to untriggered interrupt to leak
122114 */
123- state .loop { s ->
124- when (s. state) {
125- WORKING -> if (state .compareAndSet(s, State ( FINISHED , null ) )) {
126- s. cancelHandle?.dispose()
115+ _state .loop { state ->
116+ when (state) {
117+ WORKING -> if (_state .compareAndSet(state, FINISHED )) {
118+ cancelHandle?.dispose()
127119 return
128120 }
129121 INTERRUPTING -> {
@@ -134,31 +126,32 @@ private class ThreadState : CompletionHandler {
134126 }
135127 INTERRUPTED -> {
136128 // Clear it and bail out
137- Thread .interrupted();
129+ Thread .interrupted()
138130 return
139131 }
140- else -> error( " Illegal state: $s " )
132+ else -> invalidState( state)
141133 }
142134 }
143135 }
144136
145137 // Cancellation handler
146138 override fun invoke (cause : Throwable ? ) {
147- state .loop { s ->
148- when (s. state) {
139+ _state .loop { state ->
140+ when (state) {
149141 // Working -> try to transite state and interrupt the thread
150142 WORKING -> {
151- if (state .compareAndSet(s, State ( INTERRUPTING , null ) )) {
143+ if (_state .compareAndSet(state, INTERRUPTING )) {
152144 targetThread.interrupt()
153- state .value = State ( INTERRUPTED , null )
145+ _state .value = INTERRUPTED
154146 return
155147 }
156148 }
157- // Finished -- runInterruptible is already complete
158- FINISHED -> return
159- INTERRUPTING , INTERRUPTED -> return
160- else -> error(" Illegal state: $s " )
149+ // Finished -- runInterruptible is already complete, INTERRUPTING - ignore
150+ FINISHED , INTERRUPTING , INTERRUPTED -> return
151+ else -> invalidState(state)
161152 }
162153 }
163154 }
155+
156+ private fun invalidState (state : Int ): Nothing = error(" Illegal state $state " )
164157}
0 commit comments