mirror of
https://github.com/status-im/nim-chronos.git
synced 2025-02-09 09:53:27 +00:00
Improve ram usage (#243)
Remove cyclic references of {.async.} Futures, allowing them to be picked up by the regular refc instead of Mark and Sweep
This commit is contained in:
parent
7ca85ddadc
commit
7dc58d42b6
@ -54,6 +54,10 @@ type
|
|||||||
# How much refactoring is needed to make this a regular non-ref type?
|
# How much refactoring is needed to make this a regular non-ref type?
|
||||||
# Obviously, it will still be allocated on the heap when necessary.
|
# Obviously, it will still be allocated on the heap when necessary.
|
||||||
Future*[T] = ref object of FutureBase ## Typed future.
|
Future*[T] = ref object of FutureBase ## Typed future.
|
||||||
|
when defined(chronosStrictException):
|
||||||
|
closure*: iterator(f: Future[T]): FutureBase {.raises: [Defect, CatchableError], gcsafe.}
|
||||||
|
else:
|
||||||
|
closure*: iterator(f: Future[T]): FutureBase {.raises: [Defect, CatchableError, Exception], gcsafe.}
|
||||||
value: T ## Stored value
|
value: T ## Stored value
|
||||||
|
|
||||||
FutureStr*[T] = ref object of Future[T]
|
FutureStr*[T] = ref object of Future[T]
|
||||||
@ -351,6 +355,46 @@ proc `cancelCallback=`*[T](future: Future[T], cb: CallbackFunc) =
|
|||||||
## This callback will be called immediately as ``future.cancel()`` invoked.
|
## This callback will be called immediately as ``future.cancel()`` invoked.
|
||||||
future.cancelcb = cb
|
future.cancelcb = cb
|
||||||
|
|
||||||
|
{.push stackTrace: off.}
|
||||||
|
proc internalContinue[T](fut: pointer) {.gcsafe, raises: [Defect].}
|
||||||
|
|
||||||
|
proc futureContinue*[T](fut: Future[T]) {.gcsafe, raises: [Defect].} =
|
||||||
|
# Used internally by async transformation
|
||||||
|
try:
|
||||||
|
if not(fut.closure.finished()):
|
||||||
|
var next = fut.closure(fut)
|
||||||
|
# Continue while the yielded future is already finished.
|
||||||
|
while (not next.isNil()) and next.finished():
|
||||||
|
next = fut.closure(fut)
|
||||||
|
if fut.closure.finished():
|
||||||
|
break
|
||||||
|
|
||||||
|
if fut.closure.finished():
|
||||||
|
fut.closure = nil
|
||||||
|
if next == nil:
|
||||||
|
if not(fut.finished()):
|
||||||
|
raiseAssert "Async procedure (" & ($fut.location[LocCreateIndex]) & ") yielded `nil`, " &
|
||||||
|
"are you await'ing a `nil` Future?"
|
||||||
|
else:
|
||||||
|
GC_ref(fut)
|
||||||
|
next.addCallback(internalContinue[T], cast[pointer](fut))
|
||||||
|
except CancelledError:
|
||||||
|
fut.cancelAndSchedule()
|
||||||
|
except CatchableError as exc:
|
||||||
|
fut.fail(exc)
|
||||||
|
except Exception as exc:
|
||||||
|
if exc of Defect:
|
||||||
|
raise (ref Defect)(exc)
|
||||||
|
|
||||||
|
fut.fail((ref ValueError)(msg: exc.msg, parent: exc))
|
||||||
|
|
||||||
|
proc internalContinue[T](fut: pointer) {.gcsafe, raises: [Defect].} =
|
||||||
|
let asFut = cast[Future[T]](fut)
|
||||||
|
GC_unref(asFut)
|
||||||
|
futureContinue(asFut)
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
|
||||||
template getFilenameProcname(entry: StackTraceEntry): (string, string) =
|
template getFilenameProcname(entry: StackTraceEntry): (string, string) =
|
||||||
when compiles(entry.filenameStr) and compiles(entry.procnameStr):
|
when compiles(entry.filenameStr) and compiles(entry.procnameStr):
|
||||||
# We can't rely on "entry.filename" and "entry.procname" still being valid
|
# We can't rely on "entry.filename" and "entry.procname" still being valid
|
||||||
@ -375,8 +419,8 @@ proc getHint(entry: StackTraceEntry): string =
|
|||||||
if cmpIgnoreStyle(filename, "asyncdispatch.nim") == 0:
|
if cmpIgnoreStyle(filename, "asyncdispatch.nim") == 0:
|
||||||
return "Processes asynchronous completion events"
|
return "Processes asynchronous completion events"
|
||||||
|
|
||||||
if procname.endsWith("_continue"):
|
if procname == "internalContinue":
|
||||||
if cmpIgnoreStyle(filename, "asyncmacro.nim") == 0:
|
if cmpIgnoreStyle(filename, "asyncfutures.nim") == 0:
|
||||||
return "Resumes an async procedure"
|
return "Resumes an async procedure"
|
||||||
|
|
||||||
proc `$`(stackTraceEntries: seq[StackTraceEntry]): string =
|
proc `$`(stackTraceEntries: seq[StackTraceEntry]): string =
|
||||||
|
@ -15,89 +15,6 @@ proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
|
|||||||
if node[0].kind == nnkStmtList:
|
if node[0].kind == nnkStmtList:
|
||||||
result = skipUntilStmtList(node[0])
|
result = skipUntilStmtList(node[0])
|
||||||
|
|
||||||
# proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
|
|
||||||
# result = node
|
|
||||||
# if node[0].kind == nnkStmtList:
|
|
||||||
# result = node[0]
|
|
||||||
when defined(chronosStrictException):
|
|
||||||
template createCb(retFutureSym, iteratorNameSym,
|
|
||||||
strName, identName: untyped) =
|
|
||||||
bind finished
|
|
||||||
|
|
||||||
var nameIterVar = iteratorNameSym
|
|
||||||
{.push stackTrace: off.}
|
|
||||||
var identName: proc(udata: pointer) {.gcsafe, raises: [Defect].}
|
|
||||||
identName = proc(udata: pointer) {.gcsafe, raises: [Defect].} =
|
|
||||||
try:
|
|
||||||
# If the compiler complains about unlisted exception here, it's usually
|
|
||||||
# because you're calling a callback or forward declaration in your code
|
|
||||||
# for which the compiler cannot deduce raises signatures - make sure
|
|
||||||
# to annotate both forward declarations and `proc` types with `raises`!
|
|
||||||
if not(nameIterVar.finished()):
|
|
||||||
var next = nameIterVar()
|
|
||||||
# Continue while the yielded future is already finished.
|
|
||||||
while (not next.isNil()) and next.finished():
|
|
||||||
next = nameIterVar()
|
|
||||||
if nameIterVar.finished():
|
|
||||||
break
|
|
||||||
|
|
||||||
if next == nil:
|
|
||||||
if not(retFutureSym.finished()):
|
|
||||||
const msg = "Async procedure (&" & strName & ") yielded `nil`, " &
|
|
||||||
"are you await'ing a `nil` Future?"
|
|
||||||
raiseAssert msg
|
|
||||||
else:
|
|
||||||
next.addCallback(identName)
|
|
||||||
except CancelledError:
|
|
||||||
retFutureSym.cancelAndSchedule()
|
|
||||||
except CatchableError as exc:
|
|
||||||
retFutureSym.fail(exc)
|
|
||||||
|
|
||||||
identName(nil)
|
|
||||||
{.pop.}
|
|
||||||
else:
|
|
||||||
template createCb(retFutureSym, iteratorNameSym,
|
|
||||||
strName, identName: untyped) =
|
|
||||||
bind finished
|
|
||||||
|
|
||||||
var nameIterVar = iteratorNameSym
|
|
||||||
{.push stackTrace: off.}
|
|
||||||
var identName: proc(udata: pointer) {.gcsafe, raises: [Defect].}
|
|
||||||
identName = proc(udata: pointer) {.gcsafe, raises: [Defect].} =
|
|
||||||
try:
|
|
||||||
# If the compiler complains about unlisted exception here, it's usually
|
|
||||||
# because you're calling a callback or forward declaration in your code
|
|
||||||
# for which the compiler cannot deduce raises signatures - make sure
|
|
||||||
# to annotate both forward declarations and `proc` types with `raises`!
|
|
||||||
if not(nameIterVar.finished()):
|
|
||||||
var next = nameIterVar()
|
|
||||||
# Continue while the yielded future is already finished.
|
|
||||||
while (not next.isNil()) and next.finished():
|
|
||||||
next = nameIterVar()
|
|
||||||
if nameIterVar.finished():
|
|
||||||
break
|
|
||||||
|
|
||||||
if next == nil:
|
|
||||||
if not(retFutureSym.finished()):
|
|
||||||
const msg = "Async procedure (&" & strName & ") yielded `nil`, " &
|
|
||||||
"are you await'ing a `nil` Future?"
|
|
||||||
raiseAssert msg
|
|
||||||
else:
|
|
||||||
next.addCallback(identName)
|
|
||||||
except CancelledError:
|
|
||||||
retFutureSym.cancelAndSchedule()
|
|
||||||
except CatchableError as exc:
|
|
||||||
retFutureSym.fail(exc)
|
|
||||||
except Exception as exc:
|
|
||||||
# TODO remove Exception handler to turn on strict mode
|
|
||||||
if exc of Defect:
|
|
||||||
raise (ref Defect)(exc)
|
|
||||||
|
|
||||||
retFutureSym.fail((ref ValueError)(msg: exc.msg, parent: exc))
|
|
||||||
|
|
||||||
identName(nil)
|
|
||||||
{.pop.}
|
|
||||||
|
|
||||||
proc processBody(node, retFutureSym: NimNode,
|
proc processBody(node, retFutureSym: NimNode,
|
||||||
subTypeIsVoid: bool): NimNode {.compileTime.} =
|
subTypeIsVoid: bool): NimNode {.compileTime.} =
|
||||||
#echo(node.treeRepr)
|
#echo(node.treeRepr)
|
||||||
@ -185,31 +102,16 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||||||
|
|
||||||
var outerProcBody = newNimNode(nnkStmtList, prc.body)
|
var outerProcBody = newNimNode(nnkStmtList, prc.body)
|
||||||
|
|
||||||
# -> var retFuture = newFuture[T]()
|
|
||||||
var retFutureSym = ident "chronosInternalRetFuture"
|
|
||||||
var subRetType =
|
|
||||||
if returnType.kind == nnkEmpty:
|
|
||||||
newIdentNode("void")
|
|
||||||
else:
|
|
||||||
baseType
|
|
||||||
# Do not change this code to `quote do` version because `instantiationInfo`
|
|
||||||
# will be broken for `newFuture()` call.
|
|
||||||
outerProcBody.add(
|
|
||||||
newVarStmt(
|
|
||||||
retFutureSym,
|
|
||||||
newCall(newTree(nnkBracketExpr, ident "newFuture", subRetType),
|
|
||||||
newLit(prcName))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# -> iterator nameIter(): FutureBase {.closure.} =
|
# -> iterator nameIter(chronosInternalRetFuture: Future[T]): FutureBase {.closure.} =
|
||||||
# -> {.push warning[resultshadowed]: off.}
|
# -> {.push warning[resultshadowed]: off.}
|
||||||
# -> var result: T
|
# -> var result: T
|
||||||
# -> {.pop.}
|
# -> {.pop.}
|
||||||
# -> <proc_body>
|
# -> <proc_body>
|
||||||
# -> complete(retFuture, result)
|
# -> complete(chronosInternalRetFuture, result)
|
||||||
|
let internalFutureSym = ident "chronosInternalRetFuture"
|
||||||
var iteratorNameSym = genSym(nskIterator, $prcName)
|
var iteratorNameSym = genSym(nskIterator, $prcName)
|
||||||
var procBody = prc.body.processBody(retFutureSym, subtypeIsVoid)
|
var procBody = prc.body.processBody(internalFutureSym, subtypeIsVoid)
|
||||||
# don't do anything with forward bodies (empty)
|
# don't do anything with forward bodies (empty)
|
||||||
if procBody.kind != nnkEmpty:
|
if procBody.kind != nnkEmpty:
|
||||||
if subtypeIsVoid:
|
if subtypeIsVoid:
|
||||||
@ -236,12 +138,18 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||||||
|
|
||||||
procBody.add(
|
procBody.add(
|
||||||
newCall(newIdentNode("complete"),
|
newCall(newIdentNode("complete"),
|
||||||
retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
|
internalFutureSym, newIdentNode("result"))) # -> complete(chronosInternalRetFuture, result)
|
||||||
else:
|
else:
|
||||||
# -> complete(retFuture)
|
# -> complete(chronosInternalRetFuture)
|
||||||
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
|
procBody.add(newCall(newIdentNode("complete"), internalFutureSym))
|
||||||
|
|
||||||
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
|
let
|
||||||
|
internalFutureType =
|
||||||
|
if subtypeIsVoid:
|
||||||
|
newNimNode(nnkBracketExpr, prc).add(newIdentNode("Future")).add(newIdentNode("void"))
|
||||||
|
else: returnType
|
||||||
|
internalFutureParameter = nnkIdentDefs.newTree(internalFutureSym, internalFutureType, newEmptyNode())
|
||||||
|
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase"), internalFutureParameter],
|
||||||
procBody, nnkIteratorDef)
|
procBody, nnkIteratorDef)
|
||||||
closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body)
|
closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body)
|
||||||
closureIterator.addPragma(newIdentNode("closure"))
|
closureIterator.addPragma(newIdentNode("closure"))
|
||||||
@ -284,16 +192,38 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||||||
closureIterator.addPragma(newIdentNode("gcsafe"))
|
closureIterator.addPragma(newIdentNode("gcsafe"))
|
||||||
outerProcBody.add(closureIterator)
|
outerProcBody.add(closureIterator)
|
||||||
|
|
||||||
# -> createCb(retFuture)
|
# -> var resultFuture = newFuture[T]()
|
||||||
# NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce
|
# declared at the end to be sure that the closure
|
||||||
# friendlier stack traces:
|
# doesn't reference it, avoid cyclic ref (#203)
|
||||||
var cbName = genSym(nskVar, prcName & "_continue")
|
var retFutureSym = ident "resultFuture"
|
||||||
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
|
var subRetType =
|
||||||
newStrLitNode(prcName),
|
if returnType.kind == nnkEmpty:
|
||||||
cbName)
|
newIdentNode("void")
|
||||||
outerProcBody.add procCb
|
else:
|
||||||
|
baseType
|
||||||
|
# Do not change this code to `quote do` version because `instantiationInfo`
|
||||||
|
# will be broken for `newFuture()` call.
|
||||||
|
outerProcBody.add(
|
||||||
|
newVarStmt(
|
||||||
|
retFutureSym,
|
||||||
|
newCall(newTree(nnkBracketExpr, ident "newFuture", subRetType),
|
||||||
|
newLit(prcName))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# -> resultFuture.closure = iterator
|
||||||
|
outerProcBody.add(
|
||||||
|
newAssignment(
|
||||||
|
newDotExpr(retFutureSym, newIdentNode("closure")),
|
||||||
|
iteratorNameSym)
|
||||||
|
)
|
||||||
|
|
||||||
# -> return retFuture
|
# -> futureContinue(resultFuture))
|
||||||
|
outerProcBody.add(
|
||||||
|
newCall(newIdentNode("futureContinue"), retFutureSym)
|
||||||
|
)
|
||||||
|
|
||||||
|
# -> return resultFuture
|
||||||
outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
|
outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
|
||||||
|
|
||||||
if prc.kind != nnkLambda: # TODO: Nim bug?
|
if prc.kind != nnkLambda: # TODO: Nim bug?
|
||||||
@ -320,9 +250,11 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
|||||||
|
|
||||||
template await*[T](f: Future[T]): untyped =
|
template await*[T](f: Future[T]): untyped =
|
||||||
when declared(chronosInternalRetFuture):
|
when declared(chronosInternalRetFuture):
|
||||||
|
#work around https://github.com/nim-lang/Nim/issues/19193
|
||||||
when not declaredInScope(chronosInternalTmpFuture):
|
when not declaredInScope(chronosInternalTmpFuture):
|
||||||
var chronosInternalTmpFuture {.inject.}: FutureBase
|
var chronosInternalTmpFuture {.inject.}: FutureBase = f
|
||||||
chronosInternalTmpFuture = f
|
else:
|
||||||
|
chronosInternalTmpFuture = f
|
||||||
chronosInternalRetFuture.child = chronosInternalTmpFuture
|
chronosInternalRetFuture.child = chronosInternalTmpFuture
|
||||||
|
|
||||||
# This "yield" is meant for a closure iterator in the caller.
|
# This "yield" is meant for a closure iterator in the caller.
|
||||||
@ -348,9 +280,11 @@ template await*[T](f: Future[T]): untyped =
|
|||||||
|
|
||||||
template awaitne*[T](f: Future[T]): Future[T] =
|
template awaitne*[T](f: Future[T]): Future[T] =
|
||||||
when declared(chronosInternalRetFuture):
|
when declared(chronosInternalRetFuture):
|
||||||
|
#work around https://github.com/nim-lang/Nim/issues/19193
|
||||||
when not declaredInScope(chronosInternalTmpFuture):
|
when not declaredInScope(chronosInternalTmpFuture):
|
||||||
var chronosInternalTmpFuture {.inject.}: FutureBase
|
var chronosInternalTmpFuture {.inject.}: FutureBase = f
|
||||||
chronosInternalTmpFuture = f
|
else:
|
||||||
|
chronosInternalTmpFuture = f
|
||||||
chronosInternalRetFuture.child = chronosInternalTmpFuture
|
chronosInternalRetFuture.child = chronosInternalTmpFuture
|
||||||
yield chronosInternalTmpFuture
|
yield chronosInternalTmpFuture
|
||||||
chronosInternalRetFuture.child = nil
|
chronosInternalRetFuture.child = nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user