* fixes #13881
* documented changed requirements for system.onThreadDestruction
* destructors.rst: update the documentation
This commit is contained in:
Andreas Rumpf 2020-05-12 15:46:24 +02:00 • committed by GitHub
commit 06dfd31612
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 23 deletions

View file

@ -89,6 +89,9 @@
- Fix a bug where calling `close` on io streams in osproc.startProcess was a noop and led to - Fix a bug where calling `close` on io streams in osproc.startProcess was a noop and led to
hangs if a process had both reads from stdin and writes (eg to stdout). hangs if a process had both reads from stdin and writes (eg to stdout).
- The callback that is passed to `system.onThreadDestruction` must now be `.raises: []`.
## Language changes ## Language changes
- In the newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this: - In the newruntime it is now allowed to assign discriminator field without restrictions as long as case object doesn't have custom destructor. Discriminator value doesn't have to be a constant either. If you have custom destructor for case object and you do want to freely assign discriminator fields, it is recommended to refactor object into 2 objects like this:

View file

@ -117,3 +117,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimNewIntegerOps") defineSymbol("nimNewIntegerOps")
defineSymbol("nimHasInvariant") defineSymbol("nimHasInvariant")
defineSymbol("nimHasStacktraceMsgs") defineSymbol("nimHasStacktraceMsgs")
defineSymbol("nimDoesntTrackDefects")

View file

@ -469,10 +469,51 @@ for expressions of type ``lent T`` or of type ``var T``.
echo t[0] # accessor does not copy the element! echo t[0] # accessor does not copy the element!
The .cursor annotation
======================
Under the ``--gc:arc|orc`` modes Nim's `ref` type is implemented via the same runtime
"hooks" and thus via reference counting. This means that cyclic structures cannot be freed
immediately (``--gc:orc`` ships with a cycle collector). With the ``.cursor`` annotation
one can break up cycles declaratively:
.. code-block:: nim
type
Node = ref object
left: Node # owning ref
right {.cursor.}: Node # non-owning ref
But please notice that this is not C++'s weak_ptr, it means the right field is not
involved in the reference counting, it is a raw pointer without runtime checks.
Automatic reference counting also has the disadvantage that it introduces overhead
when iterating over linked structures. The ``.cursor`` annotation can also be used
to avoid this overhead:
.. code-block:: nim
var it {.cursor.} = listRoot
while it != nil:
use(it)
it = it.next
In fact, ``.cursor`` more generally prevents object construction/destruction pairs
and so can also be useful in other contexts. The alternative solution would be to
use raw pointers (``ptr``) instead which is more cumbersome and also more dangerous
for Nim's evolution: Later on the compiler can try to prove ``.cursor`` annotations
to be safe, but for ``ptr`` the compiler has to remain silent about possible
problems.
Owned refs Owned refs
========== ==========
**Note**: The ``owned`` type constructor is only available with
the ``--newruntime`` compiler switch and is experimental.
Let ``W`` be an ``owned ref`` type. Conceptually its hooks look like: Let ``W`` be an ``owned ref`` type. Conceptually its hooks look like:
.. code-block:: nim .. code-block:: nim
@ -568,14 +609,14 @@ used to specialize the object traversal in order to avoid deep recursions:
type Node = ref object type Node = ref object
x, y: int32 x, y: int32
left, right: owned Node left, right: Node
type Tree = object type Tree = object
root: owned Node root: Node
proc `=destroy`(t: var Tree) {.nodestroy.} = proc `=destroy`(t: var Tree) {.nodestroy.} =
# use an explicit stack so that we do not get stack overflows: # use an explicit stack so that we do not get stack overflows:
var s: seq[owned Node] = @[t.root] var s: seq[Node] = @[t.root]
while s.len > 0: while s.len > 0:
let x = s.pop let x = s.pop
if x.left != nil: s.add(x.left) if x.left != nil: s.add(x.left)

View file

@ -2093,7 +2093,7 @@ when not defined(js):
when hasAlloc: when hasAlloc:
when not defined(gcRegions) and not usesDestructors: when not defined(gcRegions) and not usesDestructors:
proc initGC() {.gcsafe.} proc initGC() {.gcsafe, raises: [].}
proc initStackBottom() {.inline, compilerproc.} = proc initStackBottom() {.inline, compilerproc.} =
# WARNING: This is very fragile! An array size of 8 does not work on my # WARNING: This is very fragile! An array size of 8 does not work on my

View file

@ -493,6 +493,14 @@ proc reraiseException() {.compilerRtl.} =
else: else:
raiseExceptionAux(currException) raiseExceptionAux(currException)
proc threadTrouble() =
# also forward declared, it is 'raises: []' hence the try-except.
try:
if currException != nil: reportUnhandledError(currException)
except:
discard
quit 1
proc writeStackTrace() = proc writeStackTrace() =
when hasSomeStackTrace: when hasSomeStackTrace:
var s = "" var s = ""

View file

@ -843,6 +843,7 @@ when not defined(useNimRtl):
proc GC_disable() = proc GC_disable() =
inc(gch.recGcLock) inc(gch.recGcLock)
proc GC_enable() = proc GC_enable() =
when defined(nimDoesntTrackDefects):
if gch.recGcLock <= 0: if gch.recGcLock <= 0:
raise newException(AssertionDefect, raise newException(AssertionDefect,
"API usage error: GC_enable called but GC is already enabled") "API usage error: GC_enable called but GC is already enabled")

View file

@ -14,7 +14,7 @@ when hasAlloc:
gcOptimizeSpace ## optimize for memory footprint gcOptimizeSpace ## optimize for memory footprint
when hasAlloc and not defined(js) and not usesDestructors: when hasAlloc and not defined(js) and not usesDestructors:
proc GC_disable*() {.rtl, inl, benign.} proc GC_disable*() {.rtl, inl, benign, raises: [].}
## Disables the GC. If called `n` times, `n` calls to `GC_enable` ## Disables the GC. If called `n` times, `n` calls to `GC_enable`
## are needed to reactivate the GC. ## are needed to reactivate the GC.
## ##
@ -22,7 +22,7 @@ when hasAlloc and not defined(js) and not usesDestructors:
## the mark and sweep phase with ## the mark and sweep phase with
## `GC_disableMarkAndSweep <#GC_disableMarkAndSweep>`_. ## `GC_disableMarkAndSweep <#GC_disableMarkAndSweep>`_.
proc GC_enable*() {.rtl, inl, benign.} proc GC_enable*() {.rtl, inl, benign, raises: [].}
## Enables the GC again. ## Enables the GC again.
proc GC_fullCollect*() {.rtl, benign.} proc GC_fullCollect*() {.rtl, benign.}
@ -54,7 +54,7 @@ when hasAlloc and not defined(js) and not usesDestructors:
proc GC_unref*(x: string) {.magic: "GCunref", benign.} proc GC_unref*(x: string) {.magic: "GCunref", benign.}
## See the documentation of `GC_ref <#GC_ref,string>`_. ## See the documentation of `GC_ref <#GC_ref,string>`_.
proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign.} proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign, raises: [].}
## Expands operating GC stack range to `theStackBottom`. Does nothing ## Expands operating GC stack range to `theStackBottom`. Does nothing
## if current stack bottom is already lower than `theStackBottom`. ## if current stack bottom is already lower than `theStackBottom`.

View file

@ -495,6 +495,7 @@ when not defined(useNimRtl):
proc GC_disable() = proc GC_disable() =
inc(gch.recGcLock) inc(gch.recGcLock)
proc GC_enable() = proc GC_enable() =
when defined(nimDoesntTrackDefects):
if gch.recGcLock <= 0: if gch.recGcLock <= 0:
raise newException(AssertionDefect, raise newException(AssertionDefect,
"API usage error: GC_enable called but GC is already enabled") "API usage error: GC_enable called but GC is already enabled")

View file

@ -89,9 +89,9 @@ type
data: TArg data: TArg
var var
threadDestructionHandlers {.rtlThreadVar.}: seq[proc () {.closure, gcsafe.}] threadDestructionHandlers {.rtlThreadVar.}: seq[proc () {.closure, gcsafe, raises: [].}]
proc onThreadDestruction*(handler: proc () {.closure, gcsafe.}) = proc onThreadDestruction*(handler: proc () {.closure, gcsafe, raises: [].}) =
## Registers a *thread local* handler that is called at the thread's ## Registers a *thread local* handler that is called at the thread's
## destruction. ## destruction.
## ##
@ -105,7 +105,10 @@ template afterThreadRuns() =
threadDestructionHandlers[i]() threadDestructionHandlers[i]()
when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcRegions): when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcRegions):
proc deallocOsPages() {.rtl.} proc deallocOsPages() {.rtl, raises: [].}
proc threadTrouble() {.raises: [].}
## defined in system/excpt.nim
when defined(boehmgc): when defined(boehmgc):
type GCStackBaseProc = proc(sb: pointer, t: pointer) {.noconv.} type GCStackBaseProc = proc(sb: pointer, t: pointer) {.noconv.}
@ -116,7 +119,7 @@ when defined(boehmgc):
proc boehmGC_unregister_my_thread() proc boehmGC_unregister_my_thread()
{.importc: "GC_unregister_my_thread", boehmGC.} {.importc: "GC_unregister_my_thread", boehmGC.}
proc threadProcWrapDispatch[TArg](sb: pointer, thrd: pointer) {.noconv.} = proc threadProcWrapDispatch[TArg](sb: pointer, thrd: pointer) {.noconv, raises: [].} =
boehmGC_register_my_thread(sb) boehmGC_register_my_thread(sb)
try: try:
let thrd = cast[ptr Thread[TArg]](thrd) let thrd = cast[ptr Thread[TArg]](thrd)
@ -124,11 +127,13 @@ when defined(boehmgc):
thrd.dataFn() thrd.dataFn()
else: else:
thrd.dataFn(thrd.data) thrd.dataFn(thrd.data)
except:
threadTrouble()
finally: finally:
afterThreadRuns() afterThreadRuns()
boehmGC_unregister_my_thread() boehmGC_unregister_my_thread()
else: else:
proc threadProcWrapDispatch[TArg](thrd: ptr Thread[TArg]) = proc threadProcWrapDispatch[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =
try: try:
when TArg is void: when TArg is void:
thrd.dataFn() thrd.dataFn()
@ -139,21 +144,22 @@ else:
var x: TArg var x: TArg
deepCopy(x, thrd.data) deepCopy(x, thrd.data)
thrd.dataFn(x) thrd.dataFn(x)
except:
threadTrouble()
finally: finally:
afterThreadRuns() afterThreadRuns()
proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) = proc threadProcWrapStackFrame[TArg](thrd: ptr Thread[TArg]) {.raises: [].} =
when defined(boehmgc): when defined(boehmgc):
boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd) boehmGC_call_with_stack_base(threadProcWrapDispatch[TArg], thrd)
elif not defined(nogc) and not defined(gogc) and not defined(gcRegions) and not usesDestructors: elif not defined(nogc) and not defined(gogc) and not defined(gcRegions) and not usesDestructors:
var p {.volatile.}: proc(a: ptr Thread[TArg]) {.nimcall, gcsafe.} = var p {.volatile.}: pointer
threadProcWrapDispatch[TArg]
# init the GC for refc/markandsweep # init the GC for refc/markandsweep
nimGC_setStackBottom(addr(p)) nimGC_setStackBottom(addr(p))
initGC() initGC()
when declared(threadType): when declared(threadType):
threadType = ThreadType.NimThread threadType = ThreadType.NimThread
p(thrd) threadProcWrapDispatch[TArg](thrd)
when declared(deallocOsPages): deallocOsPages() when declared(deallocOsPages): deallocOsPages()
else: else:
threadProcWrapDispatch(thrd) threadProcWrapDispatch(thrd)