Merge branch 'devel' of github.com:nim-lang/Nim into devel
This commit is contained in:
commit
b6eeebd993
6 changed files with 201 additions and 19 deletions
|
|
@ -111,6 +111,8 @@ This now needs to be written as:
|
||||||
- The ``[]`` proc for strings now raises an ``IndexError`` exception when
|
- The ``[]`` proc for strings now raises an ``IndexError`` exception when
|
||||||
the specified slice is out of bounds. See issue
|
the specified slice is out of bounds. See issue
|
||||||
[#6223](https://github.com/nim-lang/Nim/issues/6223) for more details.
|
[#6223](https://github.com/nim-lang/Nim/issues/6223) for more details.
|
||||||
|
You can use ``substr(str, start, finish)`` to get the old behaviour back,
|
||||||
|
see [this commit](https://github.com/nim-lang/nimbot/commit/98cc031a27ea89947daa7f0bb536bcf86462941f) for an example.
|
||||||
- ``strutils.split`` and ``strutils.rsplit`` with an empty string and a
|
- ``strutils.split`` and ``strutils.rsplit`` with an empty string and a
|
||||||
separator now returns that empty string.
|
separator now returns that empty string.
|
||||||
See issue [#4377](https://github.com/nim-lang/Nim/issues/4377).
|
See issue [#4377](https://github.com/nim-lang/Nim/issues/4377).
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@ proc addParam(procType: PType; param: PSym) =
|
||||||
rawAddSon(procType, param.typ)
|
rawAddSon(procType, param.typ)
|
||||||
|
|
||||||
proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp;
|
proc liftBody(c: PContext; typ: PType; kind: TTypeAttachedOp;
|
||||||
info: TLineInfo): PSym {.discardable.} =
|
info: TLineInfo): PSym =
|
||||||
var a: TLiftCtx
|
var a: TLiftCtx
|
||||||
a.info = info
|
a.info = info
|
||||||
a.c = c
|
a.c = c
|
||||||
|
|
|
||||||
|
|
@ -1493,7 +1493,7 @@ proc poll*(timeout = 500) =
|
||||||
## Waits for completion events and processes them. Raises ``ValueError``
|
## Waits for completion events and processes them. Raises ``ValueError``
|
||||||
## if there are no pending operations. This runs the underlying OS
|
## if there are no pending operations. This runs the underlying OS
|
||||||
## `epoll`:idx: or `kqueue`:idx: primitive only once.
|
## `epoll`:idx: or `kqueue`:idx: primitive only once.
|
||||||
discard runOnce()
|
discard runOnce(timeout)
|
||||||
|
|
||||||
# Common procedures between current and upcoming asyncdispatch
|
# Common procedures between current and upcoming asyncdispatch
|
||||||
include includes.asynccommon
|
include includes.asynccommon
|
||||||
|
|
|
||||||
|
|
@ -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,17 +217,78 @@ 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 getHint(entry: StackTraceEntry): string =
|
||||||
# TODO: Come up with something better.
|
## We try to provide some hints about stack trace entries that the user
|
||||||
when not defined(release):
|
## may not be familiar with, in particular calls inside the stdlib.
|
||||||
var msg = ""
|
result = ""
|
||||||
msg.add("\n " & future.fromProc & "'s lead up to read of failed Future:")
|
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 not future.errorStackTrace.isNil and future.errorStackTrace != "":
|
if entry.procname.endsWith("_continue"):
|
||||||
msg.add("\n" & indent(future.errorStackTrace.strip(), 4))
|
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:
|
else:
|
||||||
msg.add("\n Empty or nil stack trace.")
|
indent.dec(2)
|
||||||
future.error.msg.add(msg)
|
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]) =
|
||||||
|
when not defined(release):
|
||||||
|
const header = "\nAsync traceback:\n"
|
||||||
|
|
||||||
|
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 =
|
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
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,10 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
|
||||||
result = node[0]
|
result = node[0]
|
||||||
|
|
||||||
template createCb(retFutureSym, iteratorNameSym,
|
template createCb(retFutureSym, iteratorNameSym,
|
||||||
name, futureVarCompletions: untyped) =
|
strName, identName, futureVarCompletions: untyped) =
|
||||||
var nameIterVar = iteratorNameSym
|
var nameIterVar = iteratorNameSym
|
||||||
#{.push stackTrace: off.}
|
#{.push stackTrace: off.}
|
||||||
proc cb0 {.closure.} =
|
proc identName {.closure.} =
|
||||||
try:
|
try:
|
||||||
if not nameIterVar.finished:
|
if not nameIterVar.finished:
|
||||||
var next = nameIterVar()
|
var next = nameIterVar()
|
||||||
|
|
@ -36,11 +36,11 @@ template createCb(retFutureSym, iteratorNameSym,
|
||||||
if not retFutureSym.finished:
|
if not retFutureSym.finished:
|
||||||
let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " &
|
let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " &
|
||||||
"`nil` Future?"
|
"`nil` Future?"
|
||||||
raise newException(AssertionError, msg % name)
|
raise newException(AssertionError, msg % strName)
|
||||||
else:
|
else:
|
||||||
{.gcsafe.}:
|
{.gcsafe.}:
|
||||||
{.push hint[ConvFromXtoItselfNotNeeded]: off.}
|
{.push hint[ConvFromXtoItselfNotNeeded]: off.}
|
||||||
next.callback = (proc() {.closure, gcsafe.})(cb0)
|
next.callback = (proc() {.closure, gcsafe.})(identName)
|
||||||
{.pop.}
|
{.pop.}
|
||||||
except:
|
except:
|
||||||
futureVarCompletions
|
futureVarCompletions
|
||||||
|
|
@ -52,7 +52,7 @@ template createCb(retFutureSym, iteratorNameSym,
|
||||||
else:
|
else:
|
||||||
retFutureSym.fail(getCurrentException())
|
retFutureSym.fail(getCurrentException())
|
||||||
|
|
||||||
cb0()
|
identName()
|
||||||
#{.pop.}
|
#{.pop.}
|
||||||
proc generateExceptionCheck(futSym,
|
proc generateExceptionCheck(futSym,
|
||||||
tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
|
tryStmt, rootReceiver, fromNode: NimNode): NimNode {.compileTime.} =
|
||||||
|
|
@ -389,9 +389,12 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
outerProcBody.add(closureIterator)
|
outerProcBody.add(closureIterator)
|
||||||
|
|
||||||
# -> createCb(retFuture)
|
# -> createCb(retFuture)
|
||||||
#var cbName = newIdentNode("cb")
|
# NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce
|
||||||
|
# friendlier stack traces:
|
||||||
|
var cbName = genSym(nskProc, prcName & "_continue")
|
||||||
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
|
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
|
||||||
newStrLitNode(prcName),
|
newStrLitNode(prcName),
|
||||||
|
cbName,
|
||||||
createFutureVarCompletions(futureVarIdents, nil))
|
createFutureVarCompletions(futureVarIdents, nil))
|
||||||
outerProcBody.add procCb
|
outerProcBody.add procCb
|
||||||
|
|
||||||
|
|
|
||||||
116
tests/async/tasync_traceback.nim
Normal file
116
tests/async/tasync_traceback.nim
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
discard """
|
||||||
|
exitcode: 0
|
||||||
|
disabled: "windows"
|
||||||
|
output: '''
|
||||||
|
b failure
|
||||||
|
Async traceback:
|
||||||
|
tasync_traceback.nim(97) tasync_traceback
|
||||||
|
asyncmacro.nim(395) a
|
||||||
|
asyncmacro.nim(34) a_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
tasync_traceback.nim(95) aIter
|
||||||
|
asyncmacro.nim(395) b
|
||||||
|
asyncmacro.nim(34) b_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
tasync_traceback.nim(92) bIter
|
||||||
|
#[
|
||||||
|
tasync_traceback.nim(97) tasync_traceback
|
||||||
|
asyncmacro.nim(395) a
|
||||||
|
asyncmacro.nim(43) a_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
asyncfutures.nim(211) callback=
|
||||||
|
asyncfutures.nim(190) addCallback
|
||||||
|
asyncfutures.nim(53) callSoon
|
||||||
|
asyncmacro.nim(34) a_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
asyncmacro.nim(0) aIter
|
||||||
|
asyncfutures.nim(304) read
|
||||||
|
]#
|
||||||
|
Exception message: b failure
|
||||||
|
Exception type:
|
||||||
|
|
||||||
|
bar failure
|
||||||
|
Async traceback:
|
||||||
|
tasync_traceback.nim(113) tasync_traceback
|
||||||
|
asyncdispatch.nim(1492) waitFor
|
||||||
|
asyncdispatch.nim(1496) poll
|
||||||
|
## Processes asynchronous completion events
|
||||||
|
asyncdispatch.nim(1262) runOnce
|
||||||
|
asyncdispatch.nim(183) processPendingCallbacks
|
||||||
|
## Executes pending callbacks
|
||||||
|
asyncmacro.nim(34) bar_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
tasync_traceback.nim(108) barIter
|
||||||
|
#[
|
||||||
|
tasync_traceback.nim(113) tasync_traceback
|
||||||
|
asyncdispatch.nim(1492) waitFor
|
||||||
|
asyncdispatch.nim(1496) poll
|
||||||
|
## Processes asynchronous completion events
|
||||||
|
asyncdispatch.nim(1262) runOnce
|
||||||
|
asyncdispatch.nim(183) processPendingCallbacks
|
||||||
|
## Executes pending callbacks
|
||||||
|
asyncmacro.nim(34) foo_continue
|
||||||
|
## Resumes an async procedure
|
||||||
|
asyncmacro.nim(0) fooIter
|
||||||
|
asyncfutures.nim(304) read
|
||||||
|
]#
|
||||||
|
Exception message: bar failure
|
||||||
|
Exception type:'''
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
echo()
|
||||||
|
|
||||||
|
# From #6803
|
||||||
|
proc bar(): Future[string] {.async.} =
|
||||||
|
await sleepAsync(100)
|
||||||
|
if true:
|
||||||
|
raise newException(OSError, "bar failure")
|
||||||
|
|
||||||
|
proc foo(): Future[string] {.async.} = return await bar()
|
||||||
|
|
||||||
|
try:
|
||||||
|
echo waitFor(foo())
|
||||||
|
except Exception as exc:
|
||||||
|
echo exc.msg
|
||||||
|
echo()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue