Make Future tracking and stack traces optional (#108)
* Make Future tracking optional via -d:chronosDutureTracking compilation flag. * Stack traces is now optional, use -d:chronosStackTraces. * Fix mistypes and add test for chronosStackTrace option.
This commit is contained in:
parent
5629b3c41f
commit
ce6e7d17b1
|
@ -13,8 +13,9 @@ requires "nim > 0.19.4",
|
|||
task test, "Run all tests":
|
||||
var commands = [
|
||||
"nim c -r -d:useSysAssert -d:useGcAssert tests/",
|
||||
"nim c -r tests/",
|
||||
"nim c -r -d:release tests/"
|
||||
"nim c -r -d:chronosStackTrace tests/",
|
||||
"nim c -r -d:release tests/",
|
||||
"nim c -r -d:release -d:chronosFutureTracking tests/"
|
||||
]
|
||||
for testname in ["testall"]:
|
||||
for cmd in commands:
|
||||
|
|
|
@ -16,12 +16,10 @@ const
|
|||
LocCreateIndex* = 0
|
||||
LocCompleteIndex* = 1
|
||||
|
||||
type
|
||||
# ZAH: This can probably be stored with a cheaper representation
|
||||
# until the moment it needs to be printed to the screen
|
||||
# (e.g. seq[StackTraceEntry])
|
||||
StackTrace = string
|
||||
when defined(chronosStackTrace):
|
||||
type StackTrace = string
|
||||
|
||||
type
|
||||
FutureState* {.pure.} = enum
|
||||
Pending, Finished, Cancelled, Failed
|
||||
|
||||
|
@ -32,10 +30,14 @@ type
|
|||
child*: FutureBase
|
||||
state*: FutureState
|
||||
error*: ref Exception ## Stored exception
|
||||
errorStackTrace*: StackTrace
|
||||
stackTrace: StackTrace ## For debugging purposes only.
|
||||
mustCancel*: bool
|
||||
id*: int
|
||||
|
||||
when defined(chronosStackTrace):
|
||||
errorStackTrace*: StackTrace
|
||||
stackTrace: StackTrace ## For debugging purposes only.
|
||||
|
||||
when defined(chronosFutureTracking):
|
||||
next*: FutureBase
|
||||
prev*: FutureBase
|
||||
|
||||
|
@ -69,18 +71,22 @@ type
|
|||
count*: int
|
||||
|
||||
var currentID* {.threadvar.}: int
|
||||
var futureList* {.threadvar.}: FutureList
|
||||
currentID = 0
|
||||
|
||||
when defined(chronosFutureTracking):
|
||||
var futureList* {.threadvar.}: FutureList
|
||||
futureList = FutureList()
|
||||
|
||||
template setupFutureBase(loc: ptr SrcLoc) =
|
||||
new(result)
|
||||
result.state = FutureState.Pending
|
||||
when defined(chronosStackTrace):
|
||||
result.stackTrace = getStackTrace()
|
||||
result.id = currentID
|
||||
result.location[LocCreateIndex] = loc
|
||||
currentID.inc()
|
||||
|
||||
when defined(chronosFutureTracking):
|
||||
result.next = nil
|
||||
result.prev = futureList.tail
|
||||
if not(isNil(futureList.tail)):
|
||||
|
@ -155,6 +161,7 @@ proc failed*(future: FutureBase): bool {.inline.} =
|
|||
## Determines whether ``future`` completed with an error.
|
||||
result = (future.state == FutureState.Failed)
|
||||
|
||||
when defined(chronosFutureTracking):
|
||||
proc futureDestructor(udata: pointer) {.gcsafe.} =
|
||||
## This procedure will be called when Future[T] got finished, cancelled or
|
||||
## failed and all Future[T].callbacks are already scheduled and processed.
|
||||
|
@ -165,6 +172,9 @@ proc futureDestructor(udata: pointer) {.gcsafe.} =
|
|||
if not(isNil(future.prev)): future.prev.next = future.next
|
||||
futureList.count.dec()
|
||||
|
||||
proc scheduleDestructor(future: FutureBase) {.inline.} =
|
||||
callSoon(futureDestructor, cast[pointer](future))
|
||||
|
||||
proc checkFinished(future: FutureBase, loc: ptr SrcLoc) =
|
||||
## Checks whether `future` is finished. If it is then raises a
|
||||
## ``FutureDefect``.
|
||||
|
@ -179,6 +189,7 @@ proc checkFinished(future: FutureBase, loc: ptr SrcLoc) =
|
|||
msg.add("\n " & $future.location[LocCompleteIndex])
|
||||
msg.add("\n Second completion location:")
|
||||
msg.add("\n " & $loc)
|
||||
when defined(chronosStackTrace):
|
||||
msg.add("\n Stack trace to moment of creation:")
|
||||
msg.add("\n" & indent(future.stackTrace.strip(), 4))
|
||||
msg.add("\n Stack trace to moment of secondary completion:")
|
||||
|
@ -198,9 +209,6 @@ proc call(callbacks: var Deque[AsyncCallback]) =
|
|||
callSoon(item.function, item.udata)
|
||||
dec(count)
|
||||
|
||||
proc scheduleDestructor(future: FutureBase) {.inline.} =
|
||||
callSoon(futureDestructor, cast[pointer](future))
|
||||
|
||||
proc add(callbacks: var Deque[AsyncCallback], item: AsyncCallback) =
|
||||
if len(callbacks) == 0:
|
||||
callbacks = initDeque[AsyncCallback]()
|
||||
|
@ -218,6 +226,7 @@ proc complete[T](future: Future[T], val: T, loc: ptr SrcLoc) =
|
|||
future.value = val
|
||||
future.state = FutureState.Finished
|
||||
future.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(FutureBase(future))
|
||||
|
||||
template complete*[T](future: Future[T], val: T) =
|
||||
|
@ -230,6 +239,7 @@ proc complete(future: Future[void], loc: ptr SrcLoc) =
|
|||
doAssert(isNil(future.error))
|
||||
future.state = FutureState.Finished
|
||||
future.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(FutureBase(future))
|
||||
|
||||
template complete*(future: Future[void]) =
|
||||
|
@ -243,6 +253,7 @@ proc complete[T](future: FutureVar[T], loc: ptr SrcLoc) =
|
|||
doAssert(isNil(fut.error))
|
||||
fut.state = FutureState.Finished
|
||||
fut.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(FutureBase(future))
|
||||
|
||||
template complete*[T](futvar: FutureVar[T]) =
|
||||
|
@ -257,6 +268,7 @@ proc complete[T](futvar: FutureVar[T], val: T, loc: ptr SrcLoc) =
|
|||
fut.state = FutureState.Finished
|
||||
fut.value = val
|
||||
fut.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(FutureBase(fut))
|
||||
|
||||
template complete*[T](futvar: FutureVar[T], val: T) =
|
||||
|
@ -270,22 +282,31 @@ proc fail[T](future: Future[T], error: ref Exception, loc: ptr SrcLoc) =
|
|||
checkFinished(FutureBase(future), loc)
|
||||
future.state = FutureState.Failed
|
||||
future.error = error
|
||||
future.errorStackTrace =
|
||||
if getStackTrace(error) == "": getStackTrace() else: getStackTrace(error)
|
||||
when defined(chronosStackTrace):
|
||||
future.errorStackTrace = if getStackTrace(error) == "":
|
||||
getStackTrace()
|
||||
else:
|
||||
getStackTrace(error)
|
||||
future.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(FutureBase(future))
|
||||
|
||||
template fail*[T](future: Future[T], error: ref Exception) =
|
||||
## Completes ``future`` with ``error``.
|
||||
fail(future, error, getSrcLocation())
|
||||
|
||||
template newCancelledError(): ref CancelledError =
|
||||
(ref CancelledError)(msg: "Future operation cancelled!")
|
||||
|
||||
proc cancelAndSchedule(future: FutureBase, loc: ptr SrcLoc) =
|
||||
if not(future.finished()):
|
||||
checkFinished(future, loc)
|
||||
future.state = FutureState.Cancelled
|
||||
future.error = newException(CancelledError, "")
|
||||
future.error = newCancelledError()
|
||||
when defined(chronosStackTrace):
|
||||
future.errorStackTrace = getStackTrace()
|
||||
future.callbacks.call()
|
||||
when defined(chronosFutureTracking):
|
||||
scheduleDestructor(future)
|
||||
|
||||
template cancelAndSchedule*[T](future: Future[T]) =
|
||||
|
@ -405,6 +426,7 @@ proc `$`*(entries: seq[StackTraceEntry]): string =
|
|||
if hint.len > 0:
|
||||
result.add(spaces(indent+2) & "## " & hint & "\n")
|
||||
|
||||
when defined(chronosStackTrace):
|
||||
proc injectStacktrace(future: FutureBase) =
|
||||
const header = "\nAsync traceback:\n"
|
||||
|
||||
|
@ -431,6 +453,7 @@ proc injectStacktrace(future: FutureBase) =
|
|||
proc internalCheckComplete*(fut: FutureBase) =
|
||||
# For internal use only. Used in asyncmacro
|
||||
if not(isNil(fut.error)):
|
||||
when defined(chronosStackTrace):
|
||||
injectStacktrace(fut)
|
||||
raise fut.error
|
||||
|
||||
|
@ -480,6 +503,7 @@ proc asyncCheck*[T](future: Future[T]) =
|
|||
doAssert(not isNil(future), "Future is nil")
|
||||
proc cb(data: pointer) =
|
||||
if future.failed() or future.cancelled():
|
||||
when defined(chronosStackTrace):
|
||||
injectStacktrace(future)
|
||||
raise future.error
|
||||
future.callback = cb
|
||||
|
|
|
@ -959,9 +959,10 @@ proc getTracker*(id: string): TrackerBase =
|
|||
let loop = getGlobalDispatcher()
|
||||
result = loop.trackers.getOrDefault(id, nil)
|
||||
|
||||
when defined(chronosFutureTracking):
|
||||
iterator pendingFutures*(): FutureBase =
|
||||
## Iterates over the list of pending Futures (Future[T] objects which not yet
|
||||
## completed, cancelled or failed).
|
||||
## Iterates over the list of pending Futures (Future[T] objects which not
|
||||
## yet completed, cancelled or failed).
|
||||
var slider = futureList.head
|
||||
while not(isNil(slider)):
|
||||
yield slider
|
||||
|
|
|
@ -275,7 +275,7 @@ template await*[T](f: Future[T]): auto =
|
|||
yield chronosInternalTmpFuture
|
||||
chronosInternalRetFuture.child = nil
|
||||
if chronosInternalRetFuture.mustCancel:
|
||||
raise newException(CancelledError, "")
|
||||
raise newCancelledError()
|
||||
chronosInternalTmpFuture.internalCheckComplete()
|
||||
cast[type(f)](chronosInternalTmpFuture).internalRead()
|
||||
else:
|
||||
|
@ -290,7 +290,7 @@ template awaitne*[T](f: Future[T]): Future[T] =
|
|||
yield chronosInternalTmpFuture
|
||||
chronosInternalRetFuture.child = nil
|
||||
if chronosInternalRetFuture.mustCancel:
|
||||
raise newException(CancelledError, "")
|
||||
raise newCancelledError()
|
||||
cast[type(f)](chronosInternalTmpFuture)
|
||||
else:
|
||||
unsupported "awaitne is only available within {.async.}"
|
||||
|
|
|
@ -26,6 +26,7 @@ proc dumpPendingFutures*(filter = AllFutureStates): string =
|
|||
## which callbacks are scheduled, but not yet fully processed.
|
||||
var count = 0
|
||||
var res = ""
|
||||
when defined(chronosFutureTracking):
|
||||
for item in pendingFutures():
|
||||
if item.state in filter:
|
||||
inc(count)
|
||||
|
@ -48,6 +49,7 @@ proc pendingFuturesCount*(filter: set[FutureState]): int =
|
|||
##
|
||||
## If ``filter`` is equal to ``AllFutureStates`` Operation's complexity is
|
||||
## O(1), otherwise operation's complexity is O(n).
|
||||
when defined(chronosFutureTracking):
|
||||
if filter == AllFutureStates:
|
||||
pendingFuturesCount()
|
||||
else:
|
||||
|
@ -56,3 +58,5 @@ proc pendingFuturesCount*(filter: set[FutureState]): int =
|
|||
if item.state in filter:
|
||||
inc(res)
|
||||
res
|
||||
else:
|
||||
0
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
# Licensed under either of
|
||||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
import unittest, strutils
|
||||
import unittest
|
||||
import ../chronos
|
||||
|
||||
when defined(nimHasUsed): {.used.}
|
||||
|
||||
suite "Asynchronous utilities test suite":
|
||||
when defined(chronosFutureTracking):
|
||||
proc getCount(): int =
|
||||
# This procedure counts number of Future[T] in double-linked list via list
|
||||
# iteration.
|
||||
|
@ -19,6 +20,7 @@ suite "Asynchronous utilities test suite":
|
|||
inc(result)
|
||||
|
||||
test "Future clean and leaks test":
|
||||
when defined(chronosFutureTracking):
|
||||
if pendingFuturesCount(WithoutFinished) == 0:
|
||||
if pendingFuturesCount(OnlyFinished) > 0:
|
||||
poll()
|
||||
|
@ -26,8 +28,11 @@ suite "Asynchronous utilities test suite":
|
|||
else:
|
||||
echo dumpPendingFutures()
|
||||
check false
|
||||
else:
|
||||
skip()
|
||||
|
||||
test "FutureList basics test":
|
||||
when defined(chronosFutureTracking):
|
||||
var fut1 = newFuture[void]()
|
||||
check:
|
||||
getCount() == 1
|
||||
|
@ -55,8 +60,11 @@ suite "Asynchronous utilities test suite":
|
|||
check:
|
||||
getCount() == 0
|
||||
pendingFuturesCount() == 0
|
||||
else:
|
||||
skip()
|
||||
|
||||
test "FutureList async procedure test":
|
||||
when defined(chronosFutureTracking):
|
||||
proc simpleProc() {.async.} =
|
||||
await sleepAsync(10.milliseconds)
|
||||
|
||||
|
@ -74,3 +82,5 @@ suite "Asynchronous utilities test suite":
|
|||
check:
|
||||
getCount() == 0
|
||||
pendingFuturesCount() == 0
|
||||
else:
|
||||
skip()
|
||||
|
|
Loading…
Reference in New Issue