Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async raises of #327

Draft
wants to merge 37 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
473a33c
Exception tracking v2
Menduist Jan 3, 2022
45d7ba8
some fixes
Menduist Jan 3, 2022
896df6b
Nim 1.2 compat
Menduist Jan 3, 2022
fde7649
simpler things
Menduist Jan 3, 2022
8f765df
Fixes for libp2p
Menduist Jan 3, 2022
0c8d9c1
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Jan 5, 2022
3f53d01
Fixes for strictException
Menduist Jan 5, 2022
f8a55ae
better await exception check
Menduist Jan 6, 2022
1e8285b
Fix for template async proc
Menduist Jan 6, 2022
b9c229c
make async work with procTy
Menduist Jan 12, 2022
6589cd2
FuturEx is now a ref object type
Menduist Jan 15, 2022
8c36af8
add tests
Menduist Jan 15, 2022
c6075f8
update test
Menduist Jan 17, 2022
69fcf74
update readme
Menduist Jan 17, 2022
72e7956
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Feb 24, 2022
0f496db
Switch to asyncraises pragma
Menduist Feb 24, 2022
79fa1ca
Address tests review comments
Menduist Feb 24, 2022
e12147c
Rename FuturEx to RaiseTrackingFuture
Menduist Feb 24, 2022
e6faffd
Fix typo
Menduist Feb 24, 2022
673d03f
Split asyncraises into async, asyncraises
Menduist Feb 25, 2022
0f46a06
Add -d:chronosWarnMissingRaises
Menduist Feb 25, 2022
05e94c8
Add comment to RaiseTrackingFuture
Menduist Feb 25, 2022
28ca5de
Allow standalone asyncraises
Menduist Mar 4, 2022
f93409a
CheckedFuture.fail type checking
Menduist Mar 14, 2022
b09d501
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Mar 30, 2022
c3f8882
First cleanup
Menduist Mar 30, 2022
d91151a
Remove useless line
Menduist Mar 30, 2022
b9a20be
Review comments
Menduist Jun 3, 2022
9883fd2
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Jun 3, 2022
20716d2
nimble: Remove #head from unittest2
Menduist Jun 3, 2022
d5eed32
Remove implict raises: CancelledError
Menduist Jun 7, 2022
6c84b6f
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Jun 14, 2022
e69d528
Merge remote-tracking branch 'origin/master' into raisetracking
Menduist Jul 22, 2022
046722e
Move checkFutureExceptions to asyncfutures2
Menduist Jul 22, 2022
c3aa399
Small refacto
Menduist Sep 7, 2022
ea08a8f
Async raises of, first pass
Menduist Sep 7, 2022
1062953
Working asyncraisesof
Menduist Nov 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ feet, in a certain section, is to not use `await` in it.

### Error handling

Exceptions inheriting from `CatchableError` are caught by hidden `try` blocks
and placed in the `Future.error` field, changing the future's status to
`Failed`.
Exceptions inheriting from `Exception` (or `CatchableError` when
`-d:chronosStrictException` is enabled) are caught by hidden `try` blocks
and placed in the `Future.error` field, changing the future status to `Failed`.

When a future is awaited, that exception is re-raised, only to be caught again
by a hidden `try` block in the calling async procedure. That's how these
Expand Down Expand Up @@ -211,6 +211,42 @@ originating from tasks on the dispatcher queue. It is however possible that
`Defect` that happen in tasks bubble up through `poll` as these are not caught
by the transformation.

#### Checked exceptions

By specifying a `asyncraises` list to an async procedure, you can check which
exceptions can be thrown by it.
```nim
proc p1(): Future[void] {.async, asyncraises: [IOError].} =
assert not (compiles do: raise newException(ValueError, "uh-uh"))
raise newException(IOError, "works") # Or any child of IOError
```

Under the hood, the return type of `p1` will be rewritten to another type,
which will convey raises informations to await.

```nim
proc p2(): Future[void] {.async, asyncraises: [IOError].} =
await p1() # Works, because await knows that p1
# can only raise IOError
```

The hidden type (`RaiseTrackingFuture`) is implicitely convertible into a Future.
However, it may causes issues when creating callback or methods
```nim
proc p3(): Future[void] {.async, asyncraises: [IOError].} =
let fut: Future[void] = p1() # works
assert not compiles(await fut) # await lost informations about raises,
# so it can raise anything

# Callbacks
assert not(compiles do: let cb1: proc(): Future[void] = p1) # doesn't work
let cb2: proc(): Future[void] {.async, asyncraises: [IOError].} = p1 # works
assert not(compiles do:
type c = proc(): Future[void] {.async, asyncraises: [IOError, ValueError].}
let cb3: c = p1 # doesn't work, the raises must match _exactly_
)
```

### Platform independence

Several functions in `chronos` are backed by the operating system, such as
Expand Down
82 changes: 82 additions & 0 deletions chronos/asyncfutures2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import std/[os, tables, strutils, heapqueue, options, deques, sequtils]
import stew/base10
import ./srcloc
import macros
export srcloc

when defined(nimHasStacktracesModule):
Expand Down Expand Up @@ -60,6 +61,12 @@ type
closure*: iterator(f: Future[T]): FutureBase {.raises: [Defect, CatchableError, Exception], gcsafe.}
value: T ## Stored value

RaiseTrackingFuture*[T, E] = ref object of Future[T]
## Future with a tuple of possible exception types
## eg RaiseTrackingFuture[void, (ValueError, OSError)]
## Will be injected by `asyncraises`, should generally
## not be used manually

FutureStr*[T] = ref object of Future[T]
## Future to hold GC strings
gcholder*: string
Expand Down Expand Up @@ -109,6 +116,9 @@ template setupFutureBase(loc: ptr SrcLoc) =
proc newFutureImpl[T](loc: ptr SrcLoc): Future[T] =
setupFutureBase(loc)

proc newRaiseTrackingFutureImpl[T, E](loc: ptr SrcLoc): RaiseTrackingFuture[T, E] =
setupFutureBase(loc)

proc newFutureSeqImpl[A, B](loc: ptr SrcLoc): FutureSeq[A, B] =
setupFutureBase(loc)

Expand All @@ -122,6 +132,19 @@ template newFuture*[T](fromProc: static[string] = ""): Future[T] =
## that this future belongs to, is a good habit as it helps with debugging.
newFutureImpl[T](getSrcLocation(fromProc))

macro getFutureExceptions(T: typedesc): untyped =
if getTypeInst(T)[1].len > 2:
getTypeInst(T)[1][2]
else:
ident"void"

template newRaiseTrackingFuture*[T](fromProc: static[string] = ""): auto =
## Creates a new future.
##
## Specifying ``fromProc``, which is a string specifying the name of the proc
## that this future belongs to, is a good habit as it helps with debugging.
newRaiseTrackingFutureImpl[T, getFutureExceptions(typeof(result))](getSrcLocation(fromProc))

template newFutureSeq*[A, B](fromProc: static[string] = ""): FutureSeq[A, B] =
## Create a new future which can hold/preserve GC sequence until future will
## not be completed.
Expand Down Expand Up @@ -251,6 +274,65 @@ template fail*[T](future: Future[T], error: ref CatchableError) =
## Completes ``future`` with ``error``.
fail(future, error, getSrcLocation())

macro checkFailureType(future, error: typed): untyped =
let e = getTypeInst(future)[2]
let types = getType(e)

if types.eqIdent("void"):
error("Can't raise exceptions on this Future")

expectKind(types, nnkBracketExpr)
expectKind(types[0], nnkSym)
assert types[0].strVal == "tuple"
assert types.len > 1

let toMatch =
if getTypeInst(error).kind == nnkRefTy:
getTypeInst(error)[0]
elif getTypeInst(error).kind == nnkBracketExpr:
getTypeInst(error)[1]
else:
error("Unhandled error type " & $getTypeInst(error).kind)
nil

# Can't find a way to check `is` in the macro. (sameType doesn't
# work for inherited objects). Dirty hack here, for [IOError, OSError],
# this will generate:
#
# static:
# if not((`toMatch` is IOError) or (`toMatch` is OSError)
# or (`toMatch` is CancelledError) or false):
# raiseAssert("Can't fail with `toMatch`, only [IOError, OSError] is allowed")
var typeChecker = ident"false"

for errorType in types[1..^1]:
typeChecker = newCall("or", typeChecker, newCall("is", toMatch, errorType))
typeChecker = newCall(
"or", typeChecker,
newCall("is", toMatch, ident"CancelledError"))

let errorMsg = "Can't fail with " & repr(toMatch) & ". Only " & repr(types[1..^1]) & " allowed"

result = nnkStaticStmt.newNimNode(lineInfoFrom=error).add(
quote do:
if not(`typeChecker`):
raiseAssert(`errorMsg`)
)

template fail*[T, E](future: RaiseTrackingFuture[T, E], error: ref CatchableError) =
checkFailureType(future, error)
fail(future, error, getSrcLocation())

macro checkMultipleFailureType(future, errors: typed): untyped =
result = nnkStmtList.newTree()
for error in getTypeInst(errors):
let err2 = ident(error.strVal)
result.add(quote do: checkFailureType(`future`, `err2`))

template fail*[T1, E1, T2, E2](future: RaiseTrackingFuture[T1, E1], failedFuture: RaiseTrackingFuture[T2, E2]) =
checkMultipleFailureType(future, E2)
fail(Future[T1](future), failedFuture.error, getSrcLocation())

template newCancelledError(): ref CancelledError =
(ref CancelledError)(msg: "Future operation cancelled!")

Expand Down
Loading