Skip to content

Commit 134a4bc

Browse files
authored
Improve docs & code style in runInterruptibe (#1994)
* Fix code formatting in doc example and make it more concise (both vertically and horizontally). * Don't promote custom `withTimeout` logic in examples. Use the actual `withTimeout` function. * Logical introduction of `context` parameter in docs (first use without it, then explain how it helps), consistent doc references. * Improved implementation in various places: * runInterruptibleInExpectedContext does not have to be suspend * There is always Job in the context * ThreadState should not do complex init in constructor (that's bad style) * ThreadState does not need inner State class, atomic int is enough * Consistent project-wide variable naming: state -> _state * Consistent use of `error` function to throw IllegalStateException
1 parent 5ecebe1 commit 134a4bc

File tree

1 file changed

+38
-45
lines changed

1 file changed

+38
-45
lines changed

kotlinx-coroutines-core/jvm/src/Interruptible.kt

+38-45
Original file line numberDiff line numberDiff line change
@@ -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
6358
private const val INTERRUPTING = 2
6459
private 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

Comments
 (0)