222 lines
6.7 KiB
Nim
222 lines
6.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
|
||
|
|
||
|
when chronosStrictException:
|
||
|
{.pragma: closureIter, raises: [CatchableError], gcsafe.}
|
||
|
else:
|
||
|
{.pragma: closureIter, raises: [Exception], gcsafe.}
|
||
|
|
||
|
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
|
||
|
|
||
|
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
|
||
|
internalError*: ref CatchableError ## Stored exception
|
||
|
internalMustCancel*: bool
|
||
|
internalClosure*: iterator(f: FutureBase): FutureBase {.closureIter.}
|
||
|
|
||
|
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
|
||
|
|
||
|
CancelledError* = object of FutureError
|
||
|
## Exception raised when accessing the value of a cancelled future
|
||
|
|
||
|
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) =
|
||
|
fut.internalState = state
|
||
|
fut.internalLocation[LocationKind.Create] = loc
|
||
|
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 completed*(
|
||
|
F: type Future, fromProc: static[string] = ""): Future[void] =
|
||
|
## Create a new completed future
|
||
|
let res = Future[T]()
|
||
|
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 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](future: Future[T]): T =
|
||
|
## Return the value in a completed future - raises Defect when
|
||
|
## `fut.completed()` is `false`.
|
||
|
##
|
||
|
## See `read` for a version that raises an catchable error when future
|
||
|
## has not completed.
|
||
|
when chronosStrictFutureAccess:
|
||
|
if not future.completed():
|
||
|
raise (ref FutureDefect)(
|
||
|
msg: "Future not completed while accessing value",
|
||
|
cause: future)
|
||
|
|
||
|
when T isnot void:
|
||
|
future.internalValue
|
||
|
|
||
|
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():
|
||
|
raise (ref FutureDefect)(
|
||
|
msg: "Future not failed/cancelled while accessing error",
|
||
|
cause: 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
|