266 lines
8.7 KiB
Nim
266 lines
8.7 KiB
Nim
#
|
|
# Chronos
|
|
#
|
|
# (c) Copyright 2015 Dominik Picheta
|
|
# (c) Copyright 2018-2023 Status Research & Development GmbH
|
|
#
|
|
# Licensed under either of
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
# MIT license (LICENSE-MIT)
|
|
|
|
{.push raises: [].}
|
|
|
|
import "."/[config, srcloc]
|
|
|
|
export srcloc
|
|
|
|
when chronosStackTrace:
|
|
type StackTrace = string
|
|
|
|
type
|
|
LocationKind* {.pure.} = enum
|
|
Create
|
|
Finish
|
|
|
|
CallbackFunc* = proc (arg: pointer) {.gcsafe, raises: [].}
|
|
|
|
# Internal type, not part of API
|
|
InternalAsyncCallback* = object
|
|
function*: CallbackFunc
|
|
udata*: pointer
|
|
|
|
FutureState* {.pure.} = enum
|
|
Pending, Completed, Cancelled, Failed
|
|
|
|
FutureFlag* {.pure.} = enum
|
|
OwnCancelSchedule
|
|
## When OwnCancelSchedule is set, the owner of the future is responsible
|
|
## for implementing cancellation in one of 3 ways:
|
|
##
|
|
## * ensure that cancellation requests never reach the future by means of
|
|
## not exposing it to user code, `await` and `tryCancel`
|
|
## * set `cancelCallback` to `nil` to stop cancellation propagation - this
|
|
## is appropriate when it is expected that the future will be completed
|
|
## in a regular way "soon"
|
|
## * set `cancelCallback` to a handler that implements cancellation in an
|
|
## operation-specific way
|
|
##
|
|
## If `cancelCallback` is not set and the future gets cancelled, a
|
|
## `Defect` will be raised.
|
|
|
|
FutureFlags* = set[FutureFlag]
|
|
|
|
InternalFutureBase* = object of RootObj
|
|
# Internal untyped future representation - the fields are not part of the
|
|
# public API and neither is `InternalFutureBase`, ie the inheritance
|
|
# structure may change in the future (haha)
|
|
|
|
internalLocation*: array[LocationKind, ptr SrcLoc]
|
|
internalCallbacks*: seq[InternalAsyncCallback]
|
|
internalCancelcb*: CallbackFunc
|
|
internalChild*: FutureBase
|
|
internalState*: FutureState
|
|
internalFlags*: FutureFlags
|
|
internalError*: ref CatchableError ## Stored exception
|
|
internalClosure*: iterator(f: FutureBase): FutureBase {.raises: [], gcsafe.}
|
|
|
|
when chronosFutureId:
|
|
internalId*: uint
|
|
|
|
when chronosStackTrace:
|
|
internalErrorStackTrace*: StackTrace
|
|
internalStackTrace*: StackTrace ## For debugging purposes only.
|
|
|
|
when chronosFutureTracking:
|
|
internalNext*: FutureBase
|
|
internalPrev*: FutureBase
|
|
|
|
FutureBase* = ref object of InternalFutureBase
|
|
## Untyped Future
|
|
|
|
Future*[T] = ref object of FutureBase ## Typed future.
|
|
when T isnot void:
|
|
internalValue*: T ## Stored value
|
|
|
|
FutureDefect* = object of Defect
|
|
cause*: FutureBase
|
|
|
|
FutureError* = object of CatchableError
|
|
future*: FutureBase
|
|
|
|
CancelledError* = object of FutureError
|
|
## Exception raised when accessing the value of a cancelled future
|
|
|
|
func raiseFutureDefect(msg: static string, fut: FutureBase) {.
|
|
noinline, noreturn.} =
|
|
raise (ref FutureDefect)(msg: msg, cause: fut)
|
|
|
|
when chronosFutureId:
|
|
var currentID* {.threadvar.}: uint
|
|
template id*(fut: FutureBase): uint = fut.internalId
|
|
else:
|
|
template id*(fut: FutureBase): uint =
|
|
cast[uint](addr fut[])
|
|
|
|
when chronosFutureTracking:
|
|
type
|
|
FutureList* = object
|
|
head*: FutureBase
|
|
tail*: FutureBase
|
|
count*: uint
|
|
|
|
var futureList* {.threadvar.}: FutureList
|
|
|
|
# Internal utilities - these are not part of the stable API
|
|
proc internalInitFutureBase*(fut: FutureBase, loc: ptr SrcLoc,
|
|
state: FutureState, flags: FutureFlags) =
|
|
fut.internalState = state
|
|
fut.internalLocation[LocationKind.Create] = loc
|
|
fut.internalFlags = flags
|
|
if FutureFlag.OwnCancelSchedule in flags:
|
|
# Owners must replace `cancelCallback` with `nil` if they want to ignore
|
|
# cancellations
|
|
fut.internalCancelcb = proc(_: pointer) =
|
|
raiseAssert "Cancellation request for non-cancellable future"
|
|
|
|
if state != FutureState.Pending:
|
|
fut.internalLocation[LocationKind.Finish] = loc
|
|
|
|
when chronosFutureId:
|
|
currentID.inc()
|
|
fut.internalId = currentID
|
|
|
|
when chronosStackTrace:
|
|
fut.internalStackTrace = getStackTrace()
|
|
|
|
when chronosFutureTracking:
|
|
if state == FutureState.Pending:
|
|
fut.internalNext = nil
|
|
fut.internalPrev = futureList.tail
|
|
if not(isNil(futureList.tail)):
|
|
futureList.tail.internalNext = fut
|
|
futureList.tail = fut
|
|
if isNil(futureList.head):
|
|
futureList.head = fut
|
|
futureList.count.inc()
|
|
|
|
# Public API
|
|
template init*[T](F: type Future[T], fromProc: static[string] = ""): Future[T] =
|
|
## Creates a new pending future.
|
|
##
|
|
## Specifying ``fromProc``, which is a string specifying the name of the proc
|
|
## that this future belongs to, is a good habit as it helps with debugging.
|
|
let res = Future[T]()
|
|
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Pending, {})
|
|
res
|
|
|
|
template init*[T](F: type Future[T], fromProc: static[string] = "",
|
|
flags: static[FutureFlags]): Future[T] =
|
|
## Creates a new pending future.
|
|
##
|
|
## Specifying ``fromProc``, which is a string specifying the name of the proc
|
|
## that this future belongs to, is a good habit as it helps with debugging.
|
|
let res = Future[T]()
|
|
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Pending,
|
|
flags)
|
|
res
|
|
|
|
template completed*(
|
|
F: type Future, fromProc: static[string] = ""): Future[void] =
|
|
## Create a new completed future
|
|
let res = Future[void]()
|
|
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Completed,
|
|
{})
|
|
res
|
|
|
|
template completed*[T: not void](
|
|
F: type Future, valueParam: T, fromProc: static[string] = ""): Future[T] =
|
|
## Create a new completed future
|
|
let res = Future[T](internalValue: valueParam)
|
|
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Completed,
|
|
{})
|
|
res
|
|
|
|
template failed*[T](
|
|
F: type Future[T], errorParam: ref CatchableError,
|
|
fromProc: static[string] = ""): Future[T] =
|
|
## Create a new failed future
|
|
let res = Future[T](internalError: errorParam)
|
|
internalInitFutureBase(res, getSrcLocation(fromProc), FutureState.Failed, {})
|
|
when chronosStackTrace:
|
|
res.internalErrorStackTrace =
|
|
if getStackTrace(res.error) == "":
|
|
getStackTrace()
|
|
else:
|
|
getStackTrace(res.error)
|
|
res
|
|
|
|
func state*(future: FutureBase): FutureState =
|
|
future.internalState
|
|
|
|
func flags*(future: FutureBase): FutureFlags =
|
|
future.internalFlags
|
|
|
|
func finished*(future: FutureBase): bool {.inline.} =
|
|
## Determines whether ``future`` has finished, i.e. ``future`` state changed
|
|
## from state ``Pending`` to one of the states (``Finished``, ``Cancelled``,
|
|
## ``Failed``).
|
|
future.state != FutureState.Pending
|
|
|
|
func cancelled*(future: FutureBase): bool {.inline.} =
|
|
## Determines whether ``future`` has cancelled.
|
|
future.state == FutureState.Cancelled
|
|
|
|
func failed*(future: FutureBase): bool {.inline.} =
|
|
## Determines whether ``future`` finished with an error.
|
|
future.state == FutureState.Failed
|
|
|
|
func completed*(future: FutureBase): bool {.inline.} =
|
|
## Determines whether ``future`` finished with a value.
|
|
future.state == FutureState.Completed
|
|
|
|
func location*(future: FutureBase): array[LocationKind, ptr SrcLoc] =
|
|
future.internalLocation
|
|
|
|
func value*[T: not void](future: Future[T]): lent T =
|
|
## Return the value in a completed future - raises Defect when
|
|
## `fut.completed()` is `false`.
|
|
##
|
|
## See `read` for a version that raises a catchable error when future
|
|
## has not completed.
|
|
when chronosStrictFutureAccess:
|
|
if not future.completed():
|
|
raiseFutureDefect("Future not completed while accessing value", future)
|
|
|
|
future.internalValue
|
|
|
|
func value*(future: Future[void]) =
|
|
## Return the value in a completed future - raises Defect when
|
|
## `fut.completed()` is `false`.
|
|
##
|
|
## See `read` for a version that raises a catchable error when future
|
|
## has not completed.
|
|
when chronosStrictFutureAccess:
|
|
if not future.completed():
|
|
raiseFutureDefect("Future not completed while accessing value", future)
|
|
|
|
func error*(future: FutureBase): ref CatchableError =
|
|
## Return the error of `future`, or `nil` if future did not fail.
|
|
##
|
|
## See `readError` for a version that raises a catchable error when the
|
|
## future has not failed.
|
|
when chronosStrictFutureAccess:
|
|
if not future.failed() and not future.cancelled():
|
|
raiseFutureDefect(
|
|
"Future not failed/cancelled while accessing error", future)
|
|
|
|
future.internalError
|
|
|
|
when chronosFutureTracking:
|
|
func next*(fut: FutureBase): FutureBase = fut.internalNext
|
|
func prev*(fut: FutureBase): FutureBase = fut.internalPrev
|
|
|
|
when chronosStackTrace:
|
|
func errorStackTrace*(fut: FutureBase): StackTrace = fut.internalErrorStackTrace
|
|
func stackTrace*(fut: FutureBase): StackTrace = fut.internalStackTrace
|