Skip to content

Commit

Permalink
Nice tracktraces proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
yglukhov committed Sep 4, 2019
1 parent ea9fa93 commit adcea92
Showing 1 changed file with 65 additions and 14 deletions.
79 changes: 65 additions & 14 deletions chronos/asyncfutures2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const
LocCreateIndex = 0
LocCompleteIndex = 1

stackTraces = compileOption("stackTrace")

type
CallbackFunc* = proc (arg: pointer = nil) {.gcsafe.}
CallSoonProc* = proc (c: CallbackFunc, u: pointer = nil) {.gcsafe.}
Expand All @@ -35,6 +37,8 @@ type

FutureBase* = ref object of RootObj ## Untyped future.
location: array[2, ptr SrcLoc]
when stackTraces:
creationLocation: SrcLoc
callbacks: Deque[AsyncCallback]
cancelcb*: CallbackFunc
child*: FutureBase
Expand Down Expand Up @@ -69,6 +73,16 @@ type
var currentID* {.threadvar.}: int
currentID = 0

{.push stackTrace: off.}
proc callerLocation(): SrcLoc =
when stackTraces:
var f = getFrame()
if not f.isNil:
result.file = f.filename
result.line = f.line
result.procedure = f.procname
{.pop.}

# ZAH: This seems unnecessary. Isn't it easy to introduce a seperate
# module for the dispatcher type, so it can be directly referenced here?
var callSoonHolder {.threadvar.}: CallSoonProc
Expand All @@ -85,61 +99,63 @@ proc callSoon*(c: CallbackFunc, u: pointer = nil) =
## Call ``cbproc`` "soon".
callSoonHolder(c, u)

template setupFutureBase(loc: ptr SrcLoc) =
template setupFutureBase(loc: ptr SrcLoc, caller: SrcLoc) =
new(result)
result.state = FutureState.Pending
result.stackTrace = getStackTrace()
result.id = currentID
result.location[LocCreateIndex] = loc
when stackTraces:
result.creationLocation = caller
currentID.inc()

## ZAH: As far as I undestand `fromProc` is just a debugging helper.
## It would be more efficient if it's represented as a simple statically
## known `char *` in the final program (so it needs to be a `cstring` in Nim).
## The public API can be defined as a template expecting a `static[string]`
## and converting this immediately to a `cstring`.
proc newFuture[T](loc: ptr SrcLoc): Future[T] =
setupFutureBase(loc)
proc newFuture[T](loc: ptr SrcLoc, srcLoc: SrcLoc): Future[T] =
setupFutureBase(loc, srcLoc)

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

proc newFutureStr[T](loc: ptr SrcLoc): FutureStr[T] =
setupFutureBase(loc)
proc newFutureStr[T](loc: ptr SrcLoc, srcLoc: SrcLoc): FutureStr[T] =
setupFutureBase(loc, srcLoc)

proc newFutureVar[T](loc: ptr SrcLoc): FutureVar[T] =
FutureVar[T](newFuture[T](loc))
proc newFutureVar[T](loc: ptr SrcLoc, srcLoc: SrcLoc): FutureVar[T] =
FutureVar[T](newFuture[T](loc, srcLoc))

template newFuture*[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.
newFuture[T](getSrcLocation(fromProc))
newFuture[T](getSrcLocation(fromProc), callerLocation())

template newFutureSeq*[A, B](fromProc: static[string] = ""): auto =
## Create a new future which can hold/preserve GC sequence until future will
## not be completed.
##
## 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.
newFutureSeq[A, B](getSrcLocation(fromProc))
newFutureSeq[A, B](getSrcLocation(fromProc), callerLocation())

template newFutureStr*[T](fromProc: static[string] = ""): auto =
## Create a new future which can hold/preserve GC string until future will
## not be completed.
##
## 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.
newFutureStr[T](getSrcLocation(fromProc))
newFutureStr[T](getSrcLocation(fromProc), callerLocation())

template newFutureVar*[T](fromProc: static[string] = ""): auto =
## Create a new ``FutureVar``. This Future type is ideally suited for
## situations where you want to avoid unnecessary allocations of Futures.
##
## 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.
newFutureVar[T](getSrcLocation(fromProc))
newFutureVar[T](getSrcLocation(fromProc), callerLocation())

proc clean*[T](future: FutureVar[T]) =
## Resets the ``finished`` status of ``future``.
Expand Down Expand Up @@ -395,6 +411,41 @@ proc `$`*(entries: seq[StackTraceEntry]): string =
if hint.len > 0:
result.add(spaces(indent+2) & "## " & hint & "\n")

proc getStackTraceEntries(fut: FutureBase): string =
when stackTraces:
var f = fut

result = "---- Nice stacktrace ------\n"

var topFut = fut

while not f.isNil:
topFut = f
result &= $f.creationLocation.file & " (" & $f.creationLocation.line & ") " & $f.creationLocation.procedure & " ---\n"
f = f.child

var stStart = 0

let s = fut.error.getStackTraceEntries()
var i = 1
for e in s:
if e.line == -10: break
if e.procName == "poll":
stStart = i
elif e.filename == topFut.creationLocation.file and e.line == topFut.creationLocation.line:
stStart = i
break
inc i

for i in stStart ..< s.len:
if s[i].line == -10: break
result &= $s[i].filename & " (" & $s[i].line & ") " & $s[i].procname & "\n"

result &= "---- End of nice stacktrace ------\n"
result &= "---- Raw upper exception stacktrace ------\n"
result &= $fut.error.getStackTraceEntries()
result &= "stStart: " & $stStart & "\n"

proc injectStacktrace(future: FutureBase) =
const header = "\nAsync traceback:\n"

Expand All @@ -407,7 +458,7 @@ proc injectStacktrace(future: FutureBase) =

var newMsg = exceptionMsg & header

let entries = getStackTraceEntries(future.error)
let entries = getStackTraceEntries(future)
newMsg.add($entries)

newMsg.add("Exception message: " & exceptionMsg & "\n")
Expand Down

0 comments on commit adcea92

Please sign in to comment.