Fixes#23615
## Root cause
The leak does not require async at all -- this minimal closure iterator
leaks the exception and its stacktrace seq under ARC/ORC:
```nim
iterator it(): int {.closure.} =
try:
yield 1 # try spanning a yield => closureiters transform
raise newException(ValueError, "x")
except ValueError: # typed except => generated `of` check
discard
yield 2
```
A bare `except:` does not leak; a *typed* `except` does:
1. `collectExceptState` in `compiler/closureiters.nim` generates the
except-branch type check as `of(getCurrentException(), T)`, using the
raw generic magic sym from `getSysMagic("of", mOf)`.
2. `injectdestructors` skips call arguments whose *formal* parameter
type is `isCompileTimeOnly`, and the raw generic `of` sym's formal
params are `tyGenericParam` so both arguments of the generated `of` call
are never processed.
3. `getCurrentException()` increfs `currException` via `=copy` into its
result. Since the arc pass never wraps that owned temp in a destroy
(`--expandArc` shows the condition left untouched, while a user-written
`if f() of ValueError` in the same iterator gets a `:tmpD` +
`=destroy`), the caught exception's refcount stays +1 forever.
Every `try: await x() except SomeError` in async code has this shape, so
each caught async exception leaked once.
## Fix
The state-machine wrapper already stores the active exception in the
`:curExc` env field before jumping to the except landing state, and
`currException == :curExc` on every path into that state. The generated
condition now references the env field via `ctx.newCurExcAccess()`
instead of calling `getCurrentException()` again -- no ownership
transfer, no temp to destroy, one fewer runtime call.
Note: the underlying `injectdestructors` behavior (skipping args of
calls whose formal params are raw `tyGenericParam`, e.g. from
`getSysMagic`) is a separate latent gap that could affect other
compiler-generated code; it is intentionally left untouched here.
## Valgrind, before and after
Exact code and command from the issue, on Linux (Valgrind 3.19):
```
nim c -d:danger --mm:orc --debugger:native --threads:off -d:useMalloc bug.nim
valgrind --leak-check=full --show-leak-kinds=all ./bug
```
Before (devel):
```
==14663== HEAP SUMMARY:
==14663== in use at exit: 136 bytes in 2 blocks
==14663== total heap usage: 22 allocs, 20 frees, 116,466 bytes allocated
==14663==
==14663== 56 bytes in 1 blocks are indirectly lost in loss record 1 of 2
==14663== at 0x488A1C4: realloc (vg_replace_malloc.c:1437)
==14663== by 0x10C663: prepareSeqAddUninit (seqs_v2.nim:212)
==14663== by 0x10CF6F: raiseExceptionEx (excpt.nim:538)
==14663== by 0x11661F: amain::amainX20X28AsyncX29_(Future<void>) (bug.nim:10)
==14663== ...
==14663==
==14663== 136 (80 direct, 56 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==14663== at 0x48850C8: malloc (vg_replace_malloc.c:381)
==14663== by 0x10C8E3: nimNewObj (arc.nim:122)
==14663== by 0x115403: err::errX20X28AsyncX29_(Future<void>) (asyncmacro.nim:274)
==14663== by 0x116027: err::errNimAsyncContinue(Future<void>, ClosureIt<void>) (asyncmacro.nim:44)
==14663== by 0x1163D3: bug::err (bug.nim:3)
==14663== ...
==14663==
==14663== LEAK SUMMARY:
==14663== definitely lost: 80 bytes in 1 blocks
==14663== indirectly lost: 56 bytes in 1 blocks
==14663== possibly lost: 0 bytes in 0 blocks
==14663== still reachable: 0 bytes in 0 blocks
==14663== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```
After (this PR):
```
==14675== HEAP SUMMARY:
==14675== in use at exit: 0 bytes in 0 blocks
==14675== total heap usage: 22 allocs, 22 frees, 116,466 bytes allocated
==14675==
==14675== All heap blocks were freed -- no leaks are possible
==14675==
==14675== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```
## Testing
- New `tests/async/t23615.nim` (modeled on `t23212.nim`: `valgrind:
true` + alloc-stats assertion) covers both the pure closure-iterator
form and the async form from the issue, with the caught exception looped
50x so the leak blows well past the slack threshold. It passes with this
PR and fails against devel.
- Testament categories `async`, `arc`, `iter`, `exception` all pass with
the patched compiler (323 tests).
- Behavior is unchanged on a sanity program covering multi-branch
dispatch, `as e` binding, nested try, and re-raise across yields: output
is byte-identical to devel; the patched build just frees 2 more blocks
per caught exception.
Reworked closureiter transformation.
- Convolutedly nested finallies should cause no problems now.
- CurrentException state now follows nim runtime rules (pushes and pops
appropriately), and mimics normal code, which is somewhat buggy, see
#25031
- Previously state optimization (removing empty states or extra jumps)
missed some opportunities, I've reimplemented it to do everything
possible to optimize the states. At this point any extra states or jumps
should be considered a bug.
The resulting codegen (compiled binaries) is also slightly smaller.
**BUT:**
- I had to change C++ reraising logic, see expt.nim. Because with
closure iters `currentException` is not always in sync with C++'s notion
of current exception. From my tests and understanding of C++ runtime
there should not be any problems, but I'm only 99% sure :)
- I've reused `nfNoRewrite` flag in one specific case during the
transformation. This flag is also used in term-rewriting logic. Again,
99% sure, these 2 scenarios will never intersect.
Fixes#24895
- Remove all bio handling
- Remove all `sendPendingSslData` which only seems to make things work
by chance
- Wrap the client socket on `acceptAddr` (std/net does this)
- Do the SSL handshake on accept (std/net does this)
The only concern is if addWrite/addRead works well on Windows.
implements https://github.com/nim-lang/RFCs/issues/557
It inserts defect handing into a bare except branch
```nim
try:
raiseAssert "test"
except:
echo "nope"
```
=>
```nim
try:
raiseAssert "test"
except:
# New behaviov, now well-defined: **never** catches the assert, regardless of panic mode
raiseDefect()
echo "nope"
```
In this way, `except` still catches foreign exceptions, but panics on
`Defect`. Probably when Nim has `except {.foreign.}`, we can extend
`raiseDefect` to foreign exceptions as well. That's supposed to be a
small use case anyway.
`--legacy:noPanicOnExcept` is provided for a transition period.
This makes await point to the caller line instead of asyncmacro. It also
reworks the "Async traceback:" section of the traceback. Follow up PR
#21091 (issue #19931) so it works if there is asynchronous work done.
kqueue will remove pipes automatically if their read end is closed.
Unfortunately this means that trying to unregister it (which is
necessary to clean up resources & for consistency with other ioselectors
implementations) will set an ENOENT error, which currently raises an
exception.
(ETA: in other words, it is currently impossible to call unregister on a
pipe fd without potentially getting the selector into an invalid state
on platforms with kqueue.)
Avoid this issue by ignoring ENOENT errors returned from kqueue.
(Tested on FreeBSD. I added a test case to the tioselectors file; the
seemingly unrelated change is to fix a race condition that doesn't
appear on Linux, so that it would run my code too.)
In this PR, the following changes were made:
1. Replaced `raise newException(OSError, osErrorMsg(errno))` in batches
with `raiseOSError(errcode)`.
2. Replaced `newException(OSError, osErrorMsg(errno))` in batches with
`newOSError(errcode)`.
There are still some places that have not been replaced. After checking,
they are not system errors in the traditional sense.
```nim
proc dlclose(lib: LibHandle) =
raise newException(OSError, "dlclose not implemented on Nintendo Switch!")
```
```nim
if not fileExists(result) and not dirExists(result):
# consider using: `raiseOSError(osLastError(), result)`
raise newException(OSError, "file '" & result & "' does not exist")
```
```nim
proc paramStr*(i: int): string =
raise newException(OSError, "paramStr is not implemented on Genode")
```
* wip; fixes#22210; transform return future in try/finally properly
* add a test case for #22210
* minor
* inserts a needsCompletion flag
* uses copyNimNode
* Add test case for a const being used inside an async proc
* Use `typeof` to get the type of the block instead of overloaded templates
This removes the problem with the symbol having different types
I am unsure why I didn't use this in the first place. IIRC I had problems with `typeof` when I first tried to use it in the original implementation
* Name iterators something human readable
Remove intermediate async procs from stacktraces
Clean async traceback message from reraises message
* Remove unused import/variable
* Fix failing tests
Don't add {.stackTrace: off.} to anonymous procs (They already don't appear in stacktrace)
* Fix failing tests in pragma category
Now check that the nim is a routine type first so we don't run into any assertion defects
* Hide stack trace pragma in docs and update doc tests
User doesn't need to know if something won't appear so this more becomes verbose noise
If this is a bad idea we can always add a `when defined(nimdoc)` switch so we don't add {.stackTrace: off.} to the Future[T] returning proc for docs
* Implicit return working for asyncdispatch proc
Closes#11558
* Test case
* Test that return value is actually used
* Update tests/async/t11558.nim
Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
* fix =#13790 ptr char (+friends) should not implicitly convert to cstring
* Apply suggestions from code review
* first round; compiles on windows
* nimPreviewSlimSystem
* conversion is unsafe, cast needed
* fixes more tests
* fixes asyncnet
* another try another error
* last one
* true
* one more
* why bugs didn't show at once
* add `nimPreviewCstringConversion` switch
* typo
* fixes ptr to cstring warnings[backport]
* add fixes
Co-authored-by: xflywind <43030857+xflywind@users.noreply.github.com>
* Better error message and tests for bad await
* Use compiles to check if await is valid
* temp: disable windows noasync test
* Better error report, simplify test
Co-authored-by: flywind <xzsflywind@gmail.com>
* deprecate unsafeAddr; extend addr
addr is now available for all addressable locations, unsafeAddr is deprecated and become an alias for addr
* follow @Vindaar's advice
* change the signature of addr
* unsafeAddr => addr (stdlib)
* Update changelog.md
* unsafeAddr => addr (tests)
* Revert "unsafeAddr => addr (stdlib)"
This reverts commit ab83c99c50.
* doc changes; thanks to @konsumlamm
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com>
* fixes#18858 [backport]
* ensure async tests work with --experimental:strictEffects [backport]
* ensure async tests work with --experimental:strictEffects [backport]
Copying StackTraceEntry instances when nimStackTraceOverride is defined
breaks the link between a cstring field that's supposed to point at
another string field in the same object.
Sometimes, the original object is garbage collected, that memory region
reused for storing other strings, so when the StackTraceEntry copy tries
to use its cstring pointer to construct a traceback message, it accesses
unrelated strings.
This only happens for async tracebacks and this patch prevents that by
making sure we only use the string fields when nimStackTraceOverride is
defined.
Async tracebacks also beautified slightly by getting rid of an extra line
that was supposed to be commented out, along with the corresponding debugging output.
There's also a micro-optimisation to avoid concatenating two strings just
to get their combined length.
Fixes#15003.
This is a serious bug which occurs when data cannot be read/sent
immediately and there are a bunch of other read/write events
pending. What happens is that the new events are dropped which
results in the case of the reported bug resulted in some data not
being sent (!).
* Fix asyncdispatch drain behavior (#14820)
* Changed test to use asyncCheck instead of discard after code review (#14820)
* Added some debug statements to help understand what is happening in Azure.
* Removed debug statements and increased timeouts by 1 order of magnitude to account for slow Azure VMs
Co-authored-by: Ray Imber <ray@crankuptheamps.com>