Roman Elizarov | a567659 | 2017-01-19 10:31:13 +0300 | [diff] [blame] | 1 | package kotlinx.coroutines.experimental.internal |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 2 | |
| 3 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater |
| 4 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater |
| 5 | |
| 6 | private 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 Elizarov | a567659 | 2017-01-19 10:31:13 +0300 | [diff] [blame] | 17 | internal open class LockFreeLinkedListNode { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 18 | @Volatile |
| 19 | private var _next: Any = this // DoubleLinkedNode | Removed | CondAdd |
| 20 | @Volatile |
| 21 | private var prev: Any = this // DoubleLinkedNode | Removed |
Roman Elizarov | 6f82783 | 2017-01-25 16:42:23 +0300 | [diff] [blame] | 22 | @Volatile |
| 23 | private var removedRef: Removed? = null // lazily cached removed ref to this |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 24 | |
| 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 Elizarov | 6f82783 | 2017-01-25 16:42:23 +0300 | [diff] [blame] | 32 | @JvmStatic |
| 33 | val REMOVED_REF: AtomicReferenceFieldUpdater<Node, Removed?> = |
| 34 | AtomicReferenceFieldUpdater.newUpdater(Node::class.java, Removed::class.java, "removedRef") |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 35 | } |
| 36 | |
| 37 | private class Removed(val ref: Node) { |
| 38 | override fun toString(): String = "Removed[$ref]" |
| 39 | } |
| 40 | |
Roman Elizarov | 6f82783 | 2017-01-25 16:42:23 +0300 | [diff] [blame] | 41 | private fun removed(): Removed = |
| 42 | removedRef ?: Removed(this).also { REMOVED_REF.lazySet(this, it) } |
| 43 | |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 44 | abstract class CondAdd { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 45 | 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 Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 82 | val isRemoved: Boolean get() = _next is Removed |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 83 | |
| 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 Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 94 | fun next(): Node = next.unwrap() |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 95 | |
Roman Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 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 { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 105 | 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 Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 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 { |
Roman Elizarov | daa7922 | 2017-01-26 11:31:31 +0300 | [diff] [blame] | 133 | 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 Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 142 | // ------ addLastXXX ------ |
| 143 | |
| 144 | private fun addLastCC(node: Node, condAdd: CondAdd?): Boolean { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 145 | require(node.isFresh) |
| 146 | condAdd?.newNode = node |
| 147 | while (true) { // lock-free loop on prev.next |
Roman Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 148 | val prev = prevHelper() ?: continue |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 149 | 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 Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 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 |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 178 | } |
| 179 | } |
| 180 | |
Roman Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 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 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 201 | /** |
Roman Elizarov | 53a0a40 | 2017-01-19 20:21:57 +0300 | [diff] [blame] | 202 | * Removes this node from the list. Returns `true` when removed successfully. |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 203 | */ |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 204 | open fun remove(): Boolean { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 205 | while (true) { // lock-free loop on next |
| 206 | val next = this.next |
Roman Elizarov | 53a0a40 | 2017-01-19 20:21:57 +0300 | [diff] [blame] | 207 | if (next is Removed) return false // was already removed -- don't try to help (original thread will take care) |
Roman Elizarov | 6f82783 | 2017-01-25 16:42:23 +0300 | [diff] [blame] | 208 | if (NEXT.compareAndSet(this, next, (next as Node).removed())) { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 209 | // was removed successfully (linearized remove) -- fixup the list |
| 210 | helpDelete() |
| 211 | next.helpInsert(prev.unwrap()) |
Roman Elizarov | 53a0a40 | 2017-01-19 20:21:57 +0300 | [diff] [blame] | 212 | return true |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 217 | fun removeFirstOrNull(): Node? { |
Roman Elizarov | 53a0a40 | 2017-01-19 20:21:57 +0300 | [diff] [blame] | 218 | 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 Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 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 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 261 | 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 Elizarov | 6f82783 | 2017-01-25 16:42:23 +0300 | [diff] [blame] | 265 | if (PREV.compareAndSet(this, prev, (prev as Node).removed())) return prev |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 266 | } |
| 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 Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 342 | fun validateNode(prev: Node, next: Node) { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 343 | check(prev === this.prev) |
| 344 | check(next === this.next) |
| 345 | } |
| 346 | } |
| 347 | |
Roman Elizarov | a567659 | 2017-01-19 10:31:13 +0300 | [diff] [blame] | 348 | internal open class LockFreeLinkedListHead : LockFreeLinkedListNode() { |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 349 | val isEmpty: Boolean get() = next() == this |
Roman Elizarov | 53a0a40 | 2017-01-19 20:21:57 +0300 | [diff] [blame] | 350 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 351 | /** |
| 352 | * Iterates over all elements in this list of a specified type. |
| 353 | */ |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 354 | inline fun <reified T : Node> forEach(block: (T) -> Unit) { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 355 | var cur: Node = next() |
| 356 | while (cur != this) { |
| 357 | if (cur is T) block(cur) |
| 358 | cur = cur.next() |
| 359 | } |
| 360 | } |
| 361 | |
Roman Elizarov | 01934df | 2017-01-31 09:18:34 +0300 | [diff] [blame^] | 362 | // just a defensive programming -- makes sure that list head sentinel is never removed |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 363 | final override fun remove() = throw UnsupportedOperationException() |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 364 | |
Roman Elizarov | 55888f2 | 2017-01-26 11:48:37 +0300 | [diff] [blame] | 365 | fun validate() { |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 366 | 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 | } |