Add `tearDownForeignThreadGc` function (#5369)
This commit is contained in:
parent
279e4b0451
commit
6fa1dba515
5 changed files with 132 additions and 10 deletions
|
|
@ -461,3 +461,11 @@ can then attach a GC to this thread via
|
||||||
It is **not** safe to disable the garbage collector and enable it after the
|
It is **not** safe to disable the garbage collector and enable it after the
|
||||||
call from your background thread even if the code you are calling is short
|
call from your background thread even if the code you are calling is short
|
||||||
lived.
|
lived.
|
||||||
|
|
||||||
|
Before the thread exits, you should tear down the thread's GC to prevent memory
|
||||||
|
leaks by calling
|
||||||
|
|
||||||
|
.. code-block:: nim
|
||||||
|
|
||||||
|
system.tearDownForeignThreadGc()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,13 +128,7 @@ iterator items(stack: ptr GcStack): ptr GcStack =
|
||||||
yield s
|
yield s
|
||||||
s = s.next
|
s = s.next
|
||||||
|
|
||||||
# There will be problems with GC in foreign threads if `threads` option is off or TLS emulation is enabled
|
when declared(threadType):
|
||||||
const allowForeignThreadGc = compileOption("threads") and not compileOption("tlsEmulation")
|
|
||||||
|
|
||||||
when allowForeignThreadGc:
|
|
||||||
var
|
|
||||||
localGcInitialized {.rtlThreadVar.}: bool
|
|
||||||
|
|
||||||
proc setupForeignThreadGc*() {.gcsafe.} =
|
proc setupForeignThreadGc*() {.gcsafe.} =
|
||||||
## Call this if you registered a callback that will be run from a thread not
|
## Call this if you registered a callback that will be run from a thread not
|
||||||
## under your control. This has a cheap thread-local guard, so the GC for
|
## under your control. This has a cheap thread-local guard, so the GC for
|
||||||
|
|
@ -143,16 +137,33 @@ when allowForeignThreadGc:
|
||||||
##
|
##
|
||||||
## This function is available only when ``--threads:on`` and ``--tlsEmulation:off``
|
## This function is available only when ``--threads:on`` and ``--tlsEmulation:off``
|
||||||
## switches are used
|
## switches are used
|
||||||
if not localGcInitialized:
|
if threadType == ThreadType.None:
|
||||||
localGcInitialized = true
|
|
||||||
initAllocator()
|
initAllocator()
|
||||||
var stackTop {.volatile.}: pointer
|
var stackTop {.volatile.}: pointer
|
||||||
setStackBottom(addr(stackTop))
|
setStackBottom(addr(stackTop))
|
||||||
initGC()
|
initGC()
|
||||||
|
threadType = ThreadType.ForeignThread
|
||||||
|
|
||||||
|
proc tearDownForeignThreadGc*() {.gcsafe.} =
|
||||||
|
## Call this to tear down the GC, previously initialized by ``setupForeignThreadGc``.
|
||||||
|
## If GC has not been previously initialized, or has already been torn down, the
|
||||||
|
## call does nothing.
|
||||||
|
##
|
||||||
|
## This function is available only when ``--threads:on`` and ``--tlsEmulation:off``
|
||||||
|
## switches are used
|
||||||
|
if threadType != ThreadType.ForeignThread:
|
||||||
|
return
|
||||||
|
when declared(deallocOsPages): deallocOsPages()
|
||||||
|
threadType = ThreadType.None
|
||||||
|
when declared(gch): zeroMem(addr gch, sizeof(gch))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
template setupForeignThreadGc*() =
|
template setupForeignThreadGc*() =
|
||||||
{.error: "setupForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
|
{.error: "setupForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
|
||||||
|
|
||||||
|
template tearDownForeignThreadGc*() =
|
||||||
|
{.error: "tearDownForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
|
||||||
|
|
||||||
# ----------------- stack management --------------------------------------
|
# ----------------- stack management --------------------------------------
|
||||||
# inspired from Smart Eiffel
|
# inspired from Smart Eiffel
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,19 @@ when useStackMaskHack:
|
||||||
when not defined(useNimRtl):
|
when not defined(useNimRtl):
|
||||||
when not useStackMaskHack:
|
when not useStackMaskHack:
|
||||||
#when not defined(createNimRtl): initStackBottom()
|
#when not defined(createNimRtl): initStackBottom()
|
||||||
when declared(initGC): initGC()
|
when declared(initGC):
|
||||||
|
initGC()
|
||||||
|
when not emulatedThreadVars:
|
||||||
|
type ThreadType {.pure.} = enum
|
||||||
|
None = 0,
|
||||||
|
NimThread = 1,
|
||||||
|
ForeignThread = 2
|
||||||
|
var
|
||||||
|
threadType {.rtlThreadVar.}: ThreadType
|
||||||
|
|
||||||
|
threadType = ThreadType.NimThread
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
when emulatedThreadVars:
|
when emulatedThreadVars:
|
||||||
if nimThreadVarsSize() > sizeof(ThreadLocalStorage):
|
if nimThreadVarsSize() > sizeof(ThreadLocalStorage):
|
||||||
|
|
@ -442,6 +454,8 @@ proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) =
|
||||||
# init the GC for refc/markandsweep
|
# init the GC for refc/markandsweep
|
||||||
setStackBottom(addr(p))
|
setStackBottom(addr(p))
|
||||||
initGC()
|
initGC()
|
||||||
|
when declared(threadType):
|
||||||
|
threadType = ThreadType.NimThread
|
||||||
when declared(registerThread):
|
when declared(registerThread):
|
||||||
thrd.stackBottom = addr(thrd)
|
thrd.stackBottom = addr(thrd)
|
||||||
registerThread(thrd)
|
registerThread(thrd)
|
||||||
|
|
|
||||||
88
tests/gc/foreign_thr.nim
Normal file
88
tests/gc/foreign_thr.nim
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
discard """
|
||||||
|
output: '''
|
||||||
|
Hello from thread
|
||||||
|
Hello from thread
|
||||||
|
Hello from thread
|
||||||
|
Hello from thread
|
||||||
|
'''
|
||||||
|
cmd: "nim $target --hints:on --threads:on --tlsEmulation:off $options $file"
|
||||||
|
"""
|
||||||
|
# Copied from stdlib
|
||||||
|
import strutils
|
||||||
|
|
||||||
|
const
|
||||||
|
StackGuardSize = 4096
|
||||||
|
ThreadStackMask = 1024*256*sizeof(int)-1
|
||||||
|
ThreadStackSize = ThreadStackMask+1 - StackGuardSize
|
||||||
|
|
||||||
|
type ThreadFunc = proc() {.thread.}
|
||||||
|
|
||||||
|
when defined(posix):
|
||||||
|
import posix
|
||||||
|
|
||||||
|
proc runInForeignThread(f: ThreadFunc) =
|
||||||
|
proc wrapper(p: pointer): pointer {.noconv.} =
|
||||||
|
let thr = cast[ThreadFunc](p)
|
||||||
|
setupForeignThreadGc()
|
||||||
|
thr()
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
setupForeignThreadGc()
|
||||||
|
thr()
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
result = nil
|
||||||
|
|
||||||
|
var attrs {.noinit.}: PthreadAttr
|
||||||
|
doAssert pthread_attr_init(addr attrs) == 0
|
||||||
|
doAssert pthread_attr_setstacksize(addr attrs, ThreadStackSize) == 0
|
||||||
|
var tid: Pthread
|
||||||
|
doAssert pthread_create(addr tid, addr attrs, wrapper, f) == 0
|
||||||
|
doAssert pthread_join(tid, nil) == 0
|
||||||
|
|
||||||
|
elif defined(windows):
|
||||||
|
import winlean
|
||||||
|
type
|
||||||
|
WinThreadProc = proc (x: pointer): int32 {.stdcall.}
|
||||||
|
|
||||||
|
proc createThread(lpThreadAttributes: pointer, dwStackSize: DWORD,
|
||||||
|
lpStartAddress: WinThreadProc,
|
||||||
|
lpParameter: pointer,
|
||||||
|
dwCreationFlags: DWORD,
|
||||||
|
lpThreadId: var DWORD): Handle {.
|
||||||
|
stdcall, dynlib: "kernel32", importc: "CreateThread".}
|
||||||
|
|
||||||
|
proc wrapper(p: pointer): int32 {.stdcall.} =
|
||||||
|
let thr = cast[ThreadFunc](p)
|
||||||
|
setupForeignThreadGc()
|
||||||
|
thr()
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
setupForeignThreadGc()
|
||||||
|
thr()
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
result = 0'i32
|
||||||
|
|
||||||
|
proc runInForeignThread(f: ThreadFunc) =
|
||||||
|
var dummyThreadId: DWORD
|
||||||
|
var h = createThread(nil, ThreadStackSize.int32, wrapper.WinThreadProc, cast[pointer](f), 0, dummyThreadId)
|
||||||
|
doAssert h != 0.Handle
|
||||||
|
doAssert waitForSingleObject(h, -1'i32) == 0.DWORD
|
||||||
|
|
||||||
|
else:
|
||||||
|
{.fatal: "Unknown system".}
|
||||||
|
|
||||||
|
proc runInNativeThread(f: ThreadFunc) =
|
||||||
|
proc wrapper(f: ThreadFunc) {.thread.} =
|
||||||
|
# These operations must be NOP
|
||||||
|
setupForeignThreadGc()
|
||||||
|
tearDownForeignThreadGc()
|
||||||
|
f()
|
||||||
|
f()
|
||||||
|
var thr: Thread[ThreadFunc]
|
||||||
|
createThread(thr, wrapper, f)
|
||||||
|
joinThread(thr)
|
||||||
|
|
||||||
|
proc f {.thread.} =
|
||||||
|
var msg = "Hello " & "from thread"
|
||||||
|
echo msg
|
||||||
|
|
||||||
|
runInForeignThread(f)
|
||||||
|
runInNativeThread(f)
|
||||||
|
|
@ -147,6 +147,7 @@ proc gcTests(r: var TResults, cat: Category, options: string) =
|
||||||
testSpec r, makeTest("tests/gc" / filename, options &
|
testSpec r, makeTest("tests/gc" / filename, options &
|
||||||
" -d:release --gc:boehm", cat, actionRun)
|
" -d:release --gc:boehm", cat, actionRun)
|
||||||
|
|
||||||
|
testWithoutBoehm "foreign_thr"
|
||||||
test "gcemscripten"
|
test "gcemscripten"
|
||||||
test "growobjcrash"
|
test "growobjcrash"
|
||||||
test "gcbench"
|
test "gcbench"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue