-
-
Notifications
You must be signed in to change notification settings - Fork 67
Fix threadpool futexes_linux wait/wake syscall number #625
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
Open
nitely
wants to merge
3
commits into
mratsim:master
Choose a base branch
from
nitely:fix_futexes_linux
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+253
−2
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| # taskpools | ||
| # Copyright (c) 2021-2026 Status Research & Development GmbH | ||
| # Licensed and distributed under either of | ||
| # * 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). | ||
| # at your option. This file may not be copied, modified, or distributed except according to those terms. | ||
|
|
||
| {.push raises: [], gcsafe.} | ||
|
|
||
| import | ||
| std/[atomics, os, unittest], | ||
| constantine/threadpool/crossthread/backoff | ||
|
|
||
| # Tests for the EventCount (the lock-free condition-variable equivalent in | ||
| # backoff.nim). The parking protocol is: | ||
| # | ||
| # while not condition: | ||
| # ticket = ec.sleepy() # announce intent to sleep (pre-wait) | ||
| # if condition: | ||
| # ec.cancelSleep() # bail out, undo the pre-wait | ||
| # break | ||
| # ec.sleep(ticket) # commit and park until wake() bumps the epoch | ||
| # | ||
| # A wake() between sleepy() and sleep() bumps the epoch, invalidating the | ||
| # ticket so sleep() returns without parking. That is how lost wakeups are | ||
| # avoided, and we test it directly (single-threaded, deterministic). | ||
|
|
||
| const | ||
| observeWindowMs = 100 | ||
| maxParkedSpins = 10_000 | ||
|
|
||
| type | ||
| ParkState = object | ||
| ec: EventCount | ||
| reached: Atomic[bool] | ||
| condition: Atomic[bool] | ||
| spins: Atomic[int] # outer-loop iterations while unsignalled | ||
| woke: Atomic[bool] | ||
|
|
||
| WakeAllState = object | ||
| ec: EventCount | ||
| condition: Atomic[bool] | ||
| woke: Atomic[int] | ||
|
|
||
| proc spinUntilBool(a: var Atomic[bool], expected: bool) = | ||
| while a.load(moAcquire) != expected: | ||
| discard | ||
|
|
||
| proc parker(s: ptr ParkState) {.thread.} = | ||
| s.reached.store(true, moRelease) | ||
| while not s.condition.load(moAcquire): | ||
| let ticket = s.ec.sleepy() | ||
| if s.condition.load(moAcquire): | ||
| s.ec.cancelSleep() | ||
| break | ||
| s.ec.sleep(ticket) | ||
| discard s.spins.fetchAdd(1, moRelaxed) | ||
| s.woke.store(true, moRelease) | ||
|
|
||
| proc multiParker(s: ptr WakeAllState) {.thread.} = | ||
| while not s.condition.load(moAcquire): | ||
| let ticket = s.ec.sleepy() | ||
| if s.condition.load(moAcquire): | ||
| s.ec.cancelSleep() | ||
| break | ||
| s.ec.sleep(ticket) | ||
| discard s.woke.fetchAdd(1, moRelease) | ||
|
|
||
| suite "EventCount": | ||
| test "sleepy() then cancelSleep() leaves no waiters": | ||
| var ec: EventCount | ||
| ec.initialize() | ||
|
|
||
| check ec.getNumWaiters().preSleep == 0 | ||
| check ec.getNumWaiters().committedSleep == 0 | ||
| discard ec.sleepy() | ||
| check ec.getNumWaiters().preSleep == 1 | ||
| check ec.getNumWaiters().committedSleep == 0 | ||
| ec.cancelSleep() | ||
| check ec.getNumWaiters().preSleep == 0 | ||
| check ec.getNumWaiters().committedSleep == 0 | ||
|
|
||
| test "sleep() does not park when the ticket is stale": | ||
| # wake() between sleepy() and sleep() bumps the epoch; sleep() must observe | ||
| # the change and return immediately instead of blocking forever. | ||
| var ec: EventCount | ||
| ec.initialize() | ||
|
|
||
| let ticket = ec.sleepy() | ||
| ec.wake() # invalidates the ticket's epoch | ||
| ec.sleep(ticket) # would hang if it parked on the stale epoch | ||
|
|
||
| check ec.getNumWaiters().preSleep == 0 | ||
| check ec.getNumWaiters().committedSleep == 0 | ||
|
|
||
| test "sleep() parks the thread until wake()": | ||
| var s: ParkState | ||
| s.ec.initialize() | ||
|
|
||
| var thr: Thread[ptr ParkState] | ||
| createThread(thr, parker, addr s) | ||
|
|
||
| # This is racy but if the futex does not | ||
| # wait and return immediately, it should register | ||
| # more than maxParkedSpins in observeWindowMs. | ||
| spinUntilBool(s.reached, true) | ||
| sleep(observeWindowMs) | ||
| check s.spins.load(moAcquire) < maxParkedSpins | ||
| check not s.woke.load(moAcquire) | ||
|
|
||
| s.condition.store(true, moRelease) | ||
| s.ec.wake() | ||
| joinThread(thr) | ||
|
|
||
| check s.woke.load(moAcquire) | ||
| check s.ec.getNumWaiters().preSleep == 0 | ||
| check s.ec.getNumWaiters().committedSleep == 0 | ||
|
|
||
| test "wakeAll() releases every parked waiter": | ||
| const numWaiters = 4 | ||
| var s: WakeAllState | ||
| s.ec.initialize() | ||
|
|
||
| var threads: array[numWaiters, Thread[ptr WakeAllState]] | ||
| for t in mitems(threads): | ||
| createThread(t, multiParker, addr s) | ||
|
|
||
| while s.ec.getNumWaiters().committedSleep != numWaiters: | ||
| discard | ||
|
|
||
| s.condition.store(true, moRelease) | ||
| s.ec.wakeAll() | ||
| joinThreads(threads) | ||
|
|
||
| check s.woke.load(moAcquire) == numWaiters | ||
| check s.ec.getNumWaiters().preSleep == 0 | ||
| check s.ec.getNumWaiters().committedSleep == 0 | ||
|
|
||
| test "supports more than 256 committed waiters": | ||
| const numWaiters = 257 | ||
| var s: WakeAllState | ||
| s.ec.initialize() | ||
|
|
||
| var threads = newSeq[Thread[ptr WakeAllState]](numWaiters) | ||
| for t in mitems(threads): | ||
| createThread(t, multiParker, addr s) | ||
|
|
||
| while s.ec.getNumWaiters().committedSleep != numWaiters: | ||
| discard | ||
|
|
||
| s.condition.store(true, moRelease) | ||
| s.ec.wakeAll() | ||
| joinThreads(threads) | ||
|
|
||
| check s.woke.load(moAcquire) == numWaiters | ||
| check s.ec.getNumWaiters().preSleep == 0 | ||
| check s.ec.getNumWaiters().committedSleep == 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # taskpools | ||
| # Copyright (c) 2021-2026 Status Research & Development GmbH | ||
| # Licensed and distributed under either of | ||
| # * 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). | ||
| # at your option. This file may not be copied, modified, or distributed except according to those terms. | ||
|
|
||
| {.push raises: [], gcsafe.} | ||
|
|
||
| import | ||
| std/[atomics, os, unittest], | ||
| constantine/threadpool/primitives/futexes | ||
|
|
||
| const | ||
| observeWindowMs = 100 | ||
| maxParkedSpins = 10_000 | ||
|
|
||
| type | ||
| WaitState = object | ||
| futex: Futex | ||
| reachedWait: Atomic[bool] # waiter has entered its wait loop | ||
| spins: Atomic[int] # times wait() returned while still unsignalled | ||
| woke: Atomic[bool] # waiter observed the signal and left the loop | ||
|
|
||
| WakeAllState = object | ||
| futex: Futex | ||
| ready: Atomic[int] # count of waiters that entered their wait loop | ||
| woke: Atomic[int] # count of waiters released after the signal | ||
|
|
||
| proc spinUntil[T](a: var Atomic[T], expected: T) = | ||
| while a.load(moAcquire) != expected: | ||
| discard | ||
|
|
||
| proc waiter(s: ptr WaitState) {.thread.} = | ||
| s.reachedWait.store(true, moRelease) | ||
| while s.futex.load(moAcquire) == 0: | ||
| s.futex.wait(0) | ||
| discard s.spins.fetchAdd(1, moRelaxed) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| s.woke.store(true, moRelease) | ||
|
|
||
| proc wakeAllWaiter(s: ptr WakeAllState) {.thread.} = | ||
| discard s.ready.fetchAdd(1, moRelease) | ||
| while s.futex.load(moAcquire) == 0: | ||
| s.futex.wait(0) | ||
| discard s.woke.fetchAdd(1, moRelease) | ||
|
|
||
| suite "Futex": | ||
| test "wait() parks the thread until wake()": | ||
| var s: WaitState | ||
| s.futex.initialize() | ||
|
|
||
| var thr: Thread[ptr WaitState] | ||
| createThread(thr, waiter, addr s) | ||
|
|
||
| # This is racy but if the futex does not | ||
| # wait and return immediately, it should register | ||
| # more than maxParkedSpins in observeWindowMs. | ||
| spinUntil(s.reachedWait, true) | ||
| sleep(observeWindowMs) | ||
| check s.spins.load(moAcquire) < maxParkedSpins | ||
| check not s.woke.load(moAcquire) | ||
|
|
||
| s.futex.store(1, moRelease) | ||
| s.futex.wake() | ||
| joinThread(thr) | ||
| check s.woke.load(moAcquire) | ||
|
|
||
| s.futex.teardown() | ||
|
|
||
| test "wait() returns immediately when value != expected": | ||
| var s: WaitState | ||
| s.futex.initialize() | ||
| s.futex.store(1, moRelease) | ||
|
|
||
| var thr: Thread[ptr WaitState] | ||
| createThread(thr, waiter, addr s) | ||
| joinThread(thr) | ||
|
|
||
| check s.woke.load(moAcquire) | ||
|
nitely marked this conversation as resolved.
Outdated
|
||
| s.futex.teardown() | ||
|
|
||
| test "wakeAll() releases every parked waiter": | ||
| const numWaiters = 4 | ||
| var s: WakeAllState | ||
| s.futex.initialize() | ||
|
|
||
| var threads: array[numWaiters, Thread[ptr WakeAllState]] | ||
| for t in mitems(threads): | ||
| createThread(t, wakeAllWaiter, addr s) | ||
|
|
||
| spinUntil(s.ready, numWaiters) | ||
| s.futex.store(1, moRelease) | ||
| s.futex.wakeAll() | ||
| joinThreads(threads) | ||
|
|
||
| check s.woke.load(moAcquire) == numWaiters | ||
| s.futex.teardown() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.