Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 39 additions & 9 deletions lib/pure/asyncfutures.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# distribution, for details about the copyright.
#

import std/[os, sets, tables, strutils, times, heapqueue, options, deques, cstrutils, typetraits]
import std/[os, macros, sets, tables, strutils, strformat, times, heapqueue, options, deques, cstrutils, typetraits]

import system/stacktraces

Expand Down Expand Up @@ -39,6 +39,10 @@ type

FutureVar*[T] = distinct Future[T]

FutureEx*[T, E] = distinct Future[T]

InternalFuture*[T] = ref object of Future[T]

FutureError* = object of Defect
cause*: FutureBase

Expand Down Expand Up @@ -126,6 +130,9 @@ proc newFuture*[T](fromProc: string = "unspecified"): owned(Future[T]) =
setupFutureBase(fromProc)
when isFutureLoggingEnabled: logFutureStart(result)

proc newInternalFuture*[T](fromProc: string = "unspecified"): owned(InternalFuture[T]) =
setupFutureBase(fromProc)

proc newFutureVar*[T](fromProc = "unspecified"): owned(FutureVar[T]) =
## Create a new `FutureVar`. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
Expand All @@ -141,7 +148,7 @@ proc clean*[T](future: FutureVar[T]) =
Future[T](future).finished = false
Future[T](future).error = nil

proc checkFinished[T](future: Future[T]) =
proc checkFinished[T](future: Future[T]) {.raises: [].} =
## Checks whether `future` is finished. If it is then raises a
## `FutureError`.
when not defined(release):
Expand All @@ -162,11 +169,13 @@ proc checkFinished[T](future: Future[T]) =
err.cause = future
raise err

proc call(callbacks: var CallbackList) =
proc call(callbacks: var CallbackList) {.raises: [].} =
var current = callbacks
while true:
if not current.function.isNil:
callSoon(current.function)
# XXX make callbacks {.raise: [].}
{.cast(raises: []).}:
callSoon(current.function)

if current.next.isNil:
break
Expand Down Expand Up @@ -293,12 +302,11 @@ template getFilenameProcname(entry: StackTraceEntry): (string, string) =
else:
($entry.filename, $entry.procname)

proc format(entry: StackTraceEntry): string =
proc format(entry: StackTraceEntry): string {.raises: [].} =
let (filename, procname) = getFilenameProcname(entry)
let left = "$#($#)" % [filename, $entry.line]
result = spaces(2) & "$# $#\n" % [left, procname]
result = &"{spaces(2)}{filename}({$entry.line}) {procname}\n"

proc isInternal(entry: StackTraceEntry): bool =
proc isInternal(entry: StackTraceEntry): bool {.raises: [].} =
# --excessiveStackTrace:off
const internals = [
"asyncdispatch.nim",
Expand All @@ -311,7 +319,7 @@ proc isInternal(entry: StackTraceEntry): bool =
return true
return false

proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string =
proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string {.raises: [].} =
result = ""
when defined(nimStackTraceOverride):
let entries = addDebuggingInfo(stackTraceEntries)
Expand Down Expand Up @@ -384,6 +392,28 @@ proc read*[T](future: Future[T] | FutureVar[T]): lent T =
proc read*(future: Future[void] | FutureVar[void]) =
readImpl(future, void)

macro readTrackedImpl(future: FutureEx): untyped =
# XXX refactor readImpl
let t = getTypeInst(future)[1]
let e = getTypeInst(future)[2]
let types = getType(e)
var raisesList = newNimNode(nnkBracket)
for r in types[1..^1]:
raisesList.add(r)
#echo repr raisesList
#echo repr t
let raisesCast = quote do:
cast(raises: `raisesList`)
quote do:
{.`raisesCast`.}:
readImpl(`future`, `t`)

proc read*[T, E](future: FutureEx[T, E]): lent T =
readTrackedImpl(future)

proc read*[E](future: FutureEx[void, E]) =
readTrackedImpl(future)

proc readError*[T](future: Future[T]): ref Exception =
## Retrieves the exception stored in `future`.
##
Expand Down
116 changes: 104 additions & 12 deletions lib/pure/asyncmacro.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

## Implements the `async` and `multisync` macros for `asyncdispatch`.

import std/[macros, strutils, asyncfutures]
import std/[macros, strutils, asyncfutures, effecttraits]

type
Context = ref object
Expand Down Expand Up @@ -49,9 +49,10 @@ template createCb(futTyp, strName, identName, futureVarCompletions: untyped) =
except:
futureVarCompletions
if fut.finished:
# Take a look at tasyncexceptions for the bug which this fixes.
# That test explains it better than I can here.
raise
{.cast(raises: []).}:
# Take a look at tasyncexceptions for the bug which this fixes.
# That test explains it better than I can here.
raise
else:
fut.fail(getCurrentException())
{.pop.}
Expand Down Expand Up @@ -158,6 +159,50 @@ proc verifyReturnType(typeName: string, node: NimNode = nil) =
error("Expected return type of 'Future' got '$1'" %
typeName, node)

proc isAsyncPrc(n: NimNode): bool =
if n.getTypeInst.kind notin {nnkProcTy} + RoutineNodes:
return false
return repr(n.getTypeInst.params[0][0]) == "InternalFuture"

proc hasRaises(n: NimNode): bool =
if n.getTypeInst.kind notin {nnkProcTy} + RoutineNodes:
return false
return repr(getRaisesList(n)) != "[UncomputedEffects]"

macro withRaises[T](f: Future[T], body: untyped): untyped =
#echo repr f.kind
# XXX support more cases
let prcSym = case f.kind
of nnkSym:
if f.getImpl.kind == nnkIdentDefs and f.getImpl[^1].kind == nnkCall:
#echo repr f.getImpl[^1][0]
f.getImpl[^1][0]
else:
nil
of nnkCall:
if f[0].kind == nnkSym:
f[0]
elif f[0].kind == nnkDotExpr and f[0][1].kind == nnkSym:
f[0][1]
else:
nil
else:
nil
#echo repr prcSym
if prcSym != nil and isAsyncPrc(prcSym) and hasRaises(prcSym):
let raisesList = getRaisesList(prcSym)
var raisesListTyp = newNimNode(nnkBracket)
if raisesList.len > 0:
for r in raisesList[1..^1]:
raisesListTyp.add(r)
let raisesCast = quote do:
cast(raises: `raisesListTyp`)
quote do:
{.`raisesCast`.}:
`body`
else:
body

template await*(f: typed): untyped {.used.} =
static:
error "await expects Future[T], got " & $typeof(f)
Expand All @@ -166,12 +211,10 @@ template await*[T](f: Future[T]): auto {.used.} =
when not defined(nimHasTemplateRedefinitionPragma):
{.pragma: redefine.}
template yieldFuture {.redefine.} = yield FutureBase()

when compiles(yieldFuture):
var internalTmpFuture: FutureBase = f
yield internalTmpFuture
{.line: instantiationInfo(fullPaths = true).}:
(cast[typeof(f)](internalTmpFuture)).read()
(cast[typeof(f)](internalTmpFuture)).read()
else:
macro errorAsync(futureError: Future[T]) =
error(
Expand All @@ -180,13 +223,34 @@ template await*[T](f: Future[T]): auto {.used.} =
futureError)
errorAsync(f)

template await*[T, E](f: FutureEx[T, E]): untyped =
template yieldFuture = yield FutureBase()
when compiles(yieldFuture):
var internalTmpFuture: FutureBase = Future[T](f)
yield internalTmpFuture
{.line: instantiationInfo(fullPaths = true).}:
cast[typeof(f)](internalTmpFuture).read()
else:
{.error: "await is only available within {.async.}".}

template await*[T](f: InternalFuture[T]): auto {.used.} =
template yieldFuture = yield FutureBase()
when compiles(yieldFuture):
var internalTmpFuture: FutureBase = f
yield internalTmpFuture
{.line: instantiationInfo(fullPaths = true).}:
withRaises f:
cast[typeof(f)](internalTmpFuture).read()
else:
{.error: "await is only available within {.async.}".}

proc asyncSingleProc(prc: NimNode): NimNode =
## This macro transforms a single procedure into a closure iterator.
## The `async` macro supports a stmtList holding multiple async procedures.
if prc.kind == nnkProcTy:
result = prc
if prc[0][0].kind == nnkEmpty:
result[0][0] = quote do: Future[void]
result[0][0] = quote do: asynced Future[void]
return result

if prc.kind in RoutineNodes and prc.name.kind != nnkEmpty:
Expand Down Expand Up @@ -245,6 +309,7 @@ proc asyncSingleProc(prc: NimNode): NimNode =
# -> <proc_body>
# -> complete(retFutParam, result)
var iteratorNameSym = genSym(nskIterator, $prcName & " (Async)")
iteratorNameSym.copyLineInfo(prc)
var needsCompletionSym = genSym(nskVar, "needsCompletion")
var ctx = Context()
var procBody = processBody(ctx, prc.body, needsCompletionSym, retFutParamSym, futureVarIdents)
Expand Down Expand Up @@ -303,7 +368,7 @@ proc asyncSingleProc(prc: NimNode): NimNode =
newVarStmt(retFutureSym,
newCall(
newNimNode(nnkBracketExpr, prc.body).add(
newIdentNode("newFuture"),
newIdentNode("newInternalFuture"),
subRetType),
newLit(prcName)))) # Get type from return type of this proc

Expand All @@ -316,13 +381,16 @@ proc asyncSingleProc(prc: NimNode): NimNode =
result = prc
# Add discardable pragma.
if returnType.kind == nnkEmpty:
# xxx consider removing `owned`? it's inconsistent with non-void case
result.params[0] = quote do: owned(Future[void])
result.params[0] = quote do: asynced Future[void]
else:
result.params[0] = quote do: asynced `returnType`

# based on the yglukhov's patch to chronos: https://github.com/status-im/nim-chronos/pull/47
if procBody.kind != nnkEmpty:
let asynctrack = ident"asynctrack"
body2.add quote do:
`outerProcBody`
block `asynctrack`:
`outerProcBody`
result.body = body2

macro async*(prc: untyped): untyped =
Expand Down Expand Up @@ -394,3 +462,27 @@ macro multisync*(prc: untyped): untyped =
result = newStmtList()
result.add(asyncSingleProc(asyncPrc))
result.add(sync)

macro toFutureEx*(prc: typed): untyped =
template check(cond: untyped): untyped =
if not cond:
error("async proc call expected", prc)
check prc.kind == nnkCall
check prc[0].kind == nnkSym
check isAsyncPrc(prc[0])
let procImpl = getTypeImpl(prc[0])
check procImpl.kind == nnkProcTy
let retTyp = procImpl.params[0]
let baseTyp = retTyp[1]
let raisesList = getRaisesList(prc[0])
let exTyp = if raisesList.len == 0:
newIdentNode("void")
else:
newNimNode(nnkTupleConstr)
for r in raisesList:
exTyp.add r
result = quote do:
FutureEx[`baseTyp`, `exTyp`](`prc`)

template asynced*[T](f: typedesc[Future[T]]): untyped =
InternalFuture[T]
7 changes: 3 additions & 4 deletions nimdoc/testproject/expected/testproject.html
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,7 @@ <h1><a class="toc-backref" href="#12">Procs</a></h1>
</div>
<div id="asyncFun1-procs-all">
<div id="asyncFun1">
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun1"><span class="Identifier">asyncFun1</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">int</span><span class="Other">]</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span>
<span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">Exception</span><span class="Other">,</span> <span class="Identifier">ValueError</span><span class="Other">]</span><span class="Other">,</span>
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun1"><span class="Identifier">asyncFun1</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">int</span><span class="Other">]</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span> <span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">RootEffect</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">forbids</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd>

Expand All @@ -572,7 +571,7 @@ <h1><a class="toc-backref" href="#12">Procs</a></h1>
</div>
<div id="asyncFun2-procs-all">
<div id="asyncFun2">
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun2"><span class="Identifier">asyncFun2</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">owned</span><span class="Other">(</span><span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">void</span><span class="Other">]</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span> <span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">Exception</span><span class="Other">]</span><span class="Other">,</span>
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun2"><span class="Identifier">asyncFun2</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">owned</span><span class="Other">(</span><span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">void</span><span class="Other">]</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span> <span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">RootEffect</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">forbids</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd>

Expand All @@ -584,7 +583,7 @@ <h1><a class="toc-backref" href="#12">Procs</a></h1>
</div>
<div id="asyncFun3-procs-all">
<div id="asyncFun3">
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun3"><span class="Identifier">asyncFun3</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">owned</span><span class="Other">(</span><span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">void</span><span class="Other">]</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span> <span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">Exception</span><span class="Other">]</span><span class="Other">,</span>
<dt><pre><span class="Keyword">proc</span> <a href="#asyncFun3"><span class="Identifier">asyncFun3</span></a><span class="Other">(</span><span class="Other">)</span><span class="Other">:</span> <span class="Identifier">owned</span><span class="Other">(</span><span class="Identifier">Future</span><span class="Other">[</span><span class="Identifier">void</span><span class="Other">]</span><span class="Other">)</span> {.<span><span class="Other pragmadots">...</span></span><span class="pragmawrap"><span class="Identifier">stackTrace</span><span class="Other">:</span> <span class="DecNumber">false</span><span class="Other">,</span> <span class="Identifier">raises</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span><span class="Other">,</span>
<span class="Identifier">tags</span><span class="Other">:</span> <span class="Other">[</span><span class="Identifier">RootEffect</span><span class="Other">]</span><span class="Other">,</span> <span class="Identifier">forbids</span><span class="Other">:</span> <span class="Other">[</span><span class="Other">]</span></span>.}</pre></dt>
<dd>

Expand Down
Loading