Skip to content

Commit 7630f39

Browse files
authored
Fixes compilation issues in v3 compatibility mode (-d:chronosHandleException) (#545)
* add missing calls to await * add test run in v3 compatibility * fix semantics for chronosHandleException so it does not override local raises/handleException annotations * distinguish between explicit override and default setting; fix test * re-enable wrongly disabled check * make implementation simpler/clearer * update docs * reflow long line * word swap
1 parent c444065 commit 7630f39

File tree

6 files changed

+61
-13
lines changed

6 files changed

+61
-13
lines changed

chronos.nimble

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ task test, "Run all tests":
6060
run args & " --mm:refc", "tests/testall"
6161
run args, "tests/testall"
6262

63+
task test_v3_compat, "Run all tests in v3 compatibility mode":
64+
for args in testArguments:
65+
if (NimMajor, NimMinor) > (1, 6):
66+
# First run tests with `refc` memory manager.
67+
run args & " --mm:refc -d:chronosHandleException", "tests/testall"
68+
69+
run args & " -d:chronosHandleException", "tests/testall"
70+
6371
task test_libbacktrace, "test with libbacktrace":
6472
if platform != "x86":
6573
let allArgs = @[

chronos/internal/asyncmacro.nim

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,12 +219,14 @@ proc decodeParams(params: NimNode): AsyncParams =
219219
var
220220
raw = false
221221
raises: NimNode = nil
222-
handleException = chronosHandleException
222+
handleException = false
223+
hasLocalAnnotations = false
223224

224225
for param in params:
225226
param.expectKind(nnkExprColonExpr)
226227

227228
if param[0].eqIdent("raises"):
229+
hasLocalAnnotations = true
228230
param[1].expectKind(nnkBracket)
229231
if param[1].len == 0:
230232
raises = makeNoRaises()
@@ -236,10 +238,14 @@ proc decodeParams(params: NimNode): AsyncParams =
236238
# boolVal doesn't work in untyped macros it seems..
237239
raw = param[1].eqIdent("true")
238240
elif param[0].eqIdent("handleException"):
241+
hasLocalAnnotations = true
239242
handleException = param[1].eqIdent("true")
240243
else:
241244
warning("Unrecognised async parameter: " & repr(param[0]), param)
242245

246+
if not hasLocalAnnotations:
247+
handleException = chronosHandleException
248+
243249
(raw, raises, handleException)
244250

245251
proc isEmpty(n: NimNode): bool {.compileTime.} =

chronos/transports/datagram.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ proc newDatagramTransportCommon(cbproc: UnsafeDatagramCallback,
720720
proc wrap(transp: DatagramTransport,
721721
remote: TransportAddress) {.async: (raises: []).} =
722722
try:
723-
cbproc(transp, remote)
723+
await cbproc(transp, remote)
724724
except CatchableError as exc:
725725
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg
726726

chronos/transports/stream.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2197,7 +2197,7 @@ proc createStreamServer*(host: TransportAddress,
21972197
proc wrap(server: StreamServer,
21982198
client: StreamTransport) {.async: (raises: []).} =
21992199
try:
2200-
cbproc(server, client)
2200+
await cbproc(server, client)
22012201
except CatchableError as exc:
22022202
raiseAssert "Unexpected exception from stream server cbproc: " & exc.msg
22032203

docs/src/error_handling.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ sometimes lead to compile errors around forward declarations, methods and
110110
closures as Nim conservatively asssumes that any `Exception` might be raised
111111
from those.
112112

113-
Make sure to excplicitly annotate these with `{.raises.}`:
113+
Make sure to explicitly annotate these with `{.raises.}`:
114114

115115
```nim
116116
# Forward declarations need to explicitly include a raises list:
@@ -124,26 +124,45 @@ proc myfunction() =
124124
125125
let closure: MyClosure = myfunction
126126
```
127+
## Compatibility modes
127128

128-
For compatibility, `async` functions can be instructed to handle `Exception` as
129-
well, specifying `handleException: true`. `Exception` that is not a `Defect` and
130-
not a `CatchableError` will then be caught and remapped to
131-
`AsyncExceptionError`:
129+
**Individual functions.** For compatibility, `async` functions can be instructed
130+
to handle `Exception` as well, specifying `handleException: true`. Any
131+
`Exception` that is not a `Defect` and not a `CatchableError` will then be
132+
caught and remapped to `AsyncExceptionError`:
132133

133134
```nim
134135
proc raiseException() {.async: (handleException: true, raises: [AsyncExceptionError]).} =
135136
raise (ref Exception)(msg: "Raising Exception is UB")
136137
137138
proc callRaiseException() {.async: (raises: []).} =
138139
try:
139-
raiseException()
140+
await raiseException()
140141
except AsyncExceptionError as exc:
141142
# The original Exception is available from the `parent` field
142143
echo exc.parent.msg
143144
```
144145

145-
This mode can be enabled globally with `-d:chronosHandleException` as a help
146-
when porting code to `chronos` but should generally be avoided as global
147-
configuration settings may interfere with libraries that use `chronos` leading
148-
to unexpected behavior.
146+
**Global flag.** This mode can be enabled globally with
147+
`-d:chronosHandleException` as a help when porting code to `chronos`. The
148+
behavior in this case will be that:
149149

150+
1. old-style functions annotated with plain `async` will behave as if they had
151+
been annotated with `async: (handleException: true)`.
152+
153+
This is functionally equivalent to
154+
`async: (handleException: true, raises: [CatchableError])` and will, as
155+
before, remap any `Exception` that is not `Defect` into
156+
`AsyncExceptionError`, while also allowing any `CatchableError` (including
157+
`AsyncExceptionError`) to get through without compilation errors.
158+
159+
2. New-style functions with `async: (raises: [...])` annotations or their own
160+
`handleException` annotations will not be affected.
161+
162+
The rationale here is to allow one to incrementally introduce exception
163+
annotations and get compiler feedback while not requiring that every bit of
164+
legacy code is updated at once.
165+
166+
This should be used sparingly and with care, however, as global configuration
167+
settings may interfere with libraries that use `chronos` leading to unexpected
168+
behavior.

tests/testmacro.nim

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import std/[macros, strutils]
99
import unittest2
1010
import ../chronos
11+
import ../chronos/config
1112

1213
{.used.}
1314

@@ -586,6 +587,20 @@ suite "Exceptions tracking":
586587

587588
waitFor(callCatchAll())
588589

590+
test "Global handleException does not override local annotations":
591+
when chronosHandleException:
592+
proc unnanotated() {.async.} = raise (ref CatchableError)()
593+
594+
checkNotCompiles:
595+
proc annotated() {.async: (raises: [ValueError]).} =
596+
raise (ref CatchableError)()
597+
598+
checkNotCompiles:
599+
proc noHandleException() {.async: (handleException: false).} =
600+
raise (ref Exception)()
601+
else:
602+
skip()
603+
589604
test "Results compatibility":
590605
proc returnOk(): Future[Result[int, string]] {.async: (raises: []).} =
591606
ok(42)

0 commit comments

Comments
 (0)