blob: 3a7b6e21643a5f38f85078f272ece4302324b184 [file] [log] [blame]
Roman Elizarova5676592017-01-19 10:31:13 +03001package kotlinx.coroutines.experimental.internal
Roman Elizarov3754f952017-01-18 20:47:54 +03002
3import java.util.concurrent.atomic.AtomicIntegerFieldUpdater
4import java.util.concurrent.atomic.AtomicReferenceFieldUpdater
5
6private typealias Node = LockFreeLinkedListNode
7
8/**
9 * Doubly-linked concurrent list node with remove support.
10 * Based on paper
11 * ["Lock-Free and Practical Doubly Linked List-Based Deques Using Single-Word Compare-and-Swap"](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.4693&rep=rep1&type=pdf)
12 * by Sundell and Tsigas.
13 * The instance of this class serves both as list head/tail sentinel and as the list item.
14 * Sentinel node should be never removed.
15 */
16@Suppress("LeakingThis")
Roman Elizarova5676592017-01-19 10:31:13 +030017internal open class LockFreeLinkedListNode {
Roman Elizarov3754f952017-01-18 20:47:54 +030018 @Volatile
19 private var _next: Any = this // DoubleLinkedNode | Removed | CondAdd
20 @Volatile
21 private var prev: Any = this // DoubleLinkedNode | Removed
Roman Elizarov6f827832017-01-25 16:42:23 +030022 @Volatile
23 private var removedRef: Removed? = null // lazily cached removed ref to this
Roman Elizarov3754f952017-01-18 20:47:54 +030024
25 private companion object {
26 @JvmStatic
27 val NEXT: AtomicReferenceFieldUpdater<Node, Any> =
28 AtomicReferenceFieldUpdater.newUpdater(Node::class.java, Any::class.java, "_next")
29 @JvmStatic
30 val PREV: AtomicReferenceFieldUpdater<Node, Any> =
31 AtomicReferenceFieldUpdater.newUpdater(Node::class.java, Any::class.java, "prev")
Roman Elizarov6f827832017-01-25 16:42:23 +030032 @JvmStatic
33 val REMOVED_REF: AtomicReferenceFieldUpdater<Node, Removed?> =
34 AtomicReferenceFieldUpdater.newUpdater(Node::class.java, Removed::class.java, "removedRef")
Roman Elizarov3754f952017-01-18 20:47:54 +030035 }
36
37 private class Removed(val ref: Node) {
38 override fun toString(): String = "Removed[$ref]"
39 }
40
Roman Elizarov6f827832017-01-25 16:42:23 +030041 private fun removed(): Removed =
42 removedRef ?: Removed(this).also { REMOVED_REF.lazySet(this, it) }
43
Roman Elizarov55888f22017-01-26 11:48:37 +030044 abstract class CondAdd {
Roman Elizarov3754f952017-01-18 20:47:54 +030045 internal lateinit var newNode: Node
46 internal lateinit var oldNext: Node
47 @Volatile
48 private var consensus: Int = UNDECIDED // status of operation
49 abstract fun isCondition(): Boolean
50
51 private companion object {
52 @JvmStatic
53 val CONSENSUS: AtomicIntegerFieldUpdater<CondAdd> =
54 AtomicIntegerFieldUpdater.newUpdater(CondAdd::class.java, "consensus")
55
56 const val UNDECIDED = 0
57 const val SUCCESS = 1
58 const val FAILURE = 2
59 }
60
61 fun completeAdd(node: Node): Boolean {
62 // make decision on status
63 var consensus: Int
64 while (true) {
65 consensus = this.consensus
66 if (consensus != UNDECIDED) break
67 val proposal = if (isCondition()) SUCCESS else FAILURE
68 if (CONSENSUS.compareAndSet(this, UNDECIDED, proposal)) {
69 consensus = proposal
70 break
71 }
72 }
73 val success = consensus == SUCCESS
74 if (NEXT.compareAndSet(node, this, if (success) newNode else oldNext)) {
75 // only the thread the makes this update actually finishes add operation
76 if (success) newNode.finishAdd(oldNext)
77 }
78 return success
79 }
80 }
81
Roman Elizarov55888f22017-01-26 11:48:37 +030082 val isRemoved: Boolean get() = _next is Removed
Roman Elizarov3754f952017-01-18 20:47:54 +030083
84 private val isFresh: Boolean get() = _next === this && prev === this
85
86 private val next: Any get() {
87 while (true) { // helper loop on _next
88 val next = this._next
89 if (next !is CondAdd) return next
90 next.completeAdd(this)
91 }
92 }
93
Roman Elizarov55888f22017-01-26 11:48:37 +030094 fun next(): Node = next.unwrap()
Roman Elizarov3754f952017-01-18 20:47:54 +030095
Roman Elizarov01934df2017-01-31 09:18:34 +030096 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 {
Roman Elizarov3754f952017-01-18 20:47:54 +0300105 require(node.isFresh)
106 condAdd?.newNode = node
107 while (true) { // lock-free loop on next
108 val next = this.next as Node // this sentinel node is never removed
109 PREV.lazySet(node, this)
110 NEXT.lazySet(node, next)
111 condAdd?.oldNext = next
112 if (NEXT.compareAndSet(this, next, condAdd ?: node)) {
113 // added successfully (linearized add) -- fixup the list
114 return condAdd?.completeAdd(this) ?: run { node.finishAdd(next); true }
115 }
116 }
117 }
118
Roman Elizarov01934df2017-01-31 09:18:34 +0300119 /**
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 {
Roman Elizarovdaa79222017-01-26 11:31:31 +0300133 require(node.isFresh)
134 PREV.lazySet(node, this)
135 NEXT.lazySet(node, this)
136 if (!NEXT.compareAndSet(this, this, node)) return false // this is not an empty list!
137 // added successfully (linearized add) -- fixup the list
138 node.finishAdd(this)
139 return true
140 }
141
Roman Elizarov01934df2017-01-31 09:18:34 +0300142 // ------ addLastXXX ------
143
144 private fun addLastCC(node: Node, condAdd: CondAdd?): Boolean {
Roman Elizarov3754f952017-01-18 20:47:54 +0300145 require(node.isFresh)
146 condAdd?.newNode = node
147 while (true) { // lock-free loop on prev.next
Roman Elizarov01934df2017-01-31 09:18:34 +0300148 val prev = prevHelper() ?: continue
Roman Elizarov3754f952017-01-18 20:47:54 +0300149 PREV.lazySet(node, prev)
150 NEXT.lazySet(node, this)
151 condAdd?.oldNext = this
152 if (NEXT.compareAndSet(prev, this, condAdd ?: node)) {
153 // added successfully (linearized add) -- fixup the list
154 return condAdd?.completeAdd(prev) ?: run { node.finishAdd(this); true }
155 }
156 }
157 }
158
Roman Elizarov01934df2017-01-31 09:18:34 +0300159 /**
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
Roman Elizarov3754f952017-01-18 20:47:54 +0300178 }
179 }
180
Roman Elizarov01934df2017-01-31 09:18:34 +0300181 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
Roman Elizarov3754f952017-01-18 20:47:54 +0300201 /**
Roman Elizarov53a0a402017-01-19 20:21:57 +0300202 * Removes this node from the list. Returns `true` when removed successfully.
Roman Elizarov3754f952017-01-18 20:47:54 +0300203 */
Roman Elizarov55888f22017-01-26 11:48:37 +0300204 open fun remove(): Boolean {
Roman Elizarov3754f952017-01-18 20:47:54 +0300205 while (true) { // lock-free loop on next
206 val next = this.next
Roman Elizarov53a0a402017-01-19 20:21:57 +0300207 if (next is Removed) return false // was already removed -- don't try to help (original thread will take care)
Roman Elizarov6f827832017-01-25 16:42:23 +0300208 if (NEXT.compareAndSet(this, next, (next as Node).removed())) {
Roman Elizarov3754f952017-01-18 20:47:54 +0300209 // was removed successfully (linearized remove) -- fixup the list
210 helpDelete()
211 next.helpInsert(prev.unwrap())
Roman Elizarov53a0a402017-01-19 20:21:57 +0300212 return true
Roman Elizarov3754f952017-01-18 20:47:54 +0300213 }
214 }
215 }
216
Roman Elizarov55888f22017-01-26 11:48:37 +0300217 fun removeFirstOrNull(): Node? {
Roman Elizarov53a0a402017-01-19 20:21:57 +0300218 while (true) { // try to linearize
219 val first = next()
220 if (first == this) return null
221 if (first.remove()) return first
222 }
223 }
224
Roman Elizarov01934df2017-01-31 09:18:34 +0300225 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
Roman Elizarov3754f952017-01-18 20:47:54 +0300261 private fun markPrev(): Node {
262 while (true) { // lock-free loop on prev
263 val prev = this.prev
264 if (prev is Removed) return prev.ref
Roman Elizarov6f827832017-01-25 16:42:23 +0300265 if (PREV.compareAndSet(this, prev, (prev as Node).removed())) return prev
Roman Elizarov3754f952017-01-18 20:47:54 +0300266 }
267 }
268
269 // fixes next links to the left of this node
270 private fun helpDelete() {
271 var last: Node? = null // will set to the node left of prev when found
272 var prev: Node = markPrev()
273 var next: Node = (this._next as Removed).ref
274 while (true) {
275 // move to the right until first non-removed node
276 val nextNext = next.next
277 if (nextNext is Removed) {
278 next.markPrev()
279 next = nextNext.ref
280 continue
281 }
282 // move the the left until first non-removed node
283 val prevNext = prev.next
284 if (prevNext is Removed) {
285 if (last != null) {
286 prev.markPrev()
287 NEXT.compareAndSet(last, prev, prevNext.ref)
288 prev = last
289 last = null
290 } else {
291 prev = prev.prev.unwrap()
292 }
293 continue
294 }
295 if (prevNext !== this) {
296 // skipped over some removed nodes to the left -- setup to fixup the next links
297 last = prev
298 prev = prevNext as Node
299 if (prev === next) return // already done!!!
300 continue
301 }
302 // Now prev & next are Ok
303 if (NEXT.compareAndSet(prev, this, next)) return // success!
304 }
305 }
306
307 // fixes prev links from this node
308 private fun helpInsert(_prev: Node) {
309 var prev: Node = _prev
310 var last: Node? = null // will be set so that last.next === prev
311 while (true) {
312 // move the the left until first non-removed node
313 val prevNext = prev.next
314 if (prevNext is Removed) {
315 if (last !== null) {
316 prev.markPrev()
317 NEXT.compareAndSet(last, prev, prevNext.ref)
318 prev = last
319 last = null
320 } else {
321 prev = prev.prev.unwrap()
322 }
323 continue
324 }
325 val oldPrev = this.prev
326 if (oldPrev is Removed) return // this node was removed, too -- its remover will take care
327 if (prevNext !== this) {
328 // need to fixup next
329 last = prev
330 prev = prevNext as Node
331 continue
332 }
333 if (oldPrev === prev) return // it is already linked as needed
334 if (PREV.compareAndSet(this, oldPrev, prev)) {
335 if (prev.prev !is Removed) return // finish only if prev was not concurrently removed
336 }
337 }
338 }
339
340 private fun Any.unwrap(): Node = if (this is Removed) ref else this as Node
341
Roman Elizarov55888f22017-01-26 11:48:37 +0300342 fun validateNode(prev: Node, next: Node) {
Roman Elizarov3754f952017-01-18 20:47:54 +0300343 check(prev === this.prev)
344 check(next === this.next)
345 }
346}
347
Roman Elizarova5676592017-01-19 10:31:13 +0300348internal open class LockFreeLinkedListHead : LockFreeLinkedListNode() {
Roman Elizarov55888f22017-01-26 11:48:37 +0300349 val isEmpty: Boolean get() = next() == this
Roman Elizarov53a0a402017-01-19 20:21:57 +0300350
Roman Elizarov3754f952017-01-18 20:47:54 +0300351 /**
352 * Iterates over all elements in this list of a specified type.
353 */
Roman Elizarov55888f22017-01-26 11:48:37 +0300354 inline fun <reified T : Node> forEach(block: (T) -> Unit) {
Roman Elizarov3754f952017-01-18 20:47:54 +0300355 var cur: Node = next()
356 while (cur != this) {
357 if (cur is T) block(cur)
358 cur = cur.next()
359 }
360 }
361
Roman Elizarov01934df2017-01-31 09:18:34 +0300362 // just a defensive programming -- makes sure that list head sentinel is never removed
Roman Elizarov55888f22017-01-26 11:48:37 +0300363 final override fun remove() = throw UnsupportedOperationException()
Roman Elizarov3754f952017-01-18 20:47:54 +0300364
Roman Elizarov55888f22017-01-26 11:48:37 +0300365 fun validate() {
Roman Elizarov3754f952017-01-18 20:47:54 +0300366 var prev: Node = this
367 var cur: Node = next()
368 while (cur != this) {
369 val next = cur.next()
370 cur.validateNode(prev, next)
371 prev = cur
372 cur = next
373 }
374 validateNode(prev, next())
375 }
376}