Implement some simple pattern-based transformation for async tracebacks.
This commit is contained in:
parent
f3a895f043
commit
f73015ad9e
2 changed files with 115 additions and 10 deletions
|
|
@ -217,17 +217,76 @@ 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 injectStacktrace[T](future: Future[T]) =
|
proc processEntries(entries: seq[StackTraceEntry]): seq[StackTraceEntry] =
|
||||||
# TODO: Come up with something better.
|
proc get(entries: seq[StackTraceEntry], i: int): StackTraceEntry =
|
||||||
when not defined(release):
|
if i >= entries.len:
|
||||||
var msg = ""
|
return StackTraceEntry(procName: "", line: 0, filename: "")
|
||||||
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
|
|
||||||
|
|
||||||
if not future.errorStackTrace.isNil and future.errorStackTrace != "":
|
|
||||||
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
|
|
||||||
else:
|
else:
|
||||||
msg.add("\n Empty or nil stack trace.")
|
return entries[i]
|
||||||
future.error.msg.add(msg)
|
|
||||||
|
result = @[]
|
||||||
|
var i = 0
|
||||||
|
while i < entries.len:
|
||||||
|
var entry = entries[i]
|
||||||
|
|
||||||
|
if entry.procName.isNil:
|
||||||
|
# Start of a re-raise traceback which we do not care for.
|
||||||
|
break
|
||||||
|
|
||||||
|
# Detect this pattern:
|
||||||
|
# (procname: a, line: 393, filename: asyncmacro.nim)
|
||||||
|
# (procname: cb0, 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
|
||||||
|
cmpIgnoreStyle($third.procName, $entry.procName & "iter") == 0
|
||||||
|
|
||||||
|
if fitsPattern:
|
||||||
|
entry = StackTraceEntry(
|
||||||
|
procName: entry.procName,
|
||||||
|
line: third.line,
|
||||||
|
filename: third.filename
|
||||||
|
)
|
||||||
|
i.inc(2)
|
||||||
|
|
||||||
|
result.add(entry)
|
||||||
|
i.inc
|
||||||
|
|
||||||
|
proc injectStacktrace[T](future: Future[T]) =
|
||||||
|
when not defined(release):
|
||||||
|
const header = "Async traceback\n---------------\n"
|
||||||
|
|
||||||
|
let originalMsg = future.error.msg
|
||||||
|
if header in originalMsg:
|
||||||
|
return
|
||||||
|
|
||||||
|
let entries = getStackTraceEntries(future.error).processEntries()
|
||||||
|
future.error.msg = "\n" & header
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Format the entries.
|
||||||
|
for entry in entries:
|
||||||
|
let left = "$#($#)" % [$entry.filename, $entry.line]
|
||||||
|
future.error.msg.add("$1$2 $3\n" % [
|
||||||
|
left,
|
||||||
|
spaces(longestLeft - left.len + 2), $entry.procName])
|
||||||
|
|
||||||
|
future.error.msg.add("Exception message: " & originalMsg & "\n")
|
||||||
|
future.error.msg.add("Exception type: ")
|
||||||
|
|
||||||
|
# For debugging purposes TODO...
|
||||||
|
for entry in getStackTraceEntries(future.error):
|
||||||
|
future.error.msg.add "\n" & $entry
|
||||||
|
|
||||||
proc read*[T](future: Future[T] | FutureVar[T]): T =
|
proc read*[T](future: Future[T] | FutureVar[T]): T =
|
||||||
## Retrieves the value of ``future``. Future must be finished otherwise
|
## Retrieves the value of ``future``. Future must be finished otherwise
|
||||||
|
|
|
||||||
46
tests/async/tasync_traceback.nim
Normal file
46
tests/async/tasync_traceback.nim
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
discard """
|
||||||
|
exitcode: 0
|
||||||
|
output: ""
|
||||||
|
"""
|
||||||
|
import asyncdispatch
|
||||||
|
|
||||||
|
# Tests to ensure our exception trace backs are friendly.
|
||||||
|
|
||||||
|
# --- Simple test. ---
|
||||||
|
#
|
||||||
|
# What does this look like when it's synchronous?
|
||||||
|
#
|
||||||
|
# tasync_traceback.nim(23) tasync_traceback
|
||||||
|
# tasync_traceback.nim(21) a
|
||||||
|
# tasync_traceback.nim(18) b
|
||||||
|
# Error: unhandled exception: b failure [OSError]
|
||||||
|
#
|
||||||
|
# Good (not quite ideal, but gotta work within constraints) traceback,
|
||||||
|
# when exception is unhandled:
|
||||||
|
#
|
||||||
|
# <traceback for the unhandled exception>
|
||||||
|
# <very much a bunch of noise>
|
||||||
|
# <would be ideal to customise this>
|
||||||
|
# <(the code responsible is in excpt:raiseExceptionAux)>
|
||||||
|
# Error: unhandled exception: b failure
|
||||||
|
# ===============
|
||||||
|
# Async traceback
|
||||||
|
# ===============
|
||||||
|
#
|
||||||
|
# tasync_traceback.nim(23) tasync_traceback
|
||||||
|
#
|
||||||
|
# tasync_traceback.nim(21) a
|
||||||
|
# tasync_traceback.nim(18) b
|
||||||
|
|
||||||
|
proc b(): Future[int] {.async.} =
|
||||||
|
if true:
|
||||||
|
raise newException(OSError, "b failure")
|
||||||
|
|
||||||
|
proc a(): Future[int] {.async.} =
|
||||||
|
return await b()
|
||||||
|
|
||||||
|
let aFut = a()
|
||||||
|
# try:
|
||||||
|
discard waitFor aFut
|
||||||
|
# except Exception as exc:
|
||||||
|
# echo exc.msg
|
||||||
Loading…
Add table
Add a link
Reference in a new issue