Merge branch 'stable' into gc

This commit is contained in:
Jacek Sieka 2024-01-02 14:20:42 +01:00 committed by GitHub
commit a42345a469
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 47 additions and 59 deletions

View File

@ -1,5 +1,5 @@
# Nim-Taskpools # taskpools
# Copyright (c) 2021 Status Research & Development GmbH # Copyright (c) 2021- Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).

View File

@ -1,7 +1,7 @@
mode = ScriptMode.Verbose mode = ScriptMode.Verbose
packageName = "taskpools" packageName = "taskpools"
version = "0.0.4" version = "0.0.5"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "lightweight, energy-efficient, easily auditable threadpool" description = "lightweight, energy-efficient, easily auditable threadpool"
license = "MIT" license = "MIT"

View File

@ -1,4 +1,4 @@
# Nim-Taskpools # taskpools
# Copyright (c) 2021 Status Research & Development GmbH # Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).

View File

@ -1,5 +1,6 @@
# Weave # taskpools
# Copyright (c) 2019 Mamy André-Ratsimbazafy # Copyright (c) 2019 Mamy André-Ratsimbazafy
# Copyright (c) 2021- Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
@ -8,12 +9,11 @@
{.push raises: [].} {.push raises: [].}
import import
std/atomics, std/[atomics, typetraits]
./instrumentation/[contracts, loggers]
type type
ChannelSPSCSingle* = object ChannelSPSCSingle*[T] = object
## A type-erased SPSC channel. ## A single-value SPSC channel
## ##
## Wait-free bounded single-producer single-consumer channel ## Wait-free bounded single-producer single-consumer channel
## that can only buffer a single item ## that can only buffer a single item
@ -27,22 +27,13 @@ type
## ##
## The channel should be the last field of an object if used in an intrusive manner ## The channel should be the last field of an object if used in an intrusive manner
full{.align: 64.}: Atomic[bool] full{.align: 64.}: Atomic[bool]
itemSize*: uint8 value*: T
buffer*{.align: 8.}: UncheckedArray[byte]
proc `=`( proc `=copy`[T](
dest: var ChannelSPSCSingle, dest: var ChannelSPSCSingle[T],
source: ChannelSPSCSingle source: ChannelSPSCSingle[T]
) {.error: "A channel cannot be copied".} ) {.error: "A channel cannot be copied".}
proc initialize*(chan: var ChannelSPSCSingle, itemsize: SomeInteger) {.inline.} =
## If ChannelSPSCSingle is used intrusive another data structure
## be aware that it should be the last part due to ending by UncheckedArray
preCondition: itemsize.int in 0 .. int high(uint8)
chan.itemSize = uint8 itemsize
chan.full.store(false, moRelaxed)
func isEmpty*(chan: var ChannelSPSCSingle): bool {.inline.} = func isEmpty*(chan: var ChannelSPSCSingle): bool {.inline.} =
not chan.full.load(moAcquire) not chan.full.load(moAcquire)
@ -51,50 +42,46 @@ func tryRecv*[T](chan: var ChannelSPSCSingle, dst: var T): bool {.inline.} =
## Returns true if successful (channel was not empty) ## Returns true if successful (channel was not empty)
## ##
## ⚠ Use only in the consumer thread that reads from the channel. ## ⚠ Use only in the consumer thread that reads from the channel.
preCondition: (sizeof(T) == chan.itemSize.int) or static: doAssert supportsCopyMem(T), "Channel is not garbage-collection-safe"
# Support dummy object
(sizeof(T) == 0 and chan.itemSize == 1)
let full = chan.full.load(moAcquire) case chan.full.load(moAcquire)
if not full: of true:
return false dst = move(chan.value)
dst = cast[ptr T](chan.buffer.addr)[]
chan.full.store(false, moRelease) chan.full.store(false, moRelease)
return true true
of false:
false
func trySend*[T](chan: var ChannelSPSCSingle, src: sink T): bool {.inline.} = func trySend*[T](chan: var ChannelSPSCSingle, src: sink T): bool {.inline.} =
## Try sending an item into the channel ## Try sending an item into the channel
## Reurns true if successful (channel was empty) ## Reurns true if successful (channel was empty)
## ##
## ⚠ Use only in the producer thread that writes from the channel. ## ⚠ Use only in the producer thread that writes from the channel.
preCondition: (sizeof(T) == chan.itemSize.int) or static: doAssert supportsCopyMem(T), "Channel is not garbage-collection-safe"
# Support dummy object
(sizeof(T) == 0 and chan.itemSize == 1)
let full = chan.full.load(moAcquire) case chan.full.load(moAcquire)
if full: of true:
return false false
cast[ptr T](chan.buffer.addr)[] = src of false:
chan.value = move(src)
chan.full.store(true, moRelease) chan.full.store(true, moRelease)
return true true
{.pop.} # raises: [] {.pop.} # raises: []
# Sanity checks # Sanity checks
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
when isMainModule: when isMainModule:
import system/ansi_c
when not compileOption("threads"): when not compileOption("threads"):
{.error: "This requires --threads:on compilation flag".} {.error: "This requires --threads:on compilation flag".}
template sendLoop[T](chan: var ChannelSPSCSingle, template sendLoop[T](chan: var ChannelSPSCSingle[T],
data: sink T, data: sink T,
body: untyped): untyped = body: untyped): untyped =
while not chan.trySend(data): while not chan.trySend(data):
body body
template recvLoop[T](chan: var ChannelSPSCSingle, template recvLoop[T](chan: var ChannelSPSCSingle[T],
data: var T, data: var T,
body: untyped): untyped = body: untyped): untyped =
while not chan.tryRecv(data): while not chan.tryRecv(data):
@ -103,7 +90,7 @@ when isMainModule:
type type
ThreadArgs = object ThreadArgs = object
ID: WorkerKind ID: WorkerKind
chan: ptr ChannelSPSCSingle chan: ptr ChannelSPSCSingle[int]
WorkerKind = enum WorkerKind = enum
Sender Sender
@ -144,13 +131,15 @@ when isMainModule:
discard discard
echo "Sender sent: ", val echo "Sender sent: ", val
import primitives/allocs
proc main() = proc main() =
echo "Testing if 2 threads can send data" echo "Testing if 2 threads can send data"
echo "-----------------------------------" echo "-----------------------------------"
var threads: array[2, Thread[ThreadArgs]] var threads: array[2, Thread[ThreadArgs]]
var chan = cast[ptr ChannelSPSCSingle](c_calloc(1, csize_t sizeof(ChannelSPSCSingle))) var chan = tp_allocAligned(
chan[].initialize(itemSize = sizeof(int)) ChannelSPSCSingle[int], sizeof(ChannelSPSCSingle[int]), 64)
zeroMem(chan, sizeof(ChannelSPSCSingle[int]))
createThread(threads[0], thread_func, ThreadArgs(ID: Receiver, chan: chan)) createThread(threads[0], thread_func, ThreadArgs(ID: Receiver, chan: chan))
createThread(threads[1], thread_func, ThreadArgs(ID: Sender, chan: chan)) createThread(threads[1], thread_func, ThreadArgs(ID: Sender, chan: chan))
@ -158,7 +147,7 @@ when isMainModule:
joinThread(threads[0]) joinThread(threads[0])
joinThread(threads[1]) joinThread(threads[1])
c_free(chan) tp_freeAligned(chan)
echo "-----------------------------------" echo "-----------------------------------"
echo "Success" echo "Success"

View File

@ -1,4 +1,4 @@
# Nim-Taskpools # taskpools
# Copyright (c) 2021 Status Research & Development GmbH # Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).

View File

@ -1,4 +1,4 @@
# Nim-Taskpools # taskpools
# Copyright (c) 2021-2023 Status Research & Development GmbH # Copyright (c) 2021-2023 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).

View File

@ -1,5 +1,6 @@
# Weave # taskpools
# Copyright (c) 2019 Mamy André-Ratsimbazafy # Copyright (c) 2019 Mamy André-Ratsimbazafy
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
@ -12,8 +13,6 @@ import
./channels_spsc_single, ./channels_spsc_single,
./primitives/allocs ./primitives/allocs
{.push gcsafe.}
type type
Flowvar*[T] = object Flowvar*[T] = object
## A Flowvar is a placeholder for a future result that may be computed in parallel ## A Flowvar is a placeholder for a future result that may be computed in parallel
@ -22,19 +21,19 @@ type
# instead of having an extra atomic bool # instead of having an extra atomic bool
# They also use type-erasure to avoid having duplicate code # They also use type-erasure to avoid having duplicate code
# due to generic monomorphization. # due to generic monomorphization.
chan: ptr ChannelSPSCSingle chan: ptr ChannelSPSCSingle[T]
# proc `=copy`*[T](dst: var Flowvar[T], src: Flowvar[T]) {.error: "Futures/Flowvars cannot be copied".} # proc `=copy`*[T](dst: var Flowvar[T], src: Flowvar[T]) {.error: "Futures/Flowvars cannot be copied".}
# #
# Unfortunately we cannot prevent this easily as internally # Unfortunately we cannot prevent this easily as internally
# we need a copy: # we need a copy:
# - nim-taskpools level when doing toTask(fnCall(args, fut)) and then returning fut. (Can be worked around with copyMem) # - taskpools level when doing toTask(fnCall(args, fut)) and then returning fut. (Can be worked around with copyMem)
# - in std/tasks (need upstream workaround) # - in std/tasks (need upstream workaround)
proc newFlowVar*(T: typedesc): Flowvar[T] {.inline.} = proc newFlowVar*(T: typedesc): Flowvar[T] {.inline.} =
let size = 2 + sizeof(T) # full flag + item size + buffer result.chan = tp_allocAligned(
result.chan = tp_allocAligned(ChannelSPSCSingle, size, alignment = 64) ChannelSPSCSingle[T], sizeof(ChannelSPSCSingle[T]), alignment = 64)
result.chan[].initialize(sizeof(T)) zeroMem(result.chan, sizeof(ChannelSPSCSingle[T]))
proc cleanup(fv: Flowvar) {.inline.} = proc cleanup(fv: Flowvar) {.inline.} =
# TODO: Nim v1.4+ can use "sink Flowvar" # TODO: Nim v1.4+ can use "sink Flowvar"

View File

@ -1,4 +1,4 @@
# Nim-Taskpools # taskpools
# Copyright (c) 2021 Status Research & Development GmbH # Copyright (c) 2021 Status Research & Development GmbH
# Licensed and distributed under either of # Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). # * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).