├── .gitmodules ├── tests ├── config.nims ├── nim.cfg ├── t_atomic_dsl.nim ├── test.nim ├── t_sipsic_threaded.nim ├── t_sip.nim ├── t_mupsic_threaded.nim ├── t_wraparound.nim ├── t_mupmuc_threaded.nim ├── t_sipsic.nim ├── t_mupsic.nim ├── t_integration.nim ├── t_mupmuc.nim ├── t_muc.nim ├── t_mup.nim ├── t_sic.nim └── t_ops.nim ├── .vscode ├── extensions.json ├── c_cpp_properties.json ├── launch.json └── tasks.json ├── nimdoc.cfg ├── .gitignore ├── .editorconfig ├── AUTHORS ├── examples ├── nim.cfg ├── sipsic.nim ├── mupsic.nim └── mupmuc.nim ├── CONTRIBUTING.md ├── src ├── lockfreequeues │ ├── constants.nim │ ├── atomic_dsl.nim │ ├── ops.nim │ ├── sipsic.nim │ ├── mupmuc.nim │ └── mupsic.nim └── lockfreequeues.nim ├── lockfreequeues.nimble ├── LICENSE ├── .github └── workflows │ ├── update-docs.yml │ └── build.yml ├── lockfreequeues.code-workspace ├── CHANGELOG.md └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["nimsaem.nimvscode", "junknet.nimlsp"] 3 | } 4 | -------------------------------------------------------------------------------- /nimdoc.cfg: -------------------------------------------------------------------------------- 1 | 2 | threads = on 3 | project = on 4 | index = on 5 | git.url = "https://github.com/elijahr/lockfreequeues" 6 | git.commit = "master" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/tags 2 | .DS_Store 3 | 4 | nimcache/ 5 | nimblecache/ 6 | 7 | /src/* 8 | /tests/* 9 | /examples/* 10 | /htmldocs/* 11 | 12 | !**/*.nims 13 | !**/*.nim 14 | !**/*.cfg 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | 5 | [*.nim] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Corporate Copyright Statements 2 | ============================== 3 | 4 | © Copyright 2020 Elijah Shaw-Rutschman 5 | 6 | See the file "LICENSE", included in this distribution for details about the copyright. 7 | 8 | Contributors 9 | ============ 10 | 11 | Elijah Shaw-Rutschman 12 | -------------------------------------------------------------------------------- /examples/nim.cfg: -------------------------------------------------------------------------------- 1 | --path:"../src/" 2 | 3 | # Do not optimize for speed/size 4 | --opt:none 5 | 6 | --showAllMismatches:on 7 | --styleCheck:hint 8 | 9 | # debuggable 10 | --define:debug 11 | --debuginfo 12 | --debugger:native 13 | --lineDir:on 14 | 15 | # threading 16 | --threads:on 17 | --tlsEmulation:off 18 | -------------------------------------------------------------------------------- /tests/nim.cfg: -------------------------------------------------------------------------------- 1 | --path:"../src/" 2 | 3 | # Do not optimize for speed/size 4 | --opt:none 5 | 6 | --showAllMismatches:on 7 | --styleCheck:hint 8 | 9 | --define:testing 10 | 11 | # debuggable 12 | --define:debug 13 | --debuginfo 14 | --debugger:native 15 | --lineDir:on 16 | 17 | # threading 18 | --threads:on 19 | --tlsEmulation:off 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to lockfreequeues 2 | 3 | The contributors are listed in AUTHORS (add yourself). This project uses the MIT license, see LICENSE. 4 | 5 | Please read these documents before you send a patch: 6 | 7 | - New functionality should be covered by a unit test. 8 | 9 | - If a bug is being fixed, please add a unit test that would fail without the bug fix. 10 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": ["${workspaceFolder}/**"], 6 | "defines": [], 7 | "macFrameworkPath": [], 8 | "compilerPath": "/usr/bin/gcc", 9 | "cStandard": "c11", 10 | "cppStandard": "gnu++14", 11 | "intelliSenseMode": "clang-x64" 12 | } 13 | ], 14 | "version": 4 15 | } 16 | -------------------------------------------------------------------------------- /src/lockfreequeues/constants.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | ## Constants used by lockfreequeues 8 | 9 | # The size of a cache line (128 bytes on PowerPC, 64 bytes elsewhere) 10 | const CacheLineBytes* {.intdefine.} = when defined(powerpc): 11 | 128 12 | else: 13 | 64 14 | -------------------------------------------------------------------------------- /tests/t_atomic_dsl.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | import atomics 8 | import unittest 9 | 10 | import lockfreequeues 11 | 12 | 13 | suite "atomic_dsl": 14 | var atom: Atomic[int] 15 | 16 | test "integration": 17 | atom.relaxed(1) 18 | assert(atom.relaxed == 1) 19 | atom.relaxed(2) 20 | assert(atom.acquire == 2) 21 | -------------------------------------------------------------------------------- /tests/test.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | import ./t_atomic_dsl 8 | import ./t_mupmuc 9 | import ./t_mupmuc_threaded 10 | import ./t_mupsic 11 | import ./t_mupsic_threaded 12 | import ./t_ops 13 | import ./t_sipsic 14 | import ./t_sipsic_threaded 15 | import ./t_wraparound 16 | 17 | export 18 | t_atomic_dsl, 19 | t_mupmuc, 20 | t_mupmuc_threaded, 21 | t_mupsic, 22 | t_mupsic_threaded, 23 | t_ops, 24 | t_sipsic, 25 | t_sipsic_threaded, 26 | t_wraparound 27 | -------------------------------------------------------------------------------- /src/lockfreequeues.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | when compileOption("threads"): 8 | import ./lockfreequeues/[ 9 | atomic_dsl, 10 | constants, 11 | mupmuc, 12 | mupsic, 13 | ops, 14 | sipsic, 15 | ] 16 | 17 | export 18 | atomic_dsl, 19 | constants, 20 | mupmuc, 21 | mupsic, 22 | ops, 23 | sipsic 24 | else: 25 | # threading off, only provide sipsic 26 | import ./lockfreequeues/[ 27 | atomic_dsl, 28 | constants, 29 | ops, 30 | sipsic, 31 | ] 32 | 33 | export 34 | atomic_dsl, 35 | constants, 36 | ops, 37 | sipsic 38 | -------------------------------------------------------------------------------- /lockfreequeues.nimble: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Package 4 | version = "3.1.0" 5 | author = "Elijah Shaw-Rutschman" 6 | description = "Lock-free queue implementations for Nim." 7 | license = "MIT" 8 | srcDir = "src" 9 | 10 | # Dependencies 11 | requires "nim >= 2.0.0" 12 | 13 | # Tasks 14 | task test, "Runs the test suite": 15 | # C 16 | exec "nim c -r -f tests/test.nim" 17 | 18 | # C++ 19 | exec "nim cpp -r -f tests/test.nim" 20 | 21 | if getEnv("SANITIZE_THREADS") != "no": 22 | # C (with thread sanitization) 23 | exec "nim c --cc:clang --passC:\"-fsanitize=thread\" --passL:\"-fsanitize=thread\" -r -f tests/test.nim" 24 | 25 | 26 | task examples, "Runs the examples": 27 | exec "nim c -r -f examples/mupmuc.nim" 28 | exec "nim c -r -f examples/mupsic.nim" 29 | exec "nim c -r -f examples/sipsic.nim" 30 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug tests (cppdbg)", 9 | "type": "cppdbg", 10 | "MIMode": "lldb", 11 | "request": "launch", 12 | "program": "${workspaceRoot}/tests/test", 13 | // "args": ["Mupmuc[N, P, C, T] threaded::"], 14 | "cwd": "${workspaceRoot}" 15 | }, 16 | { 17 | "name": "Debug examples (cppdbg)", 18 | "type": "cppdbg", 19 | "MIMode": "lldb", 20 | "request": "launch", 21 | "program": "${workspaceRoot}/examples/mupsic", 22 | // "args": ["ops.used(head, tail, capacity)::"], 23 | "cwd": "${workspaceRoot}" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Elijah Shaw-Rutschman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/update-docs.yml: -------------------------------------------------------------------------------- 1 | # yamllint disable rule:line-length 2 | 3 | name: build-docs 4 | 5 | # yamllint disable rule:truthy 6 | on: 7 | push: 8 | branches: 9 | - devel 10 | # yamllint enable rule:truthy 11 | 12 | jobs: 13 | release_docs: 14 | name: Update docs 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout project 18 | uses: actions/checkout@v2 19 | 20 | - name: Install Nim 21 | uses: asdf-vm/actions/install@v1 22 | with: 23 | tool_versions: | 24 | nim latest:2.0 25 | 26 | - name: Generate documentation 27 | run: | 28 | . "${HOME}/.asdf/asdf.sh" 29 | asdf local nim latest:2.0 30 | rm -rf htmldocs 31 | nim doc --out:htmldocs src/lockfreequeues.nim 32 | mv htmldocs/theindex.html htmldocs/index.html 33 | find htmldocs -type f -name "*.html" -exec sed -i '' -e 's/theindex\.html/index\.html/g' '{}' ';' 34 | 35 | - name: Update gh-pages branch 36 | uses: peaceiris/actions-gh-pages@v3 37 | with: 38 | github_token: ${{ secrets.GITHUB_TOKEN }} 39 | publish_dir: ./htmldocs 40 | -------------------------------------------------------------------------------- /tests/t_sipsic_threaded.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | import options 8 | import unittest 9 | 10 | import lockfreequeues 11 | 12 | const capacity = 8 13 | const itemCount = 128 14 | 15 | var 16 | queue = initSipsic[capacity, int]() 17 | output = initSipsic[itemCount, int]() 18 | 19 | 20 | proc consumerFunc() {.thread.} = 21 | var count = 0 22 | while count < itemCount: 23 | let res = queue.pop() 24 | if res.isSome: 25 | while not output.push(res.get): 26 | discard 27 | inc count 28 | 29 | 30 | proc producerFunc() {.thread.} = 31 | for i in 0..= 0 and count <= capacity 45 | 46 | 47 | proc used*( 48 | head: int, 49 | tail: int, 50 | capacity: int, 51 | ): int 52 | {.inline.} = 53 | ## Determine how many slots are taken in storage given `head`, `tail`, and 54 | ## `capacity` values. 55 | # assert validateHeadAndTail(head, tail, capacity) 56 | assert validateHeadOrTail(head, capacity) 57 | assert validateHeadOrTail(tail, capacity) 58 | result = tail - head 59 | if result < 0: 60 | # Case when front in [Capacity, 2*Capacity) 61 | # and tail in [0, Capacity) range 62 | # for example for a queue of capacity 7 that rolled twice: 63 | # 64 | # | 14 | | | 10 | 11 | 12 | 13 | 65 | # ^ ^ 66 | # back front 67 | # 68 | # front is at index 10 (real 3) 69 | # back is at index 15 (real 1) 70 | # back - front + capacity = 1 - 3 + 7 = 5 71 | result += 2 * capacity 72 | 73 | 74 | proc available*( 75 | head: int, 76 | tail: int, 77 | capacity: int, 78 | ): int 79 | {.inline.} = 80 | ## Determine how many slots are available in storage given `head`, `tail`, and 81 | ## `capacity` values. 82 | result = capacity - used(head, tail, capacity) 83 | 84 | 85 | proc index*( 86 | value: int, 87 | capacity: int, 88 | ): int 89 | {.inline.} = 90 | ## Given a head or tail `value` in the range `0..<2*capacity`, determine its 91 | ## actual index in storage. 92 | assert validateHeadOrTail(value, capacity) 93 | result = 94 | if value >= capacity: 95 | value - capacity 96 | else: 97 | value 98 | 99 | proc incOrReset*( 100 | original: int, 101 | amount: int, 102 | capacity: int, 103 | ): int 104 | {.inline.} = 105 | ## Given an `original` head or tail value and an `amount` to increment, either 106 | ## increment `original` by `amount`, or reset from zero if 107 | ## `original + amount >= 2 * capacity`. 108 | assert validateHeadOrTail(original, capacity) 109 | assert amount in 0..capacity 110 | result = original + amount 111 | if result >= 2 * capacity: 112 | result -= 2 * capacity 113 | 114 | 115 | proc full*( 116 | head: int, 117 | tail: int, 118 | capacity: int, 119 | ): bool 120 | {.inline.} = 121 | ## Determine if storage is full given `head`, `tail`, and `capacity` values. 122 | return used(head, tail, capacity) >= capacity 123 | 124 | 125 | proc empty*( 126 | head: int, 127 | tail: int, 128 | capacity: int, 129 | ): bool 130 | {.inline.} = 131 | ## Determine if storage is empty given `head` and `tail` values. 132 | # assert validateHeadAndTail(head, tail, capacity) 133 | assert validateHeadOrTail(head, capacity) 134 | assert validateHeadOrTail(tail, capacity) 135 | return head == tail 136 | 137 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # yamllint disable rule:line-length 2 | 3 | name: build 4 | 5 | # yamllint disable rule:truthy 6 | on: 7 | pull_request: 8 | paths-ignore: 9 | - '*.md' 10 | push: 11 | paths-ignore: 12 | - '*.md' 13 | # yamllint enable rule:truthy 14 | 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | jobs: 19 | test_x86: 20 | name: Test nim-${{ matrix.nim-version }} / ${{ matrix.runs-on }} / x86_64 21 | runs-on: ${{ matrix.runs-on }} 22 | timeout-minutes: ${{ matrix.timeout-minutes }} 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | include: 28 | - runs-on: ubuntu-latest 29 | nim-version: '2.0.8' 30 | sanitize-threads: 'yes' 31 | timeout-minutes: 5 32 | 33 | steps: 34 | # Optimization: re-use cached Nim->C compilation 35 | - name: Restore cache 36 | uses: actions/cache@v3 37 | with: 38 | path: ${HOME}/.cache 39 | key: cache-${{ matrix.runs-on }}-${{ matrix.nim-version }} 40 | 41 | - name: Checkout project 42 | uses: actions/checkout@v4 43 | 44 | - name: Install Nim 45 | uses: asdf-vm/actions/install@v3 46 | with: 47 | tool_versions: | 48 | nim ${{ matrix.nim-version }} 49 | 50 | - name: Install dependencies 51 | if: runner.os == 'Linux' 52 | run: | 53 | sudo apt-get update -q -y 54 | sudo apt-get -qq install -y clang 55 | 56 | - name: Run tests 57 | run: | 58 | export SANITIZE_THREADS 59 | SANITIZE_THREADS=${{ matrix.sanitize-threads }} 60 | . "${HOME}/.asdf/asdf.sh" 61 | nimble develop -y 62 | nimble test 63 | nimble examples 64 | 65 | test_non_x86: 66 | name: Test nim-${{ matrix.nim-version }} / debian-buster / ${{ matrix.arch }} 67 | runs-on: ubuntu-latest 68 | timeout-minutes: 60 69 | strategy: 70 | fail-fast: false 71 | matrix: 72 | include: 73 | - arch: aarch64 74 | nim-version: 'ref:version-2-0' 75 | 76 | steps: 77 | # Optimization: re-use cached Nim->C compilation 78 | - name: Restore cache 79 | uses: actions/cache@v3 80 | with: 81 | path: ${HOME}/.cache 82 | key: cache-${{ matrix.arch }}-${{ matrix.nim-version }} 83 | 84 | - name: Checkout Nim project 85 | uses: actions/checkout@v4 86 | 87 | # Install & run tests on non-x86 88 | - uses: uraimo/run-on-arch-action@v2.7.2 89 | name: Install Nim & run tests 90 | with: 91 | arch: ${{ matrix.arch }} 92 | distro: buster 93 | 94 | dockerRunArgs: | 95 | --volume "${HOME}/.cache:/root/.cache" 96 | 97 | setup: mkdir -p "${HOME}/.cache" 98 | 99 | shell: /usr/bin/env bash 100 | 101 | install: | 102 | set -uexo pipefail 103 | # Install asdf and dependencies 104 | apt-get update -q -y 105 | apt-get -qq install -y build-essential curl git 106 | git clone https://github.com/asdf-vm/asdf.git \ 107 | "${HOME}/.asdf" \ 108 | --branch v0.10.2 109 | 110 | env: | 111 | SANITIZE_THREADS: no 112 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 113 | 114 | run: | 115 | set -uexo pipefail 116 | . "${HOME}/.asdf/asdf.sh" 117 | 118 | # Install asdf-nim and dependencies 119 | echo "::group::Install Nim" 120 | git clone https://github.com/asdf-community/asdf-nim.git \ 121 | ~/asdf-nim \ 122 | --branch main \ 123 | --depth 1 124 | asdf plugin add nim ~/asdf-nim 125 | asdf nim install-deps -y 126 | 127 | # Install Nim 128 | asdf install nim ${{ matrix.nim-version }} 129 | asdf local nim ${{ matrix.nim-version }} 130 | echo "::endgroup::" 131 | 132 | # Run tests 133 | echo "::group::Run tests" 134 | git config --global --add safe.directory ${PWD} 135 | nimble develop -y 136 | nimble test 137 | nimble examples 138 | echo "::endgroup::" 139 | -------------------------------------------------------------------------------- /tests/t_integration.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | template testCapacity*(queue: untyped) = 8 | check(queue.capacity == 8) 9 | 10 | 11 | template testHeadAndTailReset*(queue: untyped) = 12 | queue.head.sequential(15) 13 | queue.tail.sequential(15) 14 | when ((queue is Mupsic) or (queue is Mupmuc)): 15 | queue.prevProducerIdx.sequential(0) 16 | queue.producerTails[0].sequential(15) 17 | when queue is Mupmuc: 18 | queue.prevConsumerIdx.sequential(0) 19 | queue.consumerHeads[0].sequential(15) 20 | queue.checkState( 21 | head = 15, 22 | tail = 15, 23 | storage = repeat(0, 8), 24 | ) 25 | when ((queue is Mupsic) or (queue is Mupmuc)): 26 | queue.checkState( 27 | prevProducerIdx = 0, 28 | producerTails = (@[15, 0, 0, 0]), 29 | ) 30 | when queue is Mupmuc: 31 | queue.checkState( 32 | prevConsumerIdx = 0, 33 | consumerHeads = (@[15, 0, 0, 0]), 34 | ) 35 | 36 | when ((queue is Mupsic) or (queue is Mupmuc)): 37 | check(queue.getProducer(0).push(@[1]).isNone) 38 | else: 39 | check(queue.push(@[1]).isNone) 40 | 41 | queue.checkState( 42 | head = 15, 43 | tail = 0, 44 | storage = (@[0, 0, 0, 0, 0, 0, 0, 1]), 45 | ) 46 | 47 | when ((queue is Mupsic) or (queue is Mupmuc)): 48 | queue.checkState( 49 | prevProducerIdx = 0, 50 | producerTails = (@[0, 0, 0, 0]), 51 | ) 52 | 53 | when queue is Mupmuc: 54 | queue.checkState( 55 | prevConsumerIdx = 0, 56 | consumerHeads = (@[15, 0, 0, 0]), 57 | ) 58 | 59 | let res = 60 | when queue is Mupmuc: 61 | queue.getConsumer(0).pop(1) 62 | else: 63 | queue.pop(1) 64 | 65 | check(res.isSome) 66 | check(res.get == @[1]) 67 | queue.checkState( 68 | head = 0, 69 | tail = 0, 70 | storage = (@[0, 0, 0, 0, 0, 0, 0, 1]), 71 | ) 72 | when ((queue is Mupsic) or (queue is Mupmuc)): 73 | queue.checkState( 74 | prevProducerIdx = 0, 75 | producerTails = repeat(0, 4), 76 | ) 77 | when queue is Mupmuc: 78 | queue.checkState( 79 | prevConsumerIdx = 0, 80 | consumerHeads = (@[0, 0, 0, 0]), 81 | ) 82 | 83 | 84 | template testWraps*(queue: untyped) = 85 | when ((queue is Mupsic) or (queue is Mupmuc)): 86 | check(queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 87 | else: 88 | check(queue.push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 89 | 90 | var popRes = 91 | when queue is Mupmuc: 92 | queue.getConsumer(0).pop(4) 93 | else: 94 | queue.pop(4) 95 | 96 | check(popRes.isSome) 97 | check(popRes.get == @[1, 2, 3, 4]) 98 | 99 | let pushRes = 100 | when ((queue is Mupsic) or (queue is Mupmuc)): 101 | queue.getProducer(0).push(@[9, 10, 11, 12]) 102 | else: 103 | queue.push(@[9, 10, 11, 12]) 104 | 105 | check(pushRes.isNone) 106 | 107 | queue.checkState( 108 | head = 4, 109 | tail = 12, 110 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 111 | ) 112 | when ((queue is Mupsic) or (queue is Mupmuc)): 113 | queue.checkState( 114 | prevProducerIdx = 0, 115 | producerTails = (@[12, 0, 0, 0]), 116 | ) 117 | 118 | when queue is Mupmuc: 119 | queue.checkState( 120 | prevConsumerIdx = 0, 121 | consumerHeads = (@[4, 0, 0, 0]), 122 | ) 123 | 124 | popRes = 125 | when queue is Mupmuc: 126 | queue.getConsumer(0).pop(4) 127 | else: 128 | queue.pop(4) 129 | check(popRes.isSome) 130 | check(popRes.get == @[5, 6, 7, 8]) 131 | 132 | queue.checkState( 133 | head = 8, 134 | tail = 12, 135 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 136 | ) 137 | 138 | when ((queue is Mupsic) or (queue is Mupmuc)): 139 | queue.checkState( 140 | prevProducerIdx = 0, 141 | producerTails = (@[12, 0, 0, 0]), 142 | ) 143 | 144 | when queue is Mupmuc: 145 | queue.checkState( 146 | prevConsumerIdx = 0, 147 | consumerHeads = (@[8, 0, 0, 0]), 148 | ) 149 | 150 | popRes = 151 | when queue is Mupmuc: 152 | queue.getConsumer(1).pop(4) 153 | else: 154 | queue.pop(4) 155 | check(popRes.isSome) 156 | check(popRes.get == @[9, 10, 11, 12]) 157 | 158 | queue.checkState( 159 | head = 12, 160 | tail = 12, 161 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 162 | ) 163 | when ((queue is Mupsic) or (queue is Mupmuc)): 164 | queue.checkState( 165 | prevProducerIdx = 0, 166 | producerTails = (@[12, 0, 0, 0]), 167 | ) 168 | when queue is Mupmuc: 169 | queue.checkState( 170 | prevConsumerIdx = 1, 171 | consumerHeads = (@[8, 12, 0, 0]), 172 | ) 173 | -------------------------------------------------------------------------------- /tests/t_mupmuc.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | import atomics 8 | import options 9 | import sequtils 10 | import unittest 11 | 12 | import lockfreequeues 13 | import ./t_integration 14 | import ./t_muc 15 | import ./t_mup 16 | 17 | 18 | var queue = initMupmuc[8, 4, 4, int]() 19 | 20 | 21 | suite "Mupmuc[N, P, C, T]": 22 | test "capacity": 23 | check(queue.capacity == 8) 24 | 25 | test "producerCount": 26 | check(queue.producerCount == 4) 27 | 28 | test "initial state": 29 | queue.checkState( 30 | head = 0, 31 | tail = 0, 32 | storage = repeat(0, 8), 33 | ) 34 | queue.checkState( 35 | prevProducerIdx = NoProducerIdx, 36 | producerTails = repeat(0, 4), 37 | ) 38 | 39 | 40 | suite "getProducer(Mupmuc[N, P, C, T])": 41 | setup: 42 | queue.reset() 43 | 44 | test "assigns by thread id": 45 | testMupGetProducerAssigns(queue) 46 | 47 | test "reuses assigned": 48 | testMupGetProducerReusesAssigned(queue) 49 | 50 | test "explicit index": 51 | testMupGetProducerExplicitIndex(queue) 52 | 53 | test "throws NoProducersAvailableError": 54 | testMupGetProducerThrowsNoProducersAvailable(queue) 55 | 56 | 57 | suite "push(Mupmuc[N, P, C, T])": 58 | setup: 59 | queue.reset() 60 | 61 | test "seq[T] should fail": 62 | expect InvalidCallDefect: 63 | discard queue.push(1) 64 | 65 | test "T should fail": 66 | expect InvalidCallDefect: 67 | discard queue.push(@[1]) 68 | 69 | 70 | suite "push(Producer[N, P, T], T)": 71 | setup: 72 | queue.reset() 73 | 74 | test "basic": 75 | testMupPush(queue) 76 | 77 | test "overflow": 78 | testMupPushOverflow(queue) 79 | 80 | test "wrap": 81 | testMupPushWrap(queue) 82 | 83 | 84 | suite "push(Producer[N, P, T], seq[T])": 85 | setup: 86 | queue.reset() 87 | 88 | test "basic": 89 | testMupPushSeq(queue) 90 | 91 | test "overflow": 92 | testMupPushSeqOverflow(queue) 93 | 94 | test "wrap": 95 | testMupPushSeqWrap(queue) 96 | 97 | 98 | suite "pop(Mupmuc[N, P, C, T])": 99 | setup: 100 | queue.reset() 101 | 102 | test "one": 103 | testMucPopOne(queue) 104 | 105 | test "all": 106 | testMucPopAll(queue) 107 | 108 | test "empty": 109 | testMucPopEmpty(queue) 110 | 111 | test "too many": 112 | testMucPopTooMany(queue) 113 | 114 | test "wrap": 115 | testMucPopWrap(queue) 116 | 117 | 118 | suite "pop(Mupmuc[N, P, C, T], int)": 119 | setup: 120 | queue.reset() 121 | 122 | test "one": 123 | testMucPopCountOne(queue) 124 | 125 | test "all": 126 | testMucPopCountAll(queue) 127 | 128 | test "empty": 129 | testMucPopCountEmpty(queue) 130 | 131 | test "too many": 132 | testMucPopCountTooMany(queue) 133 | 134 | test "wrap": 135 | testMucPopCountWrap(queue) 136 | 137 | 138 | suite "capacity(Mupmuc[N, P, C, T])": 139 | test "basic": 140 | testCapacity(queue) 141 | 142 | 143 | suite "Mupmuc integration": 144 | setup: 145 | queue.reset() 146 | 147 | test "head and tail reset": 148 | testHeadAndTailReset(queue) 149 | 150 | test "wraps": 151 | when ((queue is Mupsic) or (queue is Mupmuc)): 152 | check(queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 153 | else: 154 | check(queue.push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 155 | 156 | var popRes = 157 | when queue is Mupmuc: 158 | queue.getConsumer(0).pop(4) 159 | else: 160 | queue.pop(4) 161 | 162 | check(popRes.isSome) 163 | check(popRes.get == @[1, 2, 3, 4]) 164 | 165 | let pushRes = 166 | when ((queue is Mupsic) or (queue is Mupmuc)): 167 | queue.getProducer(0).push(@[9, 10, 11, 12]) 168 | else: 169 | queue.push(@[9, 10, 11, 12]) 170 | 171 | check(pushRes.isNone) 172 | 173 | queue.checkState( 174 | head = 4, 175 | tail = 12, 176 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 177 | ) 178 | when ((queue is Mupsic) or (queue is Mupmuc)): 179 | queue.checkState( 180 | prevProducerIdx = 0, 181 | producerTails = (@[12, 0, 0, 0]), 182 | ) 183 | 184 | when queue is Mupmuc: 185 | queue.checkState( 186 | prevConsumerIdx = 0, 187 | consumerHeads = (@[4, 0, 0, 0]), 188 | ) 189 | 190 | popRes = 191 | when queue is Mupmuc: 192 | queue.getConsumer(0).pop(4) 193 | else: 194 | queue.pop(4) 195 | check(popRes.isSome) 196 | check(popRes.get == @[5, 6, 7, 8]) 197 | 198 | queue.checkState( 199 | head = 8, 200 | tail = 12, 201 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 202 | ) 203 | 204 | when ((queue is Mupsic) or (queue is Mupmuc)): 205 | queue.checkState( 206 | prevProducerIdx = 0, 207 | producerTails = (@[12, 0, 0, 0]), 208 | ) 209 | 210 | when queue is Mupmuc: 211 | queue.checkState( 212 | prevConsumerIdx = 0, 213 | consumerHeads = (@[8, 0, 0, 0]), 214 | ) 215 | 216 | popRes = 217 | when queue is Mupmuc: 218 | queue.getConsumer(1).pop(4) 219 | else: 220 | queue.pop(4) 221 | check(popRes.isSome) 222 | check(popRes.get == @[9, 10, 11, 12]) 223 | 224 | queue.checkState( 225 | head = 12, 226 | tail = 12, 227 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 228 | ) 229 | when ((queue is Mupsic) or (queue is Mupmuc)): 230 | queue.checkState( 231 | prevProducerIdx = 0, 232 | producerTails = (@[12, 0, 0, 0]), 233 | ) 234 | when queue is Mupmuc: 235 | queue.checkState( 236 | prevConsumerIdx = 1, 237 | consumerHeads = (@[8, 12, 0, 0]), 238 | ) 239 | 240 | 241 | -------------------------------------------------------------------------------- /tests/t_muc.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | 8 | template testMucPopOne*(queue: untyped) = 9 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 10 | 11 | let res = queue.getConsumer(0).pop() 12 | check(res.isSome) 13 | check(res.get == 1) 14 | 15 | queue.checkState( 16 | head = 1, 17 | tail = 8, 18 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 19 | ) 20 | queue.checkState( 21 | prevProducerIdx = 0, 22 | producerTails = (@[8, 0, 0, 0]), 23 | ) 24 | queue.checkState( 25 | prevConsumerIdx = 0, 26 | consumerHeads = (@[1, 0, 0, 0]), 27 | ) 28 | 29 | 30 | template testMucPopAll*(queue: untyped) = 31 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 32 | 33 | var items = newSeq[int]() 34 | for i in 1..8: 35 | let res = queue.getConsumer(0).pop() 36 | check(res.isSome) 37 | items.add(res.get) 38 | 39 | check(items == @[1, 2, 3, 4, 5, 6, 7, 8]) 40 | 41 | queue.checkState( 42 | head = 8, 43 | tail = 8, 44 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 45 | ) 46 | queue.checkState( 47 | prevProducerIdx = 0, 48 | producerTails = (@[8, 0, 0, 0]), 49 | ) 50 | queue.checkState( 51 | prevConsumerIdx = 0, 52 | consumerHeads = (@[8, 0, 0, 0]), 53 | ) 54 | 55 | 56 | template testMucPopEmpty*(queue: untyped) = 57 | check(queue.getConsumer(0).pop().isNone) 58 | 59 | queue.checkState( 60 | head = 0, 61 | tail = 0, 62 | storage = repeat(0, 8), 63 | ) 64 | queue.checkState( 65 | prevProducerIdx = NoProducerIdx, 66 | producerTails = repeat(0, 4), 67 | ) 68 | queue.checkState( 69 | prevConsumerIdx = NoConsumerIdx, 70 | consumerHeads = repeat(0, 4), 71 | ) 72 | 73 | 74 | template testMucPopTooMany*(queue: untyped) = 75 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 76 | 77 | for i in 1..8: 78 | discard queue.getConsumer(0).pop() 79 | 80 | check(queue.getConsumer(0).pop().isNone) 81 | 82 | queue.checkState( 83 | head = 8, 84 | tail = 8, 85 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 86 | ) 87 | queue.checkState( 88 | prevProducerIdx = 0, 89 | producerTails = (@[8, 0, 0, 0]), 90 | ) 91 | queue.checkState( 92 | prevConsumerIdx = 0, 93 | consumerHeads = (@[8, 0, 0, 0]), 94 | ) 95 | 96 | 97 | template testMucPopWrap*(queue: untyped) = 98 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 99 | 100 | for i in 1..4: 101 | discard queue.getConsumer(0).pop() 102 | 103 | discard queue.getProducer(1).push(@[9, 10, 11, 12]) 104 | 105 | var items = newSeq[int]() 106 | for i in 1..8: 107 | let res = queue.getConsumer(0).pop() 108 | check(res.isSome) 109 | items.add(res.get) 110 | 111 | check(items == @[5, 6, 7, 8, 9, 10, 11, 12]) 112 | 113 | queue.checkState( 114 | head = 12, 115 | tail = 12, 116 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 117 | ) 118 | queue.checkState( 119 | prevProducerIdx = 1, 120 | producerTails = (@[8, 12, 0, 0]), 121 | ) 122 | queue.checkState( 123 | prevConsumerIdx = 0, 124 | consumerHeads = (@[12, 0, 0, 0]), 125 | ) 126 | 127 | 128 | template testMucPopCountOne*(queue: untyped) = 129 | check(queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 130 | for i in 1..8: 131 | let popped = queue.getConsumer(0).pop(1) 132 | check(popped.isSome) 133 | check(popped.get() == @[i]) 134 | queue.checkState( 135 | head = 8, 136 | tail = 8, 137 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 138 | ) 139 | queue.checkState( 140 | prevProducerIdx = 0, 141 | producerTails = (@[8, 0, 0, 0]), 142 | ) 143 | queue.checkState( 144 | prevConsumerIdx = 0, 145 | consumerHeads = (@[8, 0, 0, 0]), 146 | ) 147 | 148 | 149 | template testMucPopCountAll*(queue: untyped) = 150 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 151 | let popped = queue.getConsumer(0).pop(8) 152 | check(popped.isSome) 153 | check(popped.get() == @[1, 2, 3, 4, 5, 6, 7, 8]) 154 | queue.checkState( 155 | head = 8, 156 | tail = 8, 157 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 158 | ) 159 | queue.checkState( 160 | prevProducerIdx = 0, 161 | producerTails = (@[8, 0, 0, 0]), 162 | ) 163 | queue.checkState( 164 | prevConsumerIdx = 0, 165 | consumerHeads = (@[8, 0, 0, 0]), 166 | ) 167 | 168 | 169 | template testMucPopCountEmpty*(queue: untyped) = 170 | let popped = queue.getConsumer(0).pop(1) 171 | check(popped.isNone) 172 | queue.checkState( 173 | head = 0, 174 | tail = 0, 175 | storage = repeat(0, 8), 176 | ) 177 | queue.checkState( 178 | prevProducerIdx = NoProducerIdx, 179 | producerTails = repeat(0, 4), 180 | ) 181 | queue.checkState( 182 | prevConsumerIdx = NoConsumerIdx, 183 | consumerHeads = repeat(0, 4), 184 | ) 185 | 186 | 187 | template testMucPopCountTooMany*(queue: untyped) = 188 | check(queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]).isNone) 189 | 190 | queue.checkState( 191 | head = 0, 192 | tail = 8, 193 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 194 | ) 195 | 196 | let popped = queue.getConsumer(0).pop(10) 197 | check(popped.isSome) 198 | check(popped.get() == @[1, 2, 3, 4, 5, 6, 7, 8]) 199 | 200 | queue.checkState( 201 | head = 8, 202 | tail = 8, 203 | storage = (@[1, 2, 3, 4, 5, 6, 7, 8]), 204 | ) 205 | queue.checkState( 206 | prevProducerIdx = 0, 207 | producerTails = (@[8, 0, 0, 0]), 208 | ) 209 | queue.checkState( 210 | prevConsumerIdx = 0, 211 | consumerHeads = (@[8, 0, 0, 0]), 212 | ) 213 | 214 | 215 | template testMucPopCountWrap*(queue: untyped) = 216 | discard queue.getProducer(0).push(@[1, 2, 3, 4, 5, 6, 7, 8]) 217 | 218 | discard queue.getConsumer(0).pop(4) 219 | 220 | discard queue.getProducer(1).push(@[9, 10, 11, 12]) 221 | 222 | let popped = queue.getConsumer(1).pop(8) 223 | check(popped.isSome) 224 | check(popped.get() == @[5, 6, 7, 8, 9, 10, 11, 12]) 225 | 226 | queue.checkState( 227 | head = 12, 228 | tail = 12, 229 | storage = (@[9, 10, 11, 12, 5, 6, 7, 8]), 230 | ) 231 | queue.checkState( 232 | prevProducerIdx = 1, 233 | producerTails = (@[8, 12, 0, 0]), 234 | ) 235 | queue.checkState( 236 | prevConsumerIdx = 1, 237 | consumerHeads = (@[4, 12, 0, 0]), 238 | ) 239 | -------------------------------------------------------------------------------- /tests/t_mup.nim: -------------------------------------------------------------------------------- 1 | # lockfreequeues 2 | # © Copyright 2020 Elijah Shaw-Rutschman 3 | # 4 | # See the file "LICENSE", included in this distribution for details about the 5 | # copyright. 6 | 7 | 8 | template testMupGetProducerAssigns*(queue: untyped) = 9 | let producer = queue.getProducer() 10 | check(producer.idx == 0) 11 | check(queue.producerThreadIds[0].acquire == getThreadId()) 12 | for p in 1..= items.len): 99 | # enough room to push all items, return nothing 100 | result = NoSlice 101 | count = items.len 102 | else: 103 | # not enough room to push all items, return remainder 104 | result = some(avail..items.len - 1) 105 | count = min(avail, N) 106 | 107 | let start = index(tail, N) 108 | var stop = incOrReset(tail, count - 1, N) 109 | stop = index(stop, N) 110 | 111 | if start > stop: 112 | # data may wrap 113 | let pivot = (N-1) - start 114 | self.storage[start..start+pivot] = items[0..pivot] 115 | if stop > 0: 116 | # data wraps 117 | self.storage[0..stop] = items[pivot+1..pivot+1+stop] 118 | else: 119 | # data does not wrap 120 | self.storage[start..stop] = items[0..stop-start] 121 | 122 | let newTail = incOrReset(tail, count, N) 123 | 124 | self.tail.release(newTail) 125 | 126 | 127 | proc pop*[N: static int, T]( 128 | self: var Sipsic[N, T], 129 | ): Option[T] = 130 | ## Pop a single item from the queue. 131 | ## If the queue is empty, `none(T)` is returned. 132 | ## Otherwise an item is popped, `some(T)` is returned. 133 | let head = self.head.acquire 134 | let tail = self.tail.sequential 135 | 136 | if unlikely(empty(head, tail, N)): 137 | return 138 | 139 | let headIndex = index(head, N) 140 | 141 | result = some(self.storage[headIndex]) 142 | 143 | let newHead = incOrReset(head, 1, N) 144 | 145 | self.head.release(newHead) 146 | 147 | 148 | proc pop*[N: static int, T]( 149 | self: var Sipsic[N, T], 150 | count: int, 151 | ): Option[seq[T]] = 152 | ## Pop `count` items from the queue. 153 | ## If the queue is empty, `none(seq[T])` is returned. 154 | ## Otherwise `some(seq[T])` is returned containing at least one item. 155 | let head = self.head.acquire 156 | let tail = self.tail.sequential 157 | 158 | let used = used(head, tail, N) 159 | var actualCount: int 160 | 161 | if likely(used >= count): 162 | # Enough items to fulfill request 163 | actualCount = count 164 | elif used <= 0: 165 | # Queue is empty, return nothing 166 | return none(seq[T]) 167 | else: 168 | # Not enough items to fulfill request 169 | actualCount = min(used, N) 170 | 171 | var res = newSeq[T](actualCount) 172 | let headIndex = index(head, N) 173 | let newHead = incOrReset(head, actualCount, N) 174 | 175 | let newHeadIndex = index(newHead, N) 176 | 177 | if headIndex < newHeadIndex: 178 | # request does not wrap 179 | for i in 0.. 0: 188 | # request wraps 189 | for j in 0..= 0: 83 | result.idx = idx 84 | return 85 | 86 | # getThreadId will be undeclared unless compiled with --threads:on 87 | let threadId = getThreadId() 88 | 89 | # Try to find existing mapping of threadId -> consumerIdx 90 | for idx in 0.. consumerIdx 96 | for idx in 0..= count): 200 | # Enough items to fulfill request 201 | actualCount = count 202 | elif used <= 0: 203 | # Queue is empty, return nothing 204 | return none(seq[T]) 205 | else: 206 | # Not enough items to fulfill request 207 | actualCount = min(used, N) 208 | 209 | newHead = incOrReset(prevHead, actualCount, N) 210 | self.queue.consumerHeads[self.idx].release(newHead) 211 | 212 | if self.queue.prevConsumerIdx.compareExchangeWeak( 213 | prevConsumerIdx, 214 | self.idx, 215 | moRelease, 216 | moAcquire, 217 | ): 218 | break 219 | 220 | let start = index(prevHead, N) 221 | var stop = incOrReset(prevHead, actualCount - 1, N) 222 | stop = index(stop, N) 223 | 224 | var items = newSeq[T](actualCount) 225 | 226 | if start > stop: 227 | # data may wrap 228 | let pivot = (N-1) - start 229 | items[0..pivot] = self.queue.storage[start..start+pivot] 230 | if stop > 0: 231 | # data wraps 232 | items[pivot+1..pivot+1+stop] = self.queue.storage[0..stop] 233 | else: 234 | # data does not wrap 235 | items[0..stop-start] = self.queue.storage[start..stop] 236 | 237 | result = some(items) 238 | 239 | # Wait for prev consumer to update head, then update head 240 | if not isFirstConsumption: 241 | while true: 242 | var expectedHead = prevHead 243 | if self.queue.head.compareExchangeWeak( 244 | expectedHead, 245 | newHead, 246 | moRelease, 247 | moAcquire, 248 | ): 249 | break 250 | 251 | elif isFirstConsumption: 252 | self.queue.head.release(newHead) 253 | 254 | 255 | proc pop*[N, P, C: static int, T]( 256 | self: var Mupmuc[N, P, C, T], 257 | ): bool = 258 | ## Overload of `Sipsic.pop()` that simply raises `InvalidCallDefect`. 259 | ## Pops should happen via `Consumer.pop()`. 260 | raise newException(InvalidCallDefect, "Use Consumer.pop()") 261 | 262 | 263 | proc pop*[N, P, C: static int, T]( 264 | self: var Mupmuc[N, P, C, T], 265 | count: int, 266 | ): Option[seq[T]] = 267 | ## Overload of `Sipsic.pop()` that simply raises `InvalidCallDefect`. 268 | ## Pops should happen via `Consumer.pop()`. 269 | raise newException(InvalidCallDefect, "Use Consumer.pop()") 270 | 271 | 272 | proc consumerCount*[N, P, C: static int, T]( 273 | self: var Mupmuc[N, P, C, T], 274 | ): int 275 | {.inline.} = 276 | ## Returns the queue's number of consumers (`C`). 277 | result = C 278 | 279 | 280 | when defined(testing): 281 | import sugar 282 | from unittest import check 283 | 284 | proc reset*[N, P, C: static int, T]( 285 | self: var Mupmuc[N, P, C, T] 286 | ) = 287 | ## Resets the queue to its default state. 288 | self.clear() 289 | 290 | proc checkState*[N, P, C: static int, T]( 291 | self: var Mupmuc[N, P, C, T], 292 | prevConsumerIdx: int, 293 | consumerHeads: seq[int], 294 | ) = 295 | check(self.prevConsumerIdx.sequential == prevConsumerIdx) 296 | let heads = collect(newSeq): 297 | for c in 0..= 0: 80 | result.idx = idx 81 | return 82 | 83 | # getThreadId will be undeclared unless compiled with --threads:on 84 | let threadId = getThreadId() 85 | 86 | # Try to find existing mapping of threadId -> producerIdx 87 | for idx in 0.. producerIdx 93 | for idx in 0..= items.len): 202 | # enough room to push all items 203 | count = items.len 204 | else: 205 | if avail <= 0: 206 | # Queue is full, return 207 | return some(0..items.len - 1) 208 | else: 209 | # not enough room to push all items 210 | count = avail 211 | 212 | newTail = incOrReset(prevTail, count, N) 213 | #assert validateHeadAndTail(head, newTail, N) 214 | self.queue.producerTails[self.idx].release(newTail) 215 | 216 | if self.queue.prevProducerIdx.compareExchangeWeak( 217 | prevProducerIdx, 218 | self.idx, 219 | moRelease, 220 | moAcquire, 221 | ): 222 | break 223 | 224 | if count < items.len: 225 | # give back remainder 226 | result = some(avail..items.len - 1) 227 | else: 228 | result = NoSlice 229 | 230 | let start = index(prevTail, N) 231 | var stop = incOrReset(prevTail, count - 1, N) 232 | stop = index(stop, N) 233 | 234 | if start > stop: 235 | # data may wrap 236 | let pivot = (N-1) - start 237 | self.queue.storage[start..start+pivot] = items[0..pivot] 238 | if stop > 0: 239 | # data wraps 240 | self.queue.storage[0..stop] = items[pivot+1..pivot+1+stop] 241 | else: 242 | # data does not wrap 243 | self.queue.storage[start..stop] = items[0..stop-start] 244 | 245 | # Wait for prev producer to update tail, then update tail 246 | if not isFirstProduction: 247 | while true: 248 | var expectedTail = prevTail 249 | if self.queue.tail.compareExchangeWeak( 250 | expectedTail, 251 | newTail, 252 | moRelease, 253 | moAcquire, 254 | ): 255 | break 256 | 257 | elif isFirstProduction: 258 | self.queue.tail.release(newTail) 259 | 260 | 261 | proc push*[N, P: static int, T]( 262 | self: var Mupsic[N, P, T], 263 | item: T, 264 | ): bool = 265 | ## Overload of `Sipsic.push()` that simply raises `InvalidCallDefect`. 266 | ## Pushes should happen via `Producer.push()`. 267 | raise newException(InvalidCallDefect, "Use Producer.push()") 268 | 269 | 270 | proc push*[N, P: static int, T]( 271 | self: var Mupsic[N, P, T], 272 | items: openArray[T], 273 | ): Option[HSlice[int, int]] = 274 | ## Overload of `Sipsic.push()` that simply raises `InvalidCallDefect`. 275 | ## Pushes should happen via `Producer.push()`. 276 | raise newException(InvalidCallDefect, "Use Producer.push()") 277 | 278 | 279 | proc capacity*[N, P: static int, T]( 280 | self: var Mupsic[N, P, T], 281 | ): int 282 | {.inline.} = 283 | ## Returns the queue's storage capacity (`N`). 284 | result = N 285 | 286 | 287 | proc producerCount*[N, P: static int, T]( 288 | self: var Mupsic[N, P, T], 289 | ): int 290 | {.inline.} = 291 | ## Returns the queue's number of producers (`P`). 292 | result = P 293 | 294 | when defined(testing): 295 | import sugar 296 | from unittest import check 297 | 298 | proc reset*[N, P: static int, T]( 299 | self: var Mupsic[N, P, T] 300 | ) = 301 | ## Resets the queue to its default state. 302 | ## Probably only useful in single-threaded unit tests. 303 | self.clear() 304 | 305 | proc checkState*[N, P: static int, T]( 306 | self: var Mupsic[N, P, T], 307 | prevProducerIdx: int, 308 | producerTails: seq[int], 309 | ) = 310 | let tails = collect(newSeq): 311 | for p in 0..