Skip to content

Commit 01934df

Browse files
committed
NonCancellable context
1 parent f4d7a23 commit 01934df

File tree

3 files changed

+133
-49
lines changed

3 files changed

+133
-49
lines changed

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Job.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ public interface Job : CoroutineContext.Element {
5858

5959
/**
6060
* Cancel this activity with an optional cancellation [reason]. The result is `true` if this job was
61-
* cancelled as a result of this invocation and `false` otherwise (if it was already cancelled).
61+
* cancelled as a result of this invocation and `false` otherwise
62+
* (if it was already cancelled or it is [NonCancellable]).
63+
* Repeated invocation of this function has no effect and always produces `false`.
64+
*
6265
* When cancellation has a clear reason in the code, an instance of [CancellationException] should be created
6366
* at the corresponding original cancellation site and passed into this method to aid in debugging by providing
6467
* both the context of cancellation and text description of the reason.
@@ -183,8 +186,11 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
183186
* It shall be invoked at most once after construction after all other initialization.
184187
*/
185188
fun initParentJob(parent: Job?) {
186-
if (parent == null) return
187189
check(registration == null)
190+
if (parent == null) {
191+
registration = EmptyRegistration
192+
return
193+
}
188194
// directly pass HandlerNode to parent scope to optimize one closure object (see makeNode)
189195
val newRegistration = parent.onCompletion(CancelOnCompletion(parent, this))
190196
registration = newRegistration
@@ -260,7 +266,7 @@ internal open class JobSupport : AbstractCoroutineContextElement(Job), Job {
260266
// SINGLE/SINGLE+ state -- one completion handler
261267
state is JobNode -> {
262268
// try promote it to the list (SINGLE+ state)
263-
state.addIfEmpty(NodeList())
269+
state.addFirstIfEmpty(NodeList())
264270
// it must be in SINGLE+ state or state has changed (node could have need removed from state)
265271
val list = state.next() // either NodeList or somebody else won the race, updated state
266272
// just attempt converting it to list if state is still the same, then continue lock-free loop
@@ -424,7 +430,7 @@ private class CancelOnCompletion(
424430
override fun toString(): String = "CancelOnCompletion[$subordinateJob]"
425431
}
426432

427-
private object EmptyRegistration : Job.Registration {
433+
internal object EmptyRegistration : Job.Registration {
428434
override fun unregister() {}
429435
override fun toString(): String = "EmptyRegistration"
430436
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package kotlinx.coroutines.experimental
2+
3+
import kotlinx.coroutines.experimental.NonCancellable.isActive
4+
import kotlin.coroutines.AbstractCoroutineContextElement
5+
6+
/**
7+
* A non-cancelable job that is always [active][isActive]. It is designed to be used with [run] builder
8+
* to prevent cancellation of code blocks that need to run without cancellation, like this
9+
* ```
10+
* run(NonCancellable) {
11+
* // this code will not be cancelled
12+
* }
13+
* ```
14+
*/
15+
object NonCancellable : AbstractCoroutineContextElement(Job), Job {
16+
override val isActive: Boolean get() = true
17+
override fun getInactiveCancellationException(): CancellationException = throw IllegalStateException("This job is always active")
18+
override fun onCompletion(handler: CompletionHandler): Job.Registration = EmptyRegistration
19+
override fun cancel(reason: Throwable?): Boolean = false
20+
}

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/internal/LockFreeLinkedList.kt

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,15 @@ internal open class LockFreeLinkedListNode {
9393

9494
fun next(): Node = next.unwrap()
9595

96-
fun addFirstCC(node: Node, condAdd: CondAdd?): Boolean {
96+
fun prev(): Node {
97+
while (true) {
98+
prevHelper()?.let { return it.unwrap() }
99+
}
100+
}
101+
102+
// ------ addFirstXXX ------
103+
104+
private fun addFirstCC(node: Node, condAdd: CondAdd?): Boolean {
97105
require(node.isFresh)
98106
condAdd?.newNode = node
99107
while (true) { // lock-free loop on next
@@ -108,7 +116,20 @@ internal open class LockFreeLinkedListNode {
108116
}
109117
}
110118

111-
fun addIfEmpty(node: Node): Boolean {
119+
/**
120+
* Adds first item to this list.
121+
*/
122+
fun addFirst(node: Node) { addFirstCC(node, null) }
123+
124+
/**
125+
* Adds first item to this list atomically if the [condition] is true.
126+
*/
127+
inline fun addFirstIf(node: Node, crossinline condition: () -> Boolean): Boolean =
128+
addFirstCC(node, object : CondAdd() {
129+
override fun isCondition(): Boolean = condition()
130+
})
131+
132+
fun addFirstIfEmpty(node: Node): Boolean {
112133
require(node.isFresh)
113134
PREV.lazySet(node, this)
114135
NEXT.lazySet(node, this)
@@ -118,15 +139,13 @@ internal open class LockFreeLinkedListNode {
118139
return true
119140
}
120141

121-
fun addLastCC(node: Node, condAdd: CondAdd?): Boolean {
142+
// ------ addLastXXX ------
143+
144+
private fun addLastCC(node: Node, condAdd: CondAdd?): Boolean {
122145
require(node.isFresh)
123146
condAdd?.newNode = node
124147
while (true) { // lock-free loop on prev.next
125-
val prev = this.prev as Node // this sentinel node is never removed
126-
if (prev.next !== this) {
127-
helpInsert(prev)
128-
continue
129-
}
148+
val prev = prevHelper() ?: continue
130149
PREV.lazySet(node, prev)
131150
NEXT.lazySet(node, this)
132151
condAdd?.oldNext = this
@@ -137,20 +156,48 @@ internal open class LockFreeLinkedListNode {
137156
}
138157
}
139158

140-
private fun finishAdd(next: Node) {
141-
while (true) {
142-
val nextPrev = next.prev
143-
if (nextPrev is Removed || this.next !== next) return // next was removed, remover fixes up links
144-
if (PREV.compareAndSet(next, nextPrev, this)) {
145-
if (this.next is Removed) {
146-
// already removed
147-
next.helpInsert(nextPrev as Node)
148-
}
149-
return
150-
}
159+
/**
160+
* Adds last item to this list.
161+
*/
162+
fun addLast(node: Node) { addLastCC(node, null) }
163+
164+
/**
165+
* Adds last item to this list atomically if the [condition] is true.
166+
*/
167+
inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean =
168+
addLastCC(node, object : CondAdd() {
169+
override fun isCondition(): Boolean = condition()
170+
})
171+
172+
inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean {
173+
require(node.isFresh)
174+
while (true) { // lock-free loop on prev.next
175+
val prev = prevHelper() ?: continue
176+
if (!predicate(prev)) return false
177+
if (addAfterPrev(node, prev)) return true
151178
}
152179
}
153180

181+
private fun prevHelper(): Node? {
182+
val prev = this.prev as Node // this sentinel node is never removed
183+
if (prev.next === this) return prev
184+
helpInsert(prev)
185+
return null
186+
}
187+
188+
private fun addAfterPrev(node: Node, prev: Node): Boolean {
189+
PREV.lazySet(node, prev)
190+
NEXT.lazySet(node, this)
191+
if (NEXT.compareAndSet(prev, this, node)) {
192+
// added successfully (linearized add) -- fixup the list
193+
node.finishAdd(this)
194+
return true
195+
}
196+
return false
197+
}
198+
199+
// ------ removeXXX ------
200+
154201
/**
155202
* Removes this node from the list. Returns `true` when removed successfully.
156203
*/
@@ -175,6 +222,42 @@ internal open class LockFreeLinkedListNode {
175222
}
176223
}
177224

225+
inline fun <reified T> removeFirstIfIsInstanceOf(): T? {
226+
while (true) { // try to linearize
227+
val first = next()
228+
if (first == this) return null
229+
if (first !is T) return null
230+
if (first.remove()) return first
231+
}
232+
}
233+
234+
// just peek at item when predicate is true
235+
inline fun <reified T> removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? {
236+
while (true) { // try to linearize
237+
val first = next()
238+
if (first == this) return null
239+
if (first !is T) return null
240+
if (predicate(first)) return first // just peek when predicate is true
241+
if (first.remove()) return first
242+
}
243+
}
244+
245+
// ------ other helpers ------
246+
247+
private fun finishAdd(next: Node) {
248+
while (true) {
249+
val nextPrev = next.prev
250+
if (nextPrev is Removed || this.next !== next) return // next was removed, remover fixes up links
251+
if (PREV.compareAndSet(next, nextPrev, this)) {
252+
if (this.next is Removed) {
253+
// already removed
254+
next.helpInsert(nextPrev as Node)
255+
}
256+
return
257+
}
258+
}
259+
}
260+
178261
private fun markPrev(): Node {
179262
while (true) { // lock-free loop on prev
180263
val prev = this.prev
@@ -276,32 +359,7 @@ internal open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
276359
}
277360
}
278361

279-
/**
280-
* Adds first item to this list.
281-
*/
282-
fun addFirst(node: Node) { addFirstCC(node, null) }
283-
284-
/**
285-
* Adds first item to this list atomically if the [condition] is true.
286-
*/
287-
inline fun addFirstIf(node: Node, crossinline condition: () -> Boolean): Boolean =
288-
addFirstCC(node, object : CondAdd() {
289-
override fun isCondition(): Boolean = condition()
290-
})
291-
292-
/**
293-
* Adds last item to this list.
294-
*/
295-
fun addLast(node: Node) { addLastCC(node, null) }
296-
297-
/**
298-
* Adds last item to this list atomically if the [condition] is true.
299-
*/
300-
inline fun addLastIf(node: Node, crossinline condition: () -> Boolean): Boolean =
301-
addLastCC(node, object : CondAdd() {
302-
override fun isCondition(): Boolean = condition()
303-
})
304-
362+
// just a defensive programming -- makes sure that list head sentinel is never removed
305363
final override fun remove() = throw UnsupportedOperationException()
306364

307365
fun validate() {

0 commit comments

Comments
 (0)