Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

closes #8895 add std/once #16192

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions lib/std/once.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
when compileOption("threads"):
import std/[atomics, locks]


type
Once* = object ## A object that ensures a block of code is executed once.
when compileOption("threads"):
finished: Atomic[bool]
lock: Lock
else:
finished: bool

proc `=destroy`*(once: var Once) {.inline.} =
when compileOption("threads"):
deinitLock(once.lock)

proc init*(once: var Once) =
## Initializes a `Once` object.
when compileOption("threads"):
once.finished.store(false, moRelaxed)
initLock(once.lock)

template once*(cond: var Once, body: untyped) =
## Executes a block of code only once (the first time the block is reached).
## It is thread-safe.
runnableExamples("--gc:orc --threads:on"):
var block1: Once
var count = 0
init(block1)

for i in 1 .. 10:
once(block1):
inc count

# only the first `block1` is executed
once(block1):
count = 888

assert count == 1

when compileOption("threads"):
if not cond.finished.load(moAcquire):
withLock cond.lock:
if not cond.finished.load(moRelaxed):
try:
body
finally:
cond.finished.store(true, moRelease)
else:
if not cond.finished:
try:
body
finally:
cond.finished = true


## The code block is executed only once among threads.
runnableExamples("--gc:orc --threads:on"):
block:
var thr: array[0..4, Thread[void]]
var block1: Once
var count = 0
init(block1)
proc threadFunc() {.thread.} =
for i in 1 .. 10:
once(block1):
inc count
for i in 0..high(thr):
createThread(thr[i], threadFunc)
joinThreads(thr)
assert count == 1

## The code blocks is executed per thread.
runnableExamples("--gc:orc --threads:on"):
block:
var thr: array[0..4, Thread[void]]
var count = 0
proc threadFunc() {.thread.} =
var block1: Once
init(block1)
for i in 1 .. 10:
once(block1):
inc count
for i in 0..high(thr):
createThread(thr[i], threadFunc)
joinThreads(thr)
assert count == thr.len
34 changes: 34 additions & 0 deletions tests/stdlib/tonce.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
discard """
Copy link
Member

@timotheecour timotheecour Dec 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

js needs to be supported because system.once already works in js (not considering edge case of webworkers)

(and leave a TODO for supporting VM because VM already doesn't work with system.once)

when true:
  var count = 0
  var countCT {.compileTime.} = 0
  proc fn(n: int) =
    once:
      when nimvm:
        countCT.inc
      else:
        count.inc
      echo (n,)
    if n > 1:
      fn(n-1)
  proc main() =
    fn(5)
    fn(4)
    when nimvm:
      echo countCT
      # doAssert countCT == 1 # fails
    else:
      doAssert count == 1
  static: main()
  main()

matrix: "--gc:orc --threads:on"
"""
import std/once


block:
var thr: array[0..4, Thread[void]]
var block1: Once
var count = 0
initOnce(block1)
proc threadFunc() {.thread.} =
for i in 1 .. 10:
once(block1):
inc count
for i in 0..high(thr):
createThread(thr[i], threadFunc)
joinThreads(thr)
doAssert count == 1


block:
var thr: array[0..4, Thread[void]]
var count = 0
proc threadFunc() {.thread.} =
var block1: Once
initOnce(block1)
for i in 1 .. 10:
once(block1):
inc count
for i in 0..high(thr):
createThread(thr[i], threadFunc)
joinThreads(thr)
doAssert count == 5