All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nativeMain.internal.LockFreeMPSCQueue.kt Maven / Gradle / Ivy

There is a newer version: 1.3.2-1.3.60
Show newest version
/*
 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

package kotlinx.coroutines.internal

import kotlinx.atomicfu.*

private typealias Core = LockFreeMPSCQueueCore

/**
 * Lock-free Multiply-Producer Single-Consumer Queue.
 * *Note: This queue is NOT linearizable. It provides only quiescent consistency for its operations.*
 *
 * In particular, the following execution is permitted for this queue, but is not permitted for a linearizable queue:
 *
 * ```
 * Thread 1: addLast(1) = true, removeFirstOrNull() = null
 * Thread 2: addLast(2) = 2 // this operation is concurrent with both operations in the first thread
 * ```
 *
 * @suppress **This is unstable API and it is subject to change.**
 */
class LockFreeMPSCQueue {
    private val _cur = atomic(Core(Core.INITIAL_CAPACITY))

    // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
    val isEmpty: Boolean get() = _cur.value.isEmpty

    fun close() {
        _cur.loop { cur ->
            if (cur.close()) return // closed this copy
            _cur.compareAndSet(cur, cur.next()) // move to next
        }
    }

    fun addLast(element: E): Boolean {
        _cur.loop { cur ->
            when (cur.addLast(element)) {
                Core.ADD_SUCCESS -> return true
                Core.ADD_CLOSED -> return false
                Core.ADD_FROZEN -> _cur.compareAndSet(cur, cur.next()) // move to next
            }
        }
    }

    @Suppress("UNCHECKED_CAST")
    fun removeFirstOrNull(): E? {
        _cur.loop { cur ->
            val result = cur.removeFirstOrNull()
            if (result !== Core.REMOVE_FROZEN) return result as E?
            _cur.compareAndSet(cur, cur.next())
        }
    }
}

/**
 * Lock-free Multiply-Producer Single-Consumer Queue core.
 * *Note: This queue is NOT linearizable. It provides only quiescent consistency for its operations.*
 *
 * @see LockFreeMPSCQueue
 * @suppress **This is unstable API and it is subject to change.**
 */
internal class LockFreeMPSCQueueCore(private val capacity: Int) {
    private val mask = capacity - 1
    private val _next = atomic?>(null)
    private val _state = atomic(0L)
    private val array = arrayOfNulls(capacity);

    init {
        check(mask <= MAX_CAPACITY_MASK)
        check(capacity and mask == 0)
    }

    // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
    val isEmpty: Boolean get() = _state.value.withState { head, tail -> head == tail }

    fun close(): Boolean {
        _state.update { state ->
            if (state and CLOSED_MASK != 0L) return true // ok - already closed
            if (state and FROZEN_MASK != 0L) return false // frozen -- try next
            state or CLOSED_MASK // try set closed bit
        }
        return true
    }

    // ADD_CLOSED | ADD_FROZEN | ADD_SUCCESS
    fun addLast(element: E): Int {
        _state.loop { state ->
            if (state and (FROZEN_MASK or CLOSED_MASK) != 0L) return state.addFailReason() // cannot add
            state.withState { head, tail ->
                // there could be one REMOVE element beyond head that we cannot stump up,
                // so we check for full queue with an extra margin of one element
                if ((tail + 2) and mask == head and mask) return ADD_FROZEN // overfull, so do freeze & copy
                val newTail = (tail + 1) and MAX_CAPACITY_MASK
                if (_state.compareAndSet(state, state.updateTail(newTail))) {
                    // successfully added
                    array[tail and mask] = element
                    // could have been frozen & copied before this item was set -- correct it by filling placeholder
                    var cur = this
                    while(true) {
                        if (cur._state.value and FROZEN_MASK == 0L) break // all fine -- not frozen yet
                        cur = cur.next().fillPlaceholder(tail, element) ?: break
                    }
                    return ADD_SUCCESS // added successfully
                }
            }
        }
    }

    private fun fillPlaceholder(index: Int, element: E): Core? {
//        if (array.compareAndSet(index and mask, PLACEHOLDER, element)) {
//            // we've corrected missing element, should check if that propagated to further copies, just in case
//            return this
//        }
//        // it is Ok, no need for further action
//        return null
        if (array[index and mask] != PLACEHOLDER) return null
        array[index and mask] = element
        return this
    }

    // SINGLE CONSUMER
    // REMOVE_FROZEN | null (EMPTY) | E (SUCCESS)
    fun removeFirstOrNull(): Any? {
        _state.loop { state ->
            if (state and FROZEN_MASK != 0L) return REMOVE_FROZEN // frozen -- cannot modify
            state.withState { head, tail ->
                if ((tail and mask) == (head and mask)) return null // empty
                // because queue is Single Consumer, then element == null|PLACEHOLDER can only be when add has not finished yet
                val element = array[head and mask] ?: return null
                if (element === PLACEHOLDER) return null // same story -- consider it not added yet
                check(element !== REMOVED) { "This queue can have only one consumer" }
                // tentatively remove element to let GC work
                // we cannot put null into array, because copying thread could replace it with PLACEHOLDER
                // and that is a disaster, so a separate REMOVED token is used here.
                // Note: at most one REMOVED in the array, because single consumer.
                array[head and mask] = REMOVED
                val newHead = (head + 1) and MAX_CAPACITY_MASK
                if (_state.compareAndSet(state, state.updateHead(newHead))) {
                    array[head and mask] = null // now can safely put null (state was updated)
                    return element // successfully removed in fast-path
                }
                // Slow-path for remove in case of interference
                var cur = this
                while (true) {
                    cur = cur.removeSlowPath(head, newHead) ?: return element
                }
            }
        }
    }

    private fun removeSlowPath(oldHead: Int, newHead: Int): Core? {
        _state.loop { state ->
            state.withState { head, _ ->
                check(head == oldHead) { "This queue can have only one consumer" }
                if (state and FROZEN_MASK != 0L) {
                    // state was already frozen, so either old or REMOVED item could have been copied to next
                    val next = next()
                    next.array[head and next.mask] = REMOVED // make sure it is removed in new array regardless
                    return next // continue to correct head in next
                }
                if (_state.compareAndSet(state, state.updateHead(newHead))) {
                    array[head and mask] = null // now can safely put null (state was updated)
                    return null
                }
            }
        }
    }

    fun next(): LockFreeMPSCQueueCore = allocateOrGetNextCopy(markFrozen())

    private fun markFrozen(): Long =
        _state.updateAndGet { state ->
            if (state and FROZEN_MASK != 0L) return state // already marked
            state or FROZEN_MASK
        }

    private fun allocateOrGetNextCopy(state: Long): Core {
        _next.loop { next ->
            if (next != null) return next // already allocated & copied
            _next.compareAndSet(null, allocateNextCopy(state))
        }
    }

    private fun allocateNextCopy(state: Long): Core {
        val next = LockFreeMPSCQueueCore(capacity * 2)
        state.withState { head, tail ->
            var index = head
            while (index and mask != tail and mask) {
                // replace nulls with placeholders on copy
                next.array[index and next.mask] = array[index and mask] ?: PLACEHOLDER
                index++
            }
            next._state.value = state wo FROZEN_MASK
        }
        return next
    }

    @Suppress("PrivatePropertyName")
    internal companion object {
        internal const val INITIAL_CAPACITY = 8

        private const val CAPACITY_BITS = 30
        private const val MAX_CAPACITY_MASK = (1 shl CAPACITY_BITS) - 1
        private const val HEAD_SHIFT = 0
        private const val HEAD_MASK = MAX_CAPACITY_MASK.toLong() shl HEAD_SHIFT
        private const val TAIL_SHIFT = HEAD_SHIFT + CAPACITY_BITS
        private const val TAIL_MASK = MAX_CAPACITY_MASK.toLong() shl TAIL_SHIFT

        private const val FROZEN_SHIFT = TAIL_SHIFT + CAPACITY_BITS
        private const val FROZEN_MASK = 1L shl FROZEN_SHIFT
        private const val CLOSED_SHIFT = FROZEN_SHIFT + 1
        private const val CLOSED_MASK = 1L shl CLOSED_SHIFT
        @SharedImmutable
        internal val REMOVE_FROZEN = Symbol("REMOVE_FROZEN")

        internal const val ADD_SUCCESS = 0
        internal const val ADD_FROZEN = 1
        internal const val ADD_CLOSED = 2
        @SharedImmutable
        private val PLACEHOLDER = Symbol("PLACEHOLDER")
        @SharedImmutable
        private val REMOVED = Symbol("PLACEHOLDER")

        private infix fun Long.wo(other: Long) = this and other.inv()
        private fun Long.updateHead(newHead: Int) = (this wo HEAD_MASK) or (newHead.toLong() shl HEAD_SHIFT)
        private fun Long.updateTail(newTail: Int) = (this wo TAIL_MASK) or (newTail.toLong() shl TAIL_SHIFT)

        private inline fun  Long.withState(block: (head: Int, tail: Int) -> T): T {
            val head = ((this and HEAD_MASK) shr HEAD_SHIFT).toInt()
            val tail = ((this and TAIL_MASK) shr TAIL_SHIFT).toInt()
            return block(head, tail)
        }

        // FROZEN | CLOSED
        private fun Long.addFailReason(): Int = if (this and CLOSED_MASK != 0L) ADD_CLOSED else ADD_FROZEN
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy