Skip to content

Commit db4cbda

Browse files
committed
Nice tracktraces proof of concept
1 parent ea9fa93 commit db4cbda

File tree

1 file changed

+64
-14
lines changed

1 file changed

+64
-14
lines changed

chronos/asyncfutures2.nim

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const
1616
LocCreateIndex = 0
1717
LocCompleteIndex = 1
1818

19+
stackTraces = compileOption("stackTrace")
20+
1921
type
2022
CallbackFunc* = proc (arg: pointer = nil) {.gcsafe.}
2123
CallSoonProc* = proc (c: CallbackFunc, u: pointer = nil) {.gcsafe.}
@@ -35,6 +37,8 @@ type
3537

3638
FutureBase* = ref object of RootObj ## Untyped future.
3739
location: array[2, ptr SrcLoc]
40+
when stackTraces:
41+
creationLocation: SrcLoc
3842
callbacks: Deque[AsyncCallback]
3943
cancelcb*: CallbackFunc
4044
child*: FutureBase
@@ -69,6 +73,16 @@ type
6973
var currentID* {.threadvar.}: int
7074
currentID = 0
7175

76+
{.push stackTrace: off.}
77+
proc callerLocation(): SrcLoc =
78+
when stackTraces:
79+
var f = getFrame()
80+
if not f.isNil:
81+
result.file = f.filename
82+
result.line = f.line
83+
result.procedure = f.procname
84+
{.pop.}
85+
7286
# ZAH: This seems unnecessary. Isn't it easy to introduce a seperate
7387
# module for the dispatcher type, so it can be directly referenced here?
7488
var callSoonHolder {.threadvar.}: CallSoonProc
@@ -85,61 +99,63 @@ proc callSoon*(c: CallbackFunc, u: pointer = nil) =
8599
## Call ``cbproc`` "soon".
86100
callSoonHolder(c, u)
87101

88-
template setupFutureBase(loc: ptr SrcLoc) =
102+
template setupFutureBase(loc: ptr SrcLoc, caller: SrcLoc) =
89103
new(result)
90104
result.state = FutureState.Pending
91105
result.stackTrace = getStackTrace()
92106
result.id = currentID
93107
result.location[LocCreateIndex] = loc
108+
when stackTraces:
109+
result.creationLocation = caller
94110
currentID.inc()
95111

96112
## ZAH: As far as I undestand `fromProc` is just a debugging helper.
97113
## It would be more efficient if it's represented as a simple statically
98114
## known `char *` in the final program (so it needs to be a `cstring` in Nim).
99115
## The public API can be defined as a template expecting a `static[string]`
100116
## and converting this immediately to a `cstring`.
101-
proc newFuture[T](loc: ptr SrcLoc): Future[T] =
102-
setupFutureBase(loc)
117+
proc newFuture[T](loc: ptr SrcLoc, srcLoc: SrcLoc): Future[T] =
118+
setupFutureBase(loc, srcLoc)
103119

104-
proc newFutureSeq[A, B](loc: ptr SrcLoc): FutureSeq[A, B] =
105-
setupFutureBase(loc)
120+
proc newFutureSeq[A, B](loc: ptr SrcLoc, srcLoc: SrcLoc): FutureSeq[A, B] =
121+
setupFutureBase(loc, srcLoc)
106122

107-
proc newFutureStr[T](loc: ptr SrcLoc): FutureStr[T] =
108-
setupFutureBase(loc)
123+
proc newFutureStr[T](loc: ptr SrcLoc, srcLoc: SrcLoc): FutureStr[T] =
124+
setupFutureBase(loc, srcLoc)
109125

110-
proc newFutureVar[T](loc: ptr SrcLoc): FutureVar[T] =
111-
FutureVar[T](newFuture[T](loc))
126+
proc newFutureVar[T](loc: ptr SrcLoc, srcLoc: SrcLoc): FutureVar[T] =
127+
FutureVar[T](newFuture[T](loc, srcLoc))
112128

113129
template newFuture*[T](fromProc: static[string] = ""): auto =
114130
## Creates a new future.
115131
##
116132
## Specifying ``fromProc``, which is a string specifying the name of the proc
117133
## that this future belongs to, is a good habit as it helps with debugging.
118-
newFuture[T](getSrcLocation(fromProc))
134+
newFuture[T](getSrcLocation(fromProc), callerLocation())
119135

120136
template newFutureSeq*[A, B](fromProc: static[string] = ""): auto =
121137
## Create a new future which can hold/preserve GC sequence until future will
122138
## not be completed.
123139
##
124140
## Specifying ``fromProc``, which is a string specifying the name of the proc
125141
## that this future belongs to, is a good habit as it helps with debugging.
126-
newFutureSeq[A, B](getSrcLocation(fromProc))
142+
newFutureSeq[A, B](getSrcLocation(fromProc), callerLocation())
127143

128144
template newFutureStr*[T](fromProc: static[string] = ""): auto =
129145
## Create a new future which can hold/preserve GC string until future will
130146
## not be completed.
131147
##
132148
## Specifying ``fromProc``, which is a string specifying the name of the proc
133149
## that this future belongs to, is a good habit as it helps with debugging.
134-
newFutureStr[T](getSrcLocation(fromProc))
150+
newFutureStr[T](getSrcLocation(fromProc), callerLocation())
135151

136152
template newFutureVar*[T](fromProc: static[string] = ""): auto =
137153
## Create a new ``FutureVar``. This Future type is ideally suited for
138154
## situations where you want to avoid unnecessary allocations of Futures.
139155
##
140156
## Specifying ``fromProc``, which is a string specifying the name of the proc
141157
## that this future belongs to, is a good habit as it helps with debugging.
142-
newFutureVar[T](getSrcLocation(fromProc))
158+
newFutureVar[T](getSrcLocation(fromProc), callerLocation())
143159

144160
proc clean*[T](future: FutureVar[T]) =
145161
## Resets the ``finished`` status of ``future``.
@@ -395,6 +411,40 @@ proc `$`*(entries: seq[StackTraceEntry]): string =
395411
if hint.len > 0:
396412
result.add(spaces(indent+2) & "## " & hint & "\n")
397413

414+
proc getStackTraceEntries(fut: FutureBase): string =
415+
var f = fut
416+
417+
result = "---- Nice stacktrace ------\n"
418+
419+
var topFut = fut
420+
421+
while not f.isNil:
422+
topFut = f
423+
result &= $f.creationLocation.file & " (" & $f.creationLocation.line & ") " & $f.creationLocation.procedure & " ---\n"
424+
f = f.child
425+
426+
var stStart = 0
427+
428+
let s = fut.error.getStackTraceEntries()
429+
var i = 1
430+
for e in s:
431+
if e.line == -10: break
432+
if e.procName == "poll":
433+
stStart = i
434+
elif e.filename == topFut.creationLocation.file and e.line == topFut.creationLocation.line:
435+
stStart = i
436+
break
437+
inc i
438+
439+
for i in stStart ..< s.len:
440+
if s[i].line == -10: break
441+
result &= $s[i].filename & " (" & $s[i].line & ") " & $s[i].procname & "\n"
442+
443+
result &= "---- End of nice stacktrace ------\n"
444+
result &= "---- Raw upper exception stacktrace ------\n"
445+
result &= $fut.error.getStackTraceEntries()
446+
result &= "stStart: " & $stStart & "\n"
447+
398448
proc injectStacktrace(future: FutureBase) =
399449
const header = "\nAsync traceback:\n"
400450

@@ -407,7 +457,7 @@ proc injectStacktrace(future: FutureBase) =
407457

408458
var newMsg = exceptionMsg & header
409459

410-
let entries = getStackTraceEntries(future.error)
460+
let entries = getStackTraceEntries(future)
411461
newMsg.add($entries)
412462

413463
newMsg.add("Exception message: " & exceptionMsg & "\n")

0 commit comments

Comments
 (0)