Merge f85a3b43c2606d8966bb1eb6e36de10c7b4aab94 into b55e2816eb45f698ddaca8d8473e401502562db2

This commit is contained in:
Giuliano Mega 2025-09-04 01:35:00 +00:00 committed by GitHub
commit 7be60a4134
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 1 deletions

View File

@ -80,6 +80,14 @@ task test_libbacktrace, "test with libbacktrace":
if (NimMajor, NimMinor) > (1, 6):
run args & " --mm:orc", "tests/testall"
task test_profiler, "test with profiler instrumentation":
var allArgs = @[
"-d:release -d:chronosFutureId -d:chronosProfiling",
]
for args in allArgs:
run args, "tests/testall"
task docs, "Generate API documentation":
exec "mdbook build docs"
exec nimc & " doc " & "--git.url:https://github.com/status-im/nim-chronos --git.commit:master --outdir:docs/book/api --project chronos"

View File

@ -40,7 +40,12 @@ const
chronosStackTrace* {.booldefine.}: bool = defined(chronosDebug)
## Include stack traces in futures for creation and completion points
chronosFutureId* {.booldefine.}: bool = defined(chronosDebug)
chronosProfiling* {.booldefine.} = defined(chronosProfiling)
## Enable instrumentation callbacks which are called at
## the start, pause, or end of a Future's lifetime.
## Useful for implementing metrics or other instrumentation.
chronosFutureId* {.booldefine.}: bool = defined(chronosDebug) or chronosProfiling
## Generate a unique `id` for every future - when disabled, the address of
## the future will be used instead

View File

@ -111,6 +111,13 @@ when chronosFutureTracking:
var futureList* {.threadvar.}: FutureList
when chronosProfiling:
type AsyncFutureState* {.pure.} = enum
Running, Paused
var onBaseFutureEvent* {.threadvar.}: proc (fut: FutureBase, state: FutureState): void {.nimcall, gcsafe, raises: [].}
var onAsyncFutureEvent* {.threadvar.}: proc(fut: FutureBase, state: AsyncFutureState): void {.nimcall, gcsafe, raises: [].}
# Internal utilities - these are not part of the stable API
proc internalInitFutureBase*(fut: FutureBase, loc: ptr SrcLoc,
state: FutureState, flags: FutureFlags) =
@ -144,6 +151,10 @@ proc internalInitFutureBase*(fut: FutureBase, loc: ptr SrcLoc,
futureList.head = fut
futureList.count.inc()
when chronosProfiling:
if not isNil(onBaseFutureEvent):
onBaseFutureEvent(fut, state)
# Public API
template init*[T](F: type Future[T], fromProc: static[string] = ""): Future[T] =
## Creates a new pending future.

View File

@ -181,6 +181,11 @@ proc finish(fut: FutureBase, state: FutureState) =
# 1. `finish()` is a private procedure and `state` is under our control.
# 2. `fut.state` is checked by `checkFinished()`.
fut.internalState = state
when chronosProfiling:
if not isNil(onBaseFutureEvent):
onBaseFutureEvent(fut, state)
fut.internalCancelcb = nil # release cancellation callback memory
for item in fut.internalCallbacks.mitems():
if not(isNil(item.function)):
@ -365,6 +370,10 @@ proc futureContinue*(fut: FutureBase) {.raises: [], gcsafe.} =
#
# Every call to an `{.async.}` proc is redirected to call this function
# instead with its original body captured in `fut.closure`.
when chronosProfiling:
if not isNil(onAsyncFutureEvent):
onAsyncFutureEvent(fut, Running)
while true:
# Call closure to make progress on `fut` until it reaches `yield` (inside
# `await` typically) or completes / fails / is cancelled
@ -382,6 +391,10 @@ proc futureContinue*(fut: FutureBase) {.raises: [], gcsafe.} =
GC_ref(fut)
next.addCallback(CallbackFunc(internalContinue), cast[pointer](fut))
when chronosProfiling:
if not isNil(onAsyncFutureEvent):
onAsyncFutureEvent(fut, Paused)
# return here so that we don't remove the closure below
return