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

Fixes for typedthreads data race issues #24612

Open
wants to merge 7 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
9 changes: 5 additions & 4 deletions lib/std/private/threadtypes.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
include system/inclrtl
import std/atomics

const hasSharedHeap* = defined(boehmgc) or defined(gogc) # don't share heaps; every thread has its own

Expand Down Expand Up @@ -163,13 +164,13 @@ const hasAllocStack* = defined(zephyr) # maybe freertos too?

type
Thread*[TArg] = object
core*: PGcThread
core*: Atomic[PGcThread]
sys*: SysThread
when TArg is void:
dataFn*: proc () {.nimcall, gcsafe.}
dataFn*: Atomic[proc () {.nimcall, gcsafe.}]
else:
dataFn*: proc (m: TArg) {.nimcall, gcsafe.}
data*: TArg
dataFn*: Atomic[proc (m: TArg) {.nimcall, gcsafe.}]
data*: Atomic[TArg]
when hasAllocStack:
rawStack*: pointer

Expand Down
33 changes: 18 additions & 15 deletions lib/std/typedthreads.nim
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ deinitLock(l)
```
]##


import std/atomics
import std/private/[threadtypes]
export Thread

Expand Down Expand Up @@ -149,9 +149,9 @@ else:
nimThreadProcWrapperBody(closure)
{.pop.}

proc running*[TArg](t: Thread[TArg]): bool {.inline.} =
proc running*[TArg](t: var Thread[TArg]): bool {.inline.} =
Copy link
Contributor

Choose a reason for hiding this comment

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

the var here is a breaking change, why is it needed?

Copy link
Author

Choose a reason for hiding this comment

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

please ignore. If I manage to fix other issues that arise, this change will be removed with the next commit.

Copy link
Author

Choose a reason for hiding this comment

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

the var here is a breaking change, why is it needed?

nim c --styleCheck:usages --styleCheck:error --verbosity:0 --hints:off --skipParentCfg --skipUserCfg --outdir:build '--nimcache:build/nimcache/$projectName' -d:metricsTest -d:metrics --threads:on -d:nimTypeNames --mm:refc -r tests/chronos_server_tests
/Users/runner/work/Nim/Nim/pkgstemp/metrics/metrics/chronos_httpserver.nim(400, 6) template/generic instantiation of async from here
/Users/runner/work/Nim/Nim/pkgstemp/metrics/metrics/chronos_httpserver.nim(403, 26) template/generic instantiation of running from here
/Users/runner/work/Nim/Nim/lib/std/typedthreads.nim(155, 22) Error: type mismatch
Expression: load(t.dataFn, moAcquireRelease)
[1] t.dataFn: Atomic[proc (m: MetricsServerData){.nimcall, gcsafe.}]
[2] moAcquireRelease: MemoryOrder

Expected one of (first mismatch at [position]):
[1] proc load[T: Trivial](location: var Atomic[T];
order: MemoryOrder = moSequentiallyConsistent): T
[1] proc load[T: not Trivial](location: var Atomic[T];
order: MemoryOrder = moSequentiallyConsistent): T
expression 't.dataFn' is immutable, not 'var'

## Returns true if `t` is running.
result = t.dataFn != nil
result = t.dataFn.load() != nil

proc handle*[TArg](t: Thread[TArg]): SysThread {.inline.} =
## Returns the thread handle of `t`.
Expand Down Expand Up @@ -202,7 +202,7 @@ when false:
else:
discard pthread_cancel(t.sys)
when declared(registerThread): unregisterThread(addr(t))
t.dataFn = nil
t.dataFn.store nil
## if thread `t` already exited, `t.core` will be `null`.
if not isNil(t.core):
deallocThreadStorage(t.core)
Expand All @@ -217,10 +217,10 @@ when hostOS == "windows":
## Entry point is the proc `tp`.
## `param` is passed to `tp`. `TArg` can be `void` if you
## don't need to pass any data to the thread.
t.core = cast[PGcThread](allocThreadStorage(sizeof(GcThread)))
t.core.store cast[PGcThread](allocThreadStorage(sizeof(GcThread)))

when TArg isnot void: t.data = param
t.dataFn = tp
when TArg isnot void: t.data.store param
t.dataFn.store tp
Copy link
Contributor

Choose a reason for hiding this comment

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

these loads/stores can use relaxed memory ordering

Copy link
Author

Choose a reason for hiding this comment

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

I set moAcquireRelease, and if I'm right, this will work just like withLock

when hasSharedHeap: t.core.stackSize = ThreadStackSize
var dummyThreadId: int32 = 0'i32
t.sys = createThread(nil, ThreadStackSize, threadProcWrapper[TArg],
Expand All @@ -242,10 +242,10 @@ elif defined(genode):
proc createThread*[TArg](t: var Thread[TArg],
tp: proc (arg: TArg) {.thread, nimcall.},
param: TArg) =
t.core = cast[PGcThread](allocThreadStorage(sizeof(GcThread)))
t.core.store cast[PGcThread](allocThreadStorage(sizeof(GcThread)))

when TArg isnot void: t.data = param
t.dataFn = tp
when TArg isnot void: t.data.store param
t.dataFn.store tp
when hasSharedHeap: t.stackSize = ThreadStackSize
t.sys.initThread(
runtimeEnv,
Expand All @@ -266,11 +266,14 @@ else:
## Entry point is the proc `tp`. `param` is passed to `tp`.
## `TArg` can be `void` if you
## don't need to pass any data to the thread.
t.core = cast[PGcThread](allocThreadStorage(sizeof(GcThread)))

when TArg isnot void: t.data = param
t.dataFn = tp
when hasSharedHeap: t.core.stackSize = ThreadStackSize
t.core.store cast[PGcThread](allocThreadStorage(sizeof(GcThread)))

when TArg isnot void: t.data.store param
t.dataFn.store tp
when hasSharedHeap:
var stackSize: PGcThread = t.core.load()
stacksize.stackSize = ThreadStackSize
t.core.store stacksize
var a {.noinit.}: Pthread_attr
doAssert pthread_attr_init(a) == 0
when hasAllocStack:
Expand Down
30 changes: 17 additions & 13 deletions lib/system/threadimpl.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import std/atomics

var
nimThreadDestructionHandlers* {.rtlThreadVar.}: seq[proc () {.closure, gcsafe, raises: [].}]
when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcRegions):
Expand Down Expand Up @@ -50,10 +52,11 @@ when defined(boehmgc):
boehmGC_register_my_thread(sb)
try:
let thrd = cast[ptr Thread[TArg]](thrd)
let dataFn = thrd.dataFn.load()
when TArg is void:
thrd.dataFn()
dataFn()
else:
thrd.dataFn(thrd.data)
dataFn(thrd.data.load())
except:
threadTrouble()
finally:
Expand All @@ -62,15 +65,16 @@ when defined(boehmgc):
else:
proc threadProcWrapDispatch[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =
try:
let dataFn = thrd.dataFn.load()
when TArg is void:
thrd.dataFn()
dataFn()
else:
when defined(nimV2):
thrd.dataFn(thrd.data)
dataFn(thrd.data.load())
else:
var x: TArg = default(TArg)
deepCopy(x, thrd.data)
thrd.dataFn(x)
deepCopy(x, thrd.data.load())
dataFn(x)
except:
threadTrouble()
finally:
Expand All @@ -96,16 +100,16 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =

template nimThreadProcWrapperBody*(closure: untyped): untyped =
var thrd = cast[ptr Thread[TArg]](closure)
var core = thrd.core
when declared(globalsSlot): threadVarSetValue(globalsSlot, thrd.core)
var core = thrd.core.load()
when declared(globalsSlot): threadVarSetValue(globalsSlot, thrd.core.load())
threadProcWrapStackFrame(thrd)
# Since an unhandled exception terminates the whole process (!), there is
# no need for a ``try finally`` here, nor would it be correct: The current
# exception is tried to be re-raised by the code-gen after the ``finally``!
# no need for a `try finally` here, nor would it be correct: The current
# exception is tried to be re-raised by the code-gen after the `finally`!
# However this is doomed to fail, because we already unmapped every heap
# page!

# mark as not running anymore:
thrd.core = nil
thrd.dataFn = nil
deallocThreadStorage(cast[pointer](core))
thrd.core.store nil
thrd.dataFn.store nil
deallocThreadStorage(core)
Loading