optimized friendly stack traces

This commit is contained in:
Araq 2017-12-13 09:46:42 +01:00 • committed by Andreas Rumpf
commit 6cf2fcaaf1
2 changed files with 21 additions and 29 deletions

View file

@ -1,4 +1,4 @@
import os, tables, strutils, times, heapqueue, options, deques import os, tables, strutils, times, heapqueue, options, deques, cstrutils
# TODO: This shouldn't need to be included, but should ideally be exported. # TODO: This shouldn't need to be included, but should ideally be exported.
type type
@ -217,19 +217,15 @@ proc `callback=`*[T](future: Future[T],
## If future has already completed then ``cb`` will be called immediately. ## If future has already completed then ``cb`` will be called immediately.
future.callback = proc () = cb(future) future.callback = proc () = cb(future)
proc diff[T](a, b: seq[T]): (int, seq[T]) = proc diff[T](a, b: seq[T]; firstB, lastB: int): int =
## Iterates through both sequences until the items do not match, ## Iterates through both sequences until the items do not match,
## returns the remainder of `b` after the last item that does not match ## returns the remainder of `b[firstB..lastB]` after the last item that does not match
## together with the index of the last match. ## together with the index of the last match.
## result = firstB
## .. code-block::nim for i in 0..<min(a.len, lastB - firstB + 1):
## doAssert(diff(@[1,2,42,123], @[1,2,123,678,21]) == (1, @[123,678,21])) result = i+firstB
var lastIndex = 0 if a[i] != b[i+firstB]:
for i in 0..<min(a.len, b.len):
lastIndex = i
if a[i] != b[i]:
break break
return (lastIndex, b[lastIndex .. ^1])
proc mergeEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] = proc mergeEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] =
## Merges stack trace entries containing re-raise entries into one ## Merges stack trace entries containing re-raise entries into one
@ -248,12 +244,11 @@ proc mergeEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] =
reRaiseEnd.inc() reRaiseEnd.inc()
assert entries[reRaiseEnd].procName.isNil assert entries[reRaiseEnd].procName.isNil
assert entries[reRaiseEnd].line == -100 # Signifies end of re-raise block. assert entries[reRaiseEnd].line == -100 # Signifies end of re-raise block.
let reRaisedEntries = entries[i+1 .. reRaiseEnd-1]
let (lastIndex, remainder) = diff(result, reRaisedEntries) let lastIndex = diff(result, entries, i+1, reRaiseEnd-1)
# Insert all the entries after lastIndex. # Insert all the entries after lastIndex.
for i in 0..<remainder.len: for j in lastIndex..<reRaiseEnd - i - 1:
result.insert(remainder[i], lastIndex+i) result.insert(entries[j+i+1], j)
i = reRaiseEnd+1 i = reRaiseEnd+1
continue continue
@ -264,17 +259,15 @@ proc getHint(entry: StackTraceEntry): string =
## We try to provide some hints about stack trace entries that the user ## We try to provide some hints about stack trace entries that the user
## may not be familiar with, in particular calls inside the stdlib. ## may not be familiar with, in particular calls inside the stdlib.
result = "" result = ""
let name = ($entry.procName).normalize() if entry.procname == "processPendingCallbacks":
case name if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
of "processpendingcallbacks":
if cmpIgnoreStyle($entry.filename, "asyncdispatch.nim") == 0:
return "Executes pending callbacks" return "Executes pending callbacks"
of "poll": elif entry.procname == "poll":
if cmpIgnoreStyle($entry.filename, "asyncdispatch.nim") == 0: if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
return "Processes asynchronous completion events" return "Processes asynchronous completion events"
if name.endsWith("continue"): if entry.procname.endsWith("_continue"):
if cmpIgnoreStyle($entry.filename, "asyncmacro.nim") == 0: if cmpIgnoreStyle(entry.filename, "asyncmacro.nim") == 0:
return "Resumes an async procedure" return "Resumes an async procedure"
proc `$`*(entries: seq[StackTraceEntry]): string = proc `$`*(entries: seq[StackTraceEntry]): string =
@ -286,21 +279,18 @@ proc `$`*(entries: seq[StackTraceEntry]): string =
if left.len > longestLeft: if left.len > longestLeft:
longestLeft = left.len longestLeft = left.len
const indent = 2 const indent = spaces(2)
# Format the entries. # Format the entries.
for entry in entries: for entry in entries:
let indentStr = spaces(indent)
let left = "$#($#)" % [$entry.filename, $entry.line] let left = "$#($#)" % [$entry.filename, $entry.line]
result.add("$#$#$# $#\n" % [ result.add((indent & "$#$# $#\n") % [
indentStr,
left, left,
spaces(longestLeft - left.len + 2), spaces(longestLeft - left.len + 2),
$entry.procName $entry.procName
]) ])
let hint = getHint(entry) let hint = getHint(entry)
if hint.len > 0: if hint.len > 0:
result.add(indentStr & "└─" & hint & "\n") result.add(indent & "└─" & hint & "\n")
proc injectStacktrace[T](future: Future[T]) = proc injectStacktrace[T](future: Future[T]) =
when not defined(release): when not defined(release):

View file

@ -389,6 +389,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
outerProcBody.add(closureIterator) outerProcBody.add(closureIterator)
# -> createCb(retFuture) # -> createCb(retFuture)
# NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce
# friendlier stack traces:
var cbName = genSym(nskProc, prcName & "_continue") var cbName = genSym(nskProc, prcName & "_continue")
var procCb = getAst createCb(retFutureSym, iteratorNameSym, var procCb = getAst createCb(retFutureSym, iteratorNameSym,
newStrLitNode(prcName), newStrLitNode(prcName),