diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim index f75859e4c0..086f9c729b 100644 --- a/lib/pure/asyncfutures.nim +++ b/lib/pure/asyncfutures.nim @@ -231,13 +231,9 @@ proc diff[T](a, b: seq[T]): (int, seq[T]) = break return (lastIndex, b[lastIndex .. ^1]) -proc processEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] = - proc get(entries: seq[StackTraceEntry], i: int): StackTraceEntry = - if i >= entries.len: - return StackTraceEntry(procName: "", line: 0, filename: "") - else: - return entries[i] - +proc mergeEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] = + ## Merges stack trace entries containing re-raise entries into one + ## continuous stack trace. result = @[] var i = 0 while i < entries.len: @@ -252,7 +248,7 @@ proc processEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] = reRaiseEnd.inc() assert entries[reRaiseEnd].procName.isNil assert entries[reRaiseEnd].line == -100 # Signifies end of re-raise block. - let reRaisedEntries = processEntries(entries[i+1 .. reRaiseEnd-1]) + let reRaisedEntries = mergeEntries(entries[i+1 .. reRaiseEnd-1]) let (lastIndex, remainder) = diff(result, reRaisedEntries) for i in 0..= entries.len: + return StackTraceEntry(procName: "", line: 0, filename: "") + else: + return entries[i] + + result = @[] + var i = 0 + while i < entries.len: + var entry = entries[i] + # Detect this pattern: # (procname: a, line: 393, filename: asyncmacro.nim) - # (procname: cb0, line: 34, filename: asyncmacro.nim) + # (procname: a_continue, line: 34, filename: asyncmacro.nim) # (procname: aIter, line: 40, filename: tasync_traceback.nim) let second = get(entries, i+1) + let third = get(entries, i+2) let fitsPattern = cmpIgnoreStyle($entry.filename, "asyncmacro.nim") == 0 and cmpIgnoreStyle($second.filename, "asyncmacro.nim") == 0 and - cmpIgnoreStyle($second.procName, "cb0") == 0 and + ($second.procName).startsWith($entry.procName) and + ($second.procName).endsWith("continue") and cmpIgnoreStyle($third.procName, $entry.procName & "iter") == 0 if fitsPattern: @@ -287,10 +301,8 @@ proc getHint(entry: StackTraceEntry): string = ## We try to provide some hints about stack trace entries that the user ## may not be familiar with, in particular calls inside the stdlib. result = "" - case ($entry.procName).normalize() - of "cb0": - if cmpIgnoreStyle($entry.filename, "asyncmacro.nim") == 0: - return "Resumes an async procedure" + let name = ($entry.procName).normalize() + case name of "processpendingcallbacks": if cmpIgnoreStyle($entry.filename, "asyncdispatch.nim") == 0: return "Executes pending callbacks" @@ -298,6 +310,35 @@ proc getHint(entry: StackTraceEntry): string = if cmpIgnoreStyle($entry.filename, "asyncdispatch.nim") == 0: return "Processes asynchronous completion events" + if name.endsWith("continue"): + if cmpIgnoreStyle($entry.filename, "asyncmacro.nim") == 0: + return "Resumes an async procedure" + +proc `$`*(entries: seq[StackTraceEntry]): string = + result = "" + # Find longest filename & line number combo for alignment purposes. + var longestLeft = 0 + for entry in entries: + let left = $entry.filename & $entry.line + if left.len > longestLeft: + longestLeft = left.len + + const indent = 2 + # Format the entries. + for entry in entries: + let indentStr = spaces(indent) + + let left = "$#($#)" % [$entry.filename, $entry.line] + result.add("$#$#$# $#\n" % [ + indentStr, + left, + spaces(longestLeft - left.len + 2), + $entry.procName + ]) + let hint = getHint(entry) + if hint.len > 0: + result.add(indentStr & "└─" & hint & "\n") + proc injectStacktrace[T](future: Future[T]) = when not defined(release): const header = "\nAsync traceback:\n" @@ -309,29 +350,15 @@ proc injectStacktrace[T](future: Future[T]) = let start = exceptionMsg.find(header) exceptionMsg = exceptionMsg[0.. longestLeft: - longestLeft = left.len - - const indent = " " - # Format the entries. - for entry in entries: - let left = "$#($#)" % [$entry.filename, $entry.line] - newMsg.add("$#$#$# $#\n" % [ - indent, - left, - spaces(longestLeft - left.len + 2), - $entry.procName - ]) - let hint = getHint(entry) - if hint.len > 0: - newMsg.add(indent & "└─" & hint & "\n") + let entries = getStackTraceEntries(future.error).mergeEntries() + let shortEntries = entries.shortenEntries() + newMsg.add($shortEntries) + if entries.len > shortEntries.len: + newMsg.add("\nDetailed Async traceback:\n") + newMsg.add($entries) newMsg.add("Exception message: " & exceptionMsg & "\n") newMsg.add("Exception type:") diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index a8e378d5c4..85d44b9f4b 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -25,10 +25,10 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} = result = node[0] template createCb(retFutureSym, iteratorNameSym, - name, futureVarCompletions: untyped) = + strName, identName, futureVarCompletions: untyped) = var nameIterVar = iteratorNameSym #{.push stackTrace: off.} - proc cb0 {.closure.} = + proc identName {.closure.} = try: if not nameIterVar.finished: var next = nameIterVar() @@ -36,11 +36,11 @@ template createCb(retFutureSym, iteratorNameSym, if not retFutureSym.finished: let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " & "`nil` Future?" - raise newException(AssertionError, msg % name) + raise newException(AssertionError, msg % strName) else: {.gcsafe.}: {.push hint[ConvFromXtoItselfNotNeeded]: off.} - next.callback = (proc() {.closure, gcsafe.})(cb0) + next.callback = (proc() {.closure, gcsafe.})(identName) {.pop.} except: futureVarCompletions @@ -52,7 +52,7 @@ template createCb(retFutureSym, iteratorNameSym, else: retFutureSym.fail(getCurrentException()) - cb0() + identName() #{.pop.} proc generateExceptionCheck(futSym, tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} = @@ -389,9 +389,10 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = outerProcBody.add(closureIterator) # -> createCb(retFuture) - #var cbName = newIdentNode("cb") + var cbName = genSym(nskProc, prcName & "_continue") var procCb = getAst createCb(retFutureSym, iteratorNameSym, newStrLitNode(prcName), + cbName, createFutureVarCompletions(futureVarIdents, nil)) outerProcBody.add procCb diff --git a/tests/async/tasync_traceback.nim b/tests/async/tasync_traceback.nim index 6ab7ef9153..5e4f1142f0 100644 --- a/tests/async/tasync_traceback.nim +++ b/tests/async/tasync_traceback.nim @@ -1,5 +1,6 @@ discard """ exitcode: 0 + disabled: "windows" output: ''' b failure Async traceback: