2018-05-16 08:22:34 +00:00
|
|
|
#
|
|
|
|
#
|
|
|
|
# Nim's Runtime Library
|
|
|
|
# (c) Copyright 2015 Dominik Picheta
|
2023-10-17 12:18:14 +00:00
|
|
|
# (c) Copyright 2018-Present Status Research & Development GmbH
|
2018-05-16 08:22:34 +00:00
|
|
|
#
|
|
|
|
# See the file "copying.txt", included in this
|
|
|
|
# distribution, for details about the copyright.
|
|
|
|
#
|
|
|
|
|
2023-10-17 18:25:25 +00:00
|
|
|
import
|
|
|
|
std/[algorithm, macros, sequtils],
|
|
|
|
../[futures, config]
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-10-16 08:38:11 +00:00
|
|
|
proc processBody(node, setResultSym, baseType: NimNode): NimNode {.compileTime.} =
|
2018-05-16 08:22:34 +00:00
|
|
|
case node.kind
|
|
|
|
of nnkReturnStmt:
|
2023-10-17 12:18:14 +00:00
|
|
|
# `return ...` -> `setResult(...); return`
|
2023-06-05 11:02:13 +00:00
|
|
|
let
|
|
|
|
res = newNimNode(nnkStmtList, node)
|
2023-10-16 08:38:11 +00:00
|
|
|
if node[0].kind != nnkEmpty:
|
|
|
|
res.add newCall(setResultSym, processBody(node[0], setResultSym, baseType))
|
2023-10-17 12:18:14 +00:00
|
|
|
res.add newNimNode(nnkReturnStmt, node).add(newEmptyNode())
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-06-05 11:02:13 +00:00
|
|
|
res
|
2019-07-19 10:06:32 +00:00
|
|
|
of RoutineNodes-{nnkTemplateDef}:
|
2023-10-17 12:18:14 +00:00
|
|
|
# Skip nested routines since they have their own return value distinct from
|
|
|
|
# the Future we inject
|
2023-06-05 11:02:13 +00:00
|
|
|
node
|
|
|
|
else:
|
|
|
|
for i in 0 ..< node.len:
|
2023-10-16 08:38:11 +00:00
|
|
|
node[i] = processBody(node[i], setResultSym, baseType)
|
2023-06-05 11:02:13 +00:00
|
|
|
node
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
proc wrapInTryFinally(fut, baseType, body, raisesTuple: NimNode): NimNode {.compileTime.} =
|
2023-10-16 08:38:11 +00:00
|
|
|
# creates:
|
|
|
|
# try: `body`
|
2023-10-17 12:18:14 +00:00
|
|
|
# [for raise in raisesTuple]:
|
|
|
|
# except `raise`: closureSucceeded = false; `castFutureSym`.fail(exc)
|
2023-10-16 08:38:11 +00:00
|
|
|
# finally:
|
|
|
|
# if closureSucceeded:
|
|
|
|
# `castFutureSym`.complete(result)
|
2023-10-17 12:18:14 +00:00
|
|
|
#
|
|
|
|
# Calling `complete` inside `finally` ensures that all success paths
|
|
|
|
# (including early returns and code inside nested finally statements and
|
|
|
|
# defer) are completed with the final contents of `result`
|
|
|
|
let
|
|
|
|
closureSucceeded = genSym(nskVar, "closureSucceeded")
|
|
|
|
nTry = nnkTryStmt.newTree(body)
|
|
|
|
excName = ident"exc"
|
|
|
|
|
|
|
|
# Depending on the exception type, we must have at most one of each of these
|
|
|
|
# "special" exception handlers that are needed to implement cancellation and
|
|
|
|
# Defect propagation
|
|
|
|
var
|
|
|
|
hasDefect = false
|
|
|
|
hasCancelledError = false
|
|
|
|
hasCatchableError = false
|
|
|
|
|
|
|
|
template addDefect =
|
|
|
|
if not hasDefect:
|
|
|
|
hasDefect = true
|
|
|
|
# When a Defect is raised, the program is in an undefined state and
|
|
|
|
# continuing running other tasks while the Future completion sits on the
|
|
|
|
# callback queue may lead to further damage so we re-raise them eagerly.
|
|
|
|
nTry.add nnkExceptBranch.newTree(
|
|
|
|
nnkInfix.newTree(ident"as", ident"Defect", excName),
|
2023-10-16 08:38:11 +00:00
|
|
|
nnkStmtList.newTree(
|
|
|
|
nnkAsgn.newTree(closureSucceeded, ident"false"),
|
2023-10-17 12:18:14 +00:00
|
|
|
nnkRaiseStmt.newTree(excName)
|
2023-10-16 08:38:11 +00:00
|
|
|
)
|
|
|
|
)
|
2023-10-17 12:18:14 +00:00
|
|
|
template addCancelledError =
|
|
|
|
if not hasCancelledError:
|
|
|
|
hasCancelledError = true
|
|
|
|
nTry.add nnkExceptBranch.newTree(
|
|
|
|
ident"CancelledError",
|
|
|
|
nnkStmtList.newTree(
|
|
|
|
nnkAsgn.newTree(closureSucceeded, ident"false"),
|
|
|
|
newCall(ident "cancelAndSchedule", fut)
|
|
|
|
)
|
|
|
|
)
|
2023-10-16 08:38:11 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
template addCatchableError =
|
|
|
|
if not hasCatchableError:
|
|
|
|
hasCatchableError = true
|
|
|
|
nTry.add nnkExceptBranch.newTree(
|
|
|
|
nnkInfix.newTree(ident"as", ident"CatchableError", excName),
|
|
|
|
nnkStmtList.newTree(
|
|
|
|
nnkAsgn.newTree(closureSucceeded, ident"false"),
|
|
|
|
newCall(ident "fail", fut, excName)
|
|
|
|
))
|
|
|
|
|
|
|
|
for exc in raisesTuple:
|
|
|
|
if exc.eqIdent("Exception"):
|
|
|
|
addCancelledError
|
|
|
|
addCatchableError
|
|
|
|
addDefect
|
|
|
|
|
|
|
|
# Because we store `CatchableError` in the Future, we cannot re-raise the
|
|
|
|
# original exception
|
|
|
|
nTry.add nnkExceptBranch.newTree(
|
|
|
|
nnkInfix.newTree(ident"as", ident"Exception", excName),
|
2023-10-16 08:38:11 +00:00
|
|
|
newCall(ident "fail", fut,
|
2023-10-17 12:18:14 +00:00
|
|
|
nnkStmtList.newTree(
|
|
|
|
nnkAsgn.newTree(closureSucceeded, ident"false"),
|
|
|
|
quote do: (ref ValueError)(msg: `excName`.msg, parent: `excName`)))
|
2023-10-16 08:38:11 +00:00
|
|
|
)
|
2023-10-17 12:18:14 +00:00
|
|
|
elif exc.eqIdent("CancelledError"):
|
|
|
|
addCancelledError
|
|
|
|
elif exc.eqIdent("CatchableError"):
|
|
|
|
# Ensure cancellations are re-routed to the cancellation handler even if
|
|
|
|
# not explicitly specified in the raises list
|
|
|
|
addCancelledError
|
|
|
|
addCatchableError
|
|
|
|
else:
|
|
|
|
nTry.add nnkExceptBranch.newTree(
|
|
|
|
nnkInfix.newTree(ident"as", exc, excName),
|
|
|
|
nnkStmtList.newTree(
|
|
|
|
nnkAsgn.newTree(closureSucceeded, ident"false"),
|
|
|
|
newCall(ident "fail", fut, excName)
|
|
|
|
))
|
2023-10-16 08:38:11 +00:00
|
|
|
|
|
|
|
nTry.add nnkFinally.newTree(
|
2023-10-17 12:18:14 +00:00
|
|
|
nnkIfStmt.newTree(
|
|
|
|
nnkElifBranch.newTree(
|
|
|
|
closureSucceeded,
|
|
|
|
if baseType.eqIdent("void"): # shortcut for non-generic void
|
|
|
|
newCall(ident "complete", fut)
|
|
|
|
else:
|
|
|
|
nnkWhenStmt.newTree(
|
|
|
|
nnkElifExpr.newTree(
|
|
|
|
nnkInfix.newTree(ident "is", baseType, ident "void"),
|
|
|
|
newCall(ident "complete", fut)
|
|
|
|
),
|
|
|
|
nnkElseExpr.newTree(
|
|
|
|
newCall(ident "complete", fut, ident "result")
|
2023-10-16 08:38:11 +00:00
|
|
|
)
|
|
|
|
)
|
2023-10-17 12:18:14 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
nnkStmtList.newTree(
|
2023-10-16 08:38:11 +00:00
|
|
|
newVarStmt(closureSucceeded, ident"true"),
|
|
|
|
nTry
|
|
|
|
)
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc getName(node: NimNode): string {.compileTime.} =
|
|
|
|
case node.kind
|
2018-07-06 01:17:08 +00:00
|
|
|
of nnkSym:
|
2021-03-30 14:48:20 +00:00
|
|
|
return node.strVal
|
2018-05-16 08:22:34 +00:00
|
|
|
of nnkPostfix:
|
|
|
|
return node[1].strVal
|
|
|
|
of nnkIdent:
|
|
|
|
return node.strVal
|
|
|
|
of nnkEmpty:
|
|
|
|
return "anonymous"
|
|
|
|
else:
|
|
|
|
error("Unknown name.")
|
|
|
|
|
2019-08-15 16:35:42 +00:00
|
|
|
macro unsupported(s: static[string]): untyped =
|
|
|
|
error s
|
|
|
|
|
2023-01-19 06:52:11 +00:00
|
|
|
proc params2(someProc: NimNode): NimNode =
|
|
|
|
# until https://github.com/nim-lang/Nim/pull/19563 is available
|
|
|
|
if someProc.kind == nnkProcTy:
|
|
|
|
someProc[0]
|
|
|
|
else:
|
|
|
|
params(someProc)
|
|
|
|
|
2022-03-30 13:13:58 +00:00
|
|
|
proc cleanupOpenSymChoice(node: NimNode): NimNode {.compileTime.} =
|
|
|
|
# Replace every Call -> OpenSymChoice by a Bracket expr
|
|
|
|
# ref https://github.com/nim-lang/Nim/issues/11091
|
|
|
|
if node.kind in nnkCallKinds and
|
|
|
|
node[0].kind == nnkOpenSymChoice and node[0].eqIdent("[]"):
|
2022-05-05 09:05:04 +00:00
|
|
|
result = newNimNode(nnkBracketExpr)
|
|
|
|
for child in node[1..^1]:
|
|
|
|
result.add(cleanupOpenSymChoice(child))
|
2022-03-30 13:13:58 +00:00
|
|
|
else:
|
|
|
|
result = node.copyNimNode()
|
|
|
|
for child in node:
|
|
|
|
result.add(cleanupOpenSymChoice(child))
|
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
proc getAsyncCfg(prc: NimNode): tuple[raises: bool, async: bool, raisesTuple: NimNode] =
|
|
|
|
# reads the pragmas to extract the useful data
|
|
|
|
# and removes them
|
|
|
|
var
|
|
|
|
foundRaises = -1
|
|
|
|
foundAsync = -1
|
|
|
|
|
|
|
|
for index, pragma in pragma(prc):
|
|
|
|
if pragma.kind == nnkExprColonExpr and pragma[0] == ident "asyncraises":
|
|
|
|
foundRaises = index
|
|
|
|
elif pragma.eqIdent("async"):
|
|
|
|
foundAsync = index
|
|
|
|
elif pragma.kind == nnkExprColonExpr and pragma[0] == ident "raises":
|
|
|
|
warning("The raises pragma doesn't work on async procedure. " &
|
|
|
|
"Please remove it or use asyncraises instead")
|
|
|
|
|
|
|
|
result.raises = foundRaises >= 0
|
|
|
|
result.async = foundAsync >= 0
|
|
|
|
result.raisesTuple = nnkTupleConstr.newTree()
|
|
|
|
|
|
|
|
if foundRaises >= 0:
|
|
|
|
for possibleRaise in pragma(prc)[foundRaises][1]:
|
|
|
|
result.raisesTuple.add(possibleRaise)
|
|
|
|
if result.raisesTuple.len == 0:
|
|
|
|
result.raisesTuple = ident("void")
|
|
|
|
else:
|
|
|
|
when defined(chronosWarnMissingRaises):
|
|
|
|
warning("Async proc miss asyncraises")
|
|
|
|
const defaultException =
|
|
|
|
when defined(chronosStrictException): "CatchableError"
|
|
|
|
else: "Exception"
|
|
|
|
result.raisesTuple.add(ident(defaultException))
|
|
|
|
|
|
|
|
let toRemoveList = @[foundRaises, foundAsync].filterIt(it >= 0).sorted().reversed()
|
|
|
|
for toRemove in toRemoveList:
|
|
|
|
pragma(prc).del(toRemove)
|
|
|
|
|
|
|
|
proc isEmpty(n: NimNode): bool {.compileTime.} =
|
|
|
|
# true iff node recursively contains only comments or empties
|
|
|
|
case n.kind
|
|
|
|
of nnkEmpty, nnkCommentStmt: true
|
|
|
|
of nnkStmtList:
|
|
|
|
for child in n:
|
|
|
|
if not isEmpty(child): return false
|
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|
|
|
## This macro transforms a single procedure into a closure iterator.
|
|
|
|
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
2023-01-18 14:54:39 +00:00
|
|
|
if prc.kind notin {nnkProcTy, nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
|
|
|
|
error("Cannot transform " & $prc.kind & " into an async proc." &
|
2023-06-05 11:02:13 +00:00
|
|
|
" proc/method definition or lambda node expected.", prc)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-01-19 06:52:11 +00:00
|
|
|
let returnType = cleanupOpenSymChoice(prc.params2[0])
|
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
# Verify that the return type is a Future[T]
|
2023-01-19 06:52:11 +00:00
|
|
|
let baseType =
|
2023-06-05 11:02:13 +00:00
|
|
|
if returnType.kind == nnkEmpty:
|
|
|
|
ident "void"
|
|
|
|
elif not (
|
2023-10-17 12:18:14 +00:00
|
|
|
returnType.kind == nnkBracketExpr and
|
|
|
|
(eqIdent(returnType[0], "Future") or eqIdent(returnType[0], "InternalRaisesFuture"))):
|
2023-06-05 11:02:13 +00:00
|
|
|
error(
|
|
|
|
"Expected return type of 'Future' got '" & repr(returnType) & "'", prc)
|
|
|
|
return
|
2023-01-19 06:52:11 +00:00
|
|
|
else:
|
2023-06-05 11:02:13 +00:00
|
|
|
returnType[1]
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-06-05 11:02:13 +00:00
|
|
|
let
|
|
|
|
baseTypeIsVoid = baseType.eqIdent("void")
|
|
|
|
futureVoidType = nnkBracketExpr.newTree(ident "Future", ident "void")
|
2023-10-17 12:18:14 +00:00
|
|
|
(hasRaises, isAsync, raisesTuple) = getAsyncCfg(prc)
|
|
|
|
|
|
|
|
if hasRaises:
|
|
|
|
# Store `asyncraises` types in InternalRaisesFuture
|
|
|
|
prc.params2[0] = nnkBracketExpr.newTree(
|
|
|
|
newIdentNode("InternalRaisesFuture"),
|
|
|
|
baseType,
|
|
|
|
raisesTuple
|
|
|
|
)
|
|
|
|
elif baseTypeIsVoid:
|
|
|
|
# Adds the implicit Future[void]
|
|
|
|
prc.params2[0] =
|
|
|
|
newNimNode(nnkBracketExpr, prc).
|
|
|
|
add(newIdentNode("Future")).
|
|
|
|
add(newIdentNode("void"))
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
if prc.kind notin {nnkProcTy, nnkLambda}: # TODO: Nim bug?
|
|
|
|
prc.addPragma(newColonExpr(ident "stackTrace", ident "off"))
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# The proc itself doesn't raise
|
|
|
|
prc.addPragma(
|
|
|
|
nnkExprColonExpr.newTree(newIdentNode("raises"), nnkBracket.newTree()))
|
|
|
|
|
|
|
|
# `gcsafe` isn't deduced even though we require async code to be gcsafe
|
|
|
|
# https://github.com/nim-lang/RFCs/issues/435
|
|
|
|
prc.addPragma(newIdentNode("gcsafe"))
|
|
|
|
|
|
|
|
if isAsync == false: # `asyncraises` without `async`
|
|
|
|
# type InternalRaisesFutureRaises = `raisesTuple`
|
|
|
|
# `body`
|
|
|
|
prc.body = nnkStmtList.newTree(
|
|
|
|
nnkTypeSection.newTree(
|
|
|
|
nnkTypeDef.newTree(
|
|
|
|
ident"InternalRaisesFutureRaises",
|
|
|
|
newEmptyNode(),
|
|
|
|
raisesTuple
|
|
|
|
)
|
|
|
|
),
|
|
|
|
prc.body
|
|
|
|
)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
return prc
|
|
|
|
|
|
|
|
if prc.kind in {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo} and
|
|
|
|
not isEmpty(prc.body):
|
|
|
|
# don't do anything with forward bodies (empty)
|
2021-12-10 10:19:14 +00:00
|
|
|
let
|
2023-10-17 12:18:14 +00:00
|
|
|
prcName = prc.name.getName
|
|
|
|
setResultSym = ident "setResult"
|
|
|
|
procBody = prc.body.processBody(setResultSym, baseType)
|
2023-01-18 14:54:39 +00:00
|
|
|
internalFutureSym = ident "chronosInternalRetFuture"
|
2023-05-31 05:24:25 +00:00
|
|
|
internalFutureType =
|
2023-06-05 11:02:13 +00:00
|
|
|
if baseTypeIsVoid: futureVoidType
|
2023-05-31 05:24:25 +00:00
|
|
|
else: returnType
|
2023-06-05 11:02:13 +00:00
|
|
|
castFutureSym = nnkCast.newTree(internalFutureType, internalFutureSym)
|
2023-10-17 12:18:14 +00:00
|
|
|
resultIdent = ident "result"
|
|
|
|
|
|
|
|
resultDecl = nnkWhenStmt.newTree(
|
|
|
|
# when `baseType` is void:
|
|
|
|
nnkElifExpr.newTree(
|
|
|
|
nnkInfix.newTree(ident "is", baseType, ident "void"),
|
|
|
|
quote do:
|
|
|
|
template result: auto {.used.} =
|
|
|
|
{.fatal: "You should not reference the `result` variable inside" &
|
|
|
|
" a void async proc".}
|
|
|
|
),
|
|
|
|
# else:
|
|
|
|
nnkElseExpr.newTree(
|
|
|
|
newStmtList(
|
|
|
|
quote do: {.push warning[resultshadowed]: off.},
|
|
|
|
# var result {.used.}: `baseType`
|
|
|
|
# In the proc body, result may or may not end up being used
|
|
|
|
# depending on how the body is written - with implicit returns /
|
|
|
|
# expressions in particular, it is likely but not guaranteed that
|
|
|
|
# it is not used. Ideally, we would avoid emitting it in this
|
|
|
|
# case to avoid the default initializaiton. {.used.} typically
|
|
|
|
# works better than {.push.} which has a tendency to leak out of
|
|
|
|
# scope.
|
|
|
|
# TODO figure out if there's a way to detect `result` usage in
|
|
|
|
# the proc body _after_ template exapnsion, and therefore
|
|
|
|
# avoid creating this variable - one option is to create an
|
|
|
|
# addtional when branch witha fake `result` and check
|
|
|
|
# `compiles(procBody)` - this is not without cost though
|
|
|
|
nnkVarSection.newTree(nnkIdentDefs.newTree(
|
|
|
|
nnkPragmaExpr.newTree(
|
|
|
|
resultIdent,
|
|
|
|
nnkPragma.newTree(ident "used")),
|
|
|
|
baseType, newEmptyNode())
|
|
|
|
),
|
|
|
|
quote do: {.pop.},
|
2023-06-05 11:02:13 +00:00
|
|
|
)
|
|
|
|
)
|
2023-10-17 12:18:14 +00:00
|
|
|
)
|
2023-05-23 17:45:12 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# generates:
|
|
|
|
# template `setResultSym`(code: untyped) {.used.} =
|
|
|
|
# when typeof(code) is void: code
|
|
|
|
# else: `resultIdent` = code
|
|
|
|
#
|
|
|
|
# this is useful to handle implicit returns, but also
|
|
|
|
# to bind the `result` to the one we declare here
|
|
|
|
setResultDecl =
|
|
|
|
if baseTypeIsVoid: # shortcut for non-generic void
|
|
|
|
newEmptyNode()
|
|
|
|
else:
|
2023-10-16 08:38:11 +00:00
|
|
|
nnkTemplateDef.newTree(
|
|
|
|
setResultSym,
|
|
|
|
newEmptyNode(), newEmptyNode(),
|
|
|
|
nnkFormalParams.newTree(
|
|
|
|
newEmptyNode(),
|
|
|
|
nnkIdentDefs.newTree(
|
|
|
|
ident"code",
|
|
|
|
ident"untyped",
|
|
|
|
newEmptyNode(),
|
|
|
|
)
|
|
|
|
),
|
|
|
|
nnkPragma.newTree(ident"used"),
|
|
|
|
newEmptyNode(),
|
|
|
|
nnkWhenStmt.newTree(
|
|
|
|
nnkElifBranch.newTree(
|
2023-10-17 12:18:14 +00:00
|
|
|
nnkInfix.newTree(
|
|
|
|
ident"is", nnkTypeOfExpr.newTree(ident"code"), ident"void"),
|
2023-10-16 08:38:11 +00:00
|
|
|
ident"code"
|
|
|
|
),
|
|
|
|
nnkElse.newTree(
|
2023-10-17 12:18:14 +00:00
|
|
|
newAssignment(resultIdent, ident"code")
|
2023-10-16 08:38:11 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# Wrapping in try/finally ensures that early returns are handled properly
|
|
|
|
# and that `defer` is processed in the right scope
|
|
|
|
completeDecl = wrapInTryFinally(
|
|
|
|
castFutureSym, baseType,
|
|
|
|
if baseTypeIsVoid: procBody # shortcut for non-generic `void`
|
|
|
|
else: newCall(setResultSym, procBody),
|
|
|
|
raisesTuple
|
|
|
|
)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
closureBody = newStmtList(resultDecl, setResultDecl, completeDecl)
|
2023-05-31 05:24:25 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
internalFutureParameter = nnkIdentDefs.newTree(
|
|
|
|
internalFutureSym, newIdentNode("FutureBase"), newEmptyNode())
|
|
|
|
iteratorNameSym = genSym(nskIterator, $prcName)
|
|
|
|
closureIterator = newProc(
|
|
|
|
iteratorNameSym,
|
|
|
|
[newIdentNode("FutureBase"), internalFutureParameter],
|
|
|
|
closureBody, nnkIteratorDef)
|
2023-05-31 05:24:25 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
outerProcBody = newNimNode(nnkStmtList, prc.body)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# Copy comment for nimdoc
|
|
|
|
if prc.body.len > 0 and prc.body[0].kind == nnkCommentStmt:
|
|
|
|
outerProcBody.add(prc.body[0])
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
iteratorNameSym.copyLineInfo(prc)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body)
|
|
|
|
closureIterator.addPragma(newIdentNode("closure"))
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# `async` code must be gcsafe
|
|
|
|
closureIterator.addPragma(newIdentNode("gcsafe"))
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# Exceptions are caught inside the iterator and stored in the future
|
|
|
|
closureIterator.addPragma(nnkExprColonExpr.newTree(
|
|
|
|
newIdentNode("raises"),
|
|
|
|
nnkBracket.newTree()
|
|
|
|
))
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
outerProcBody.add(closureIterator)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# -> let resultFuture = newInternalRaisesFuture[T]()
|
|
|
|
# declared at the end to be sure that the closure
|
|
|
|
# doesn't reference it, avoid cyclic ref (#203)
|
|
|
|
let
|
|
|
|
retFutureSym = ident "resultFuture"
|
|
|
|
retFutureSym.copyLineInfo(prc)
|
|
|
|
# Do not change this code to `quote do` version because `instantiationInfo`
|
|
|
|
# will be broken for `newFuture()` call.
|
|
|
|
outerProcBody.add(
|
|
|
|
newLetStmt(
|
|
|
|
retFutureSym,
|
|
|
|
newCall(newTree(nnkBracketExpr, ident "newInternalRaisesFuture", baseType),
|
|
|
|
newLit(prcName))
|
|
|
|
)
|
|
|
|
)
|
|
|
|
# -> resultFuture.internalClosure = iterator
|
|
|
|
outerProcBody.add(
|
|
|
|
newAssignment(
|
|
|
|
newDotExpr(retFutureSym, newIdentNode("internalClosure")),
|
|
|
|
iteratorNameSym)
|
|
|
|
)
|
2019-08-15 16:35:42 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# -> futureContinue(resultFuture))
|
|
|
|
outerProcBody.add(
|
|
|
|
newCall(newIdentNode("futureContinue"), retFutureSym)
|
|
|
|
)
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
# -> return resultFuture
|
|
|
|
outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
|
2018-05-16 08:22:34 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
prc.body = outerProcBody
|
2023-01-18 14:54:39 +00:00
|
|
|
|
2023-10-17 12:18:14 +00:00
|
|
|
when chronosDumpAsync:
|
|
|
|
echo repr prc
|
2023-01-18 14:54:39 +00:00
|
|
|
prc
|
2018-05-16 08:22:34 +00:00
|
|
|
|
exception tracking (#166)
* exception tracking
This PR adds minimal exception tracking to chronos, moving the goalpost
one step further.
In particular, it becomes invalid to raise exceptions from `callSoon`
callbacks: this is critical for writing correct error handling because
there's no reasonable way that a user of chronos can possibly _reason_
about exceptions coming out of there: the event loop will be in an
indeterminite state when the loop is executing an _random_ callback.
As expected, there are several issues in the error handling of chronos:
in particular, it will end up in an inconsistent internal state whenever
the selector loop operations fail, because the internal state update
functions are not written in an exception-safe way. This PR turns this
into a Defect, which probably is not the optimal way of handling things
- expect more work to be done here.
Some API have no way of reporting back errors to callers - for example,
when something fails in the accept loop, there's not much it can do, and
no way to report it back to the user of the API - this has been fixed
with the new accept flow - the old one should be deprecated.
Finally, there is information loss in the API: in composite operations
like `poll` and `waitFor` there's no way to differentiate internal
errors from user-level errors originating from callbacks.
* store `CatchableError` in future
* annotate proc's with correct raises information
* `selectors2` to avoid non-CatchableError IOSelectorsException
* `$` should never raise
* remove unnecessary gcsafe annotations
* fix exceptions leaking out of timer waits
* fix some imports
* functions must signal raising the union of all exceptions across all
platforms to enable cross-platform code
* switch to unittest2
* add `selectors2` which supercedes the std library version and fixes
several exception handling issues in there
* fixes
* docs, platform-independent eh specifiers for some functions
* add feature flag for strict exception mode
also bump version to 3.0.0 - _most_ existing code should be compatible
with this version of exception handling but some things might need
fixing - callbacks, existing raises specifications etc.
* fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
|
|
|
template await*[T](f: Future[T]): untyped =
|
2019-08-15 13:32:46 +00:00
|
|
|
when declared(chronosInternalRetFuture):
|
2023-06-07 18:04:07 +00:00
|
|
|
chronosInternalRetFuture.internalChild = f
|
2023-05-31 05:24:25 +00:00
|
|
|
# `futureContinue` calls the iterator generated by the `async`
|
|
|
|
# transformation - `yield` gives control back to `futureContinue` which is
|
|
|
|
# responsible for resuming execution once the yielded future is finished
|
2023-06-07 18:04:07 +00:00
|
|
|
yield chronosInternalRetFuture.internalChild
|
2023-05-31 05:24:25 +00:00
|
|
|
# `child` released by `futureContinue`
|
2023-10-17 12:18:14 +00:00
|
|
|
cast[type(f)](chronosInternalRetFuture.internalChild).internalCheckComplete()
|
|
|
|
|
exception tracking (#166)
* exception tracking
This PR adds minimal exception tracking to chronos, moving the goalpost
one step further.
In particular, it becomes invalid to raise exceptions from `callSoon`
callbacks: this is critical for writing correct error handling because
there's no reasonable way that a user of chronos can possibly _reason_
about exceptions coming out of there: the event loop will be in an
indeterminite state when the loop is executing an _random_ callback.
As expected, there are several issues in the error handling of chronos:
in particular, it will end up in an inconsistent internal state whenever
the selector loop operations fail, because the internal state update
functions are not written in an exception-safe way. This PR turns this
into a Defect, which probably is not the optimal way of handling things
- expect more work to be done here.
Some API have no way of reporting back errors to callers - for example,
when something fails in the accept loop, there's not much it can do, and
no way to report it back to the user of the API - this has been fixed
with the new accept flow - the old one should be deprecated.
Finally, there is information loss in the API: in composite operations
like `poll` and `waitFor` there's no way to differentiate internal
errors from user-level errors originating from callbacks.
* store `CatchableError` in future
* annotate proc's with correct raises information
* `selectors2` to avoid non-CatchableError IOSelectorsException
* `$` should never raise
* remove unnecessary gcsafe annotations
* fix exceptions leaking out of timer waits
* fix some imports
* functions must signal raising the union of all exceptions across all
platforms to enable cross-platform code
* switch to unittest2
* add `selectors2` which supercedes the std library version and fixes
several exception handling issues in there
* fixes
* docs, platform-independent eh specifiers for some functions
* add feature flag for strict exception mode
also bump version to 3.0.0 - _most_ existing code should be compatible
with this version of exception handling but some things might need
fixing - callbacks, existing raises specifications etc.
* fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
|
|
|
when T isnot void:
|
2023-08-09 14:27:17 +00:00
|
|
|
cast[type(f)](chronosInternalRetFuture.internalChild).value()
|
2019-08-15 13:32:46 +00:00
|
|
|
else:
|
2019-08-15 16:35:42 +00:00
|
|
|
unsupported "await is only available within {.async.}"
|
2019-08-15 13:32:46 +00:00
|
|
|
|
|
|
|
template awaitne*[T](f: Future[T]): Future[T] =
|
|
|
|
when declared(chronosInternalRetFuture):
|
2023-06-07 18:04:07 +00:00
|
|
|
chronosInternalRetFuture.internalChild = f
|
|
|
|
yield chronosInternalRetFuture.internalChild
|
|
|
|
cast[type(f)](chronosInternalRetFuture.internalChild)
|
2019-08-15 13:32:46 +00:00
|
|
|
else:
|
2019-08-15 16:35:42 +00:00
|
|
|
unsupported "awaitne is only available within {.async.}"
|
2019-07-19 10:06:32 +00:00
|
|
|
|
2018-05-16 08:22:34 +00:00
|
|
|
macro async*(prc: untyped): untyped =
|
|
|
|
## Macro which processes async procedures into the appropriate
|
|
|
|
## iterators and yield statements.
|
|
|
|
if prc.kind == nnkStmtList:
|
2023-01-19 06:52:11 +00:00
|
|
|
result = newStmtList()
|
2018-05-16 08:22:34 +00:00
|
|
|
for oneProc in prc:
|
2023-10-17 12:18:14 +00:00
|
|
|
oneProc.addPragma(ident"async")
|
2018-05-16 08:22:34 +00:00
|
|
|
result.add asyncSingleProc(oneProc)
|
|
|
|
else:
|
2023-10-17 12:18:14 +00:00
|
|
|
prc.addPragma(ident"async")
|
|
|
|
result = asyncSingleProc(prc)
|
|
|
|
|
|
|
|
macro asyncraises*(possibleExceptions, prc: untyped): untyped =
|
|
|
|
# Add back the pragma and let asyncSingleProc handle it
|
|
|
|
# Exerimental / subject to change and/or removal
|
|
|
|
if prc.kind == nnkStmtList:
|
|
|
|
result = newStmtList()
|
|
|
|
for oneProc in prc:
|
|
|
|
oneProc.addPragma(nnkExprColonExpr.newTree(
|
|
|
|
ident"asyncraises",
|
|
|
|
possibleExceptions
|
|
|
|
))
|
|
|
|
result.add asyncSingleProc(oneProc)
|
|
|
|
else:
|
|
|
|
prc.addPragma(nnkExprColonExpr.newTree(
|
|
|
|
ident"asyncraises",
|
|
|
|
possibleExceptions
|
|
|
|
))
|
2018-05-16 08:22:34 +00:00
|
|
|
result = asyncSingleProc(prc)
|