summary refs log tree commit diff stats
path: root/lib/pure/asyncfutures.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/asyncfutures.nim')
-rw-r--r--lib/pure/asyncfutures.nim79
1 files changed, 70 insertions, 9 deletions
diff --git a/lib/pure/asyncfutures.nim b/lib/pure/asyncfutures.nim
index 4bd3227a1..bcc3ab613 100644
--- a/lib/pure/asyncfutures.nim
+++ b/lib/pure/asyncfutures.nim
@@ -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.
 type
@@ -217,17 +217,78 @@ proc `callback=`*[T](future: Future[T],
   ## If future has already completed then ``cb`` will be called immediately.
   future.callback = proc () = cb(future)
 
+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 = ""
+  if entry.procname == "processPendingCallbacks":
+    if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
+      return "Executes pending callbacks"
+  elif entry.procname == "poll":
+    if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
+      return "Processes asynchronous completion events"
+
+  if entry.procname.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:
+    if entry.procName.isNil: continue
+
+    let left = $entry.filename & $entry.line
+    if left.len > longestLeft:
+      longestLeft = left.len
+
+  var indent = 2
+  # Format the entries.
+  for entry in entries:
+    if entry.procName.isNil:
+      if entry.line == -10:
+        result.add(spaces(indent) & "#[\n")
+        indent.inc(2)
+      else:
+        indent.dec(2)
+        result.add(spaces(indent)& "]#\n")
+      continue
+
+    let left = "$#($#)" % [$entry.filename, $entry.line]
+    result.add((spaces(indent) & "$#$# $#\n") % [
+      left,
+      spaces(longestLeft - left.len + 2),
+      $entry.procName
+    ])
+    let hint = getHint(entry)
+    if hint.len > 0:
+      result.add(spaces(indent+2) & "## " & hint & "\n")
+
 proc injectStacktrace[T](future: Future[T]) =
-  # TODO: Come up with something better.
   when not defined(release):
-    var msg = ""
-    msg.add("\n  " & future.fromProc & "'s lead up to read of failed Future:")
+    const header = "\nAsync traceback:\n"
 
-    if not future.errorStackTrace.isNil and future.errorStackTrace != "":
-      msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
-    else:
-      msg.add("\n    Empty or nil stack trace.")
-    future.error.msg.add(msg)
+    var exceptionMsg = future.error.msg
+    if header in exceptionMsg:
+      # This is messy: extract the original exception message from the msg
+      # containing the async traceback.
+      let start = exceptionMsg.find(header)
+      exceptionMsg = exceptionMsg[0..<start]
+
+
+    var newMsg = exceptionMsg & header
+
+    let entries = getStackTraceEntries(future.error)
+    newMsg.add($entries)
+
+    newMsg.add("Exception message: " & exceptionMsg & "\n")
+    newMsg.add("Exception type:")
+
+    # # For debugging purposes
+    # for entry in getStackTraceEntries(future.error):
+    #   newMsg.add "\n" & $entry
+    future.error.msg = newMsg
 
 proc read*[T](future: Future[T] | FutureVar[T]): T =
   ## Retrieves the value of ``future``. Future must be finished otherwise