88# Apache License, version 2.0, (LICENSE-APACHEv2)
99# MIT license (LICENSE-MIT)
1010
11+ # # Features and utilities for `Future` that integrate it with the dispatcher
12+ # # and the rest of the async machinery
13+
1114{.push raises : [].}
1215
1316import std/ [sequtils, macros]
@@ -45,15 +48,28 @@ func `[]`*(loc: array[LocationKind, ptr SrcLoc], v: int): ptr SrcLoc {.
4548
4649type
4750 FutureStr * [T] = ref object of Future [T]
48- # # Future to hold GC strings
51+ # # Deprecated
4952 gcholder* : string
5053
5154 FutureSeq * [A, B] = ref object of Future [A]
52- # # Future to hold GC seqs
55+ # # Deprecated
5356 gcholder* : seq [B]
5457
58+ FuturePendingError * = object of FutureError
59+ # # Error raised when trying to `read` a Future that is still pending
60+ FutureCompletedError * = object of FutureError
61+ # # Error raised when trying access the error of a completed Future
62+
5563 SomeFuture = Future | InternalRaisesFuture
5664
65+ func raiseFuturePendingError (fut: FutureBase ) {.
66+ noinline , noreturn , raises : FuturePendingError .} =
67+ raise (ref FuturePendingError )(msg: " Future is still pending" , future: fut)
68+ func raiseFutureCompletedError (fut: FutureBase ) {.
69+ noinline , noreturn , raises : FutureCompletedError .} =
70+ raise (ref FutureCompletedError )(
71+ msg: " Future is completed, cannot read error" , future: fut)
72+
5773# Backwards compatibility for old FutureState name
5874template Finished * {.deprecated : " Use Completed instead" .} = Completed
5975template Finished * (T: type FutureState ): FutureState {.
@@ -479,6 +495,10 @@ macro internalCheckComplete*(fut: InternalRaisesFuture, raises: typed) =
479495 # generics are lost - so instead, we pass the raises list explicitly
480496
481497 let types = getRaisesTypes (raises)
498+ types.copyLineInfo (raises)
499+ for t in types:
500+ t.copyLineInfo (raises)
501+
482502 if isNoRaises (types):
483503 return quote do :
484504 if not (isNil (`fut`.internalError)):
@@ -497,8 +517,8 @@ macro internalCheckComplete*(fut: InternalRaisesFuture, raises: typed) =
497517 quote do : discard
498518 ),
499519 nnkElseExpr.newTree (
500- nnkRaiseStmt.newNimNode (lineInfoFrom = fut). add (
501- quote do : (` fut`. internalError)
520+ nnkRaiseStmt.newTree (
521+ nnkDotExpr. newTree ( fut, ident " internalError" )
502522 )
503523 )
504524 )
@@ -520,39 +540,51 @@ macro internalCheckComplete*(fut: InternalRaisesFuture, raises: typed) =
520540 ifRaise
521541 )
522542
523- proc read * [T: not void ](future: Future [T] ): lent T {.raises : [CatchableError ].} =
524- # # Retrieves the value of ``future``. Future must be finished otherwise
525- # # this function will fail with a ``ValueError`` exception.
543+ proc readFinished [T: not void ](fut: Future [T]): lent T {.
544+ raises : [CatchableError ].} =
545+ # Read a future that is known to be finished, avoiding the extra exception
546+ # effect.
547+ internalCheckComplete (fut)
548+ fut.internalValue
549+
550+ proc read * [T: not void ](fut: Future [T] ): lent T {.raises : [CatchableError ].} =
551+ # # Retrieves the value of `fut`.
526552 # #
527- # # If the result of the future is an error then that error will be raised.
528- if not future.finished ():
529- # TODO : Make a custom exception type for this?
530- raise newException (ValueError , " Future still in progress." )
553+ # # If the future failed or was cancelled, the corresponding exception will be
554+ # # raised.
555+ # #
556+ # # If the future is still pending, `FuturePendingError` will be raised.
557+ if not fut.finished ():
558+ raiseFuturePendingError (fut)
531559
532- internalCheckComplete (future)
533- future.internalValue
560+ fut.readFinished ()
534561
535- proc read * (future: Future [void ] ) {.raises : [CatchableError ].} =
536- # # Retrieves the value of ``future``. Future must be finished otherwise
537- # # this function will fail with a ``ValueError`` exception.
562+ proc read * (fut: Future [void ]) {.raises : [CatchableError ].} =
563+ # # Checks that `fut` completed.
538564 # #
539- # # If the result of the future is an error then that error will be raised.
540- if future.finished ():
541- internalCheckComplete (future)
542- else :
543- # TODO : Make a custom exception type for this?
544- raise newException (ValueError , " Future still in progress." )
565+ # # If the future failed or was cancelled, the corresponding exception will be
566+ # # raised.
567+ # #
568+ # # If the future is still pending, `FuturePendingError` will be raised.
569+ if not fut.finished ():
570+ raiseFuturePendingError (fut)
571+
572+ internalCheckComplete (fut)
545573
546- proc readError * (future : FutureBase ): ref CatchableError {.raises : [ValueError ].} =
547- # # Retrieves the exception stored in ``future` `.
574+ proc readError * (fut : FutureBase ): ref CatchableError {.raises : [FutureError ].} =
575+ # # Retrieves the exception of the failed or cancelled `fut `.
548576 # #
549- # # An ``ValueError`` exception will be thrown if no exception exists
550- # # in the specified Future.
551- if not (isNil (future.error)):
552- return future.error
553- else :
554- # TODO : Make a custom exception type for this?
555- raise newException (ValueError , " No error in future." )
577+ # # If the future was completed with a value, `FutureCompletedError` will be
578+ # # raised.
579+ # #
580+ # # If the future is still pending, `FuturePendingError` will be raised.
581+ if not fut.finished ():
582+ raiseFuturePendingError (fut)
583+
584+ if isNil (fut.error):
585+ raiseFutureCompletedError (fut)
586+
587+ fut.error
556588
557589template taskFutureLocation (future: FutureBase ): string =
558590 let loc = future.location[LocationKind .Create ]
@@ -568,18 +600,46 @@ template taskErrorMessage(future: FutureBase): string =
568600template taskCancelMessage (future: FutureBase ): string =
569601 " Asynchronous task " & taskFutureLocation (future) & " was cancelled!"
570602
571- proc waitFor * [T ](fut: Future [T] ): T {.raises : [CatchableError ].} =
572- # # ** Blocks** the current thread until the specified future finishes and
573- # # reads it, potentially raising an exception if the future failed or was
574- # # cancelled.
575- var finished = false
576- # Ensure that callbacks currently scheduled on the future run before returning
577- proc continuation (udata: pointer ) {. gcsafe .} = finished = true
603+ proc pollFor [F: Future | InternalRaisesFuture ](fut: F ): F {.raises : [].} =
604+ # Blocks the current thread of execution until `fut` has finished, returning
605+ # the given future.
606+ #
607+ # Must not be called recursively (from inside `async` procedures).
608+ #
609+ # See alse `awaitne`.
578610 if not (fut.finished ()):
611+ var finished = false
612+ # Ensure that callbacks currently scheduled on the future run before returning
613+ proc continuation (udata: pointer ) {.gcsafe .} = finished = true
579614 fut.addCallback (continuation)
615+
580616 while not (finished):
581617 poll ()
582- fut.read ()
618+
619+ fut
620+
621+ proc waitFor * [T: not void ](fut: Future [T]): lent T {.raises : [CatchableError ].} =
622+ # # Blocks the current thread of execution until `fut` has finished, returning
623+ # # its value.
624+ # #
625+ # # If the future failed or was cancelled, the corresponding exception will be
626+ # # raised.
627+ # #
628+ # # Must not be called recursively (from inside `async` procedures).
629+ # #
630+ # # See also `await`, `Future.read`
631+ pollFor (fut).readFinished ()
632+
633+ proc waitFor * (fut: Future [void ]) {.raises : [CatchableError ].} =
634+ # # Blocks the current thread of execution until `fut` has finished.
635+ # #
636+ # # If the future failed or was cancelled, the corresponding exception will be
637+ # # raised.
638+ # #
639+ # # Must not be called recursively (from inside `async` procedures).
640+ # #
641+ # # See also `await`, `Future.read`
642+ pollFor (fut).internalCheckComplete ()
583643
584644proc asyncSpawn * (future: Future [void ]) =
585645 # # Spawns a new concurrent async task.
@@ -943,7 +1003,7 @@ proc cancelAndWait*(future: FutureBase, loc: ptr SrcLoc): Future[void] {.
9431003
9441004 retFuture
9451005
946- template cancelAndWait * (future: FutureBase ): Future [void ] =
1006+ template cancelAndWait * (future: FutureBase ): Future [void ]. Raising ([ CancelledError ]) =
9471007 # # Cancel ``future``.
9481008 cancelAndWait (future, getSrcLocation ())
9491009
@@ -1500,37 +1560,56 @@ when defined(windows):
15001560
15011561{.pop .} # Automatically deduced raises from here onwards
15021562
1503- proc waitFor * [T, E](fut: InternalRaisesFuture [T, E]): T = # {.raises: [E]}
1504- # # **Blocks** the current thread until the specified future finishes and
1505- # # reads it, potentially raising an exception if the future failed or was
1506- # # cancelled.
1507- while not (fut.finished ()):
1508- poll ()
1563+ proc readFinished [T: not void ; E](fut: InternalRaisesFuture [T, E]): lent T =
1564+ internalCheckComplete (fut, E)
1565+ fut.internalValue
1566+
1567+ proc read * [T: not void , E](fut: InternalRaisesFuture [T, E]): lent T = # {.raises: [E, FuturePendingError].}
1568+ # # Retrieves the value of `fut`.
1569+ # #
1570+ # # If the future failed or was cancelled, the corresponding exception will be
1571+ # # raised.
1572+ # #
1573+ # # If the future is still pending, `FuturePendingError` will be raised.
1574+ if not fut.finished ():
1575+ raiseFuturePendingError (fut)
15091576
1510- fut.read ()
1577+ fut.readFinished ()
15111578
1512- proc read * [T: not void , E](future: InternalRaisesFuture [T, E]): lent T = # {.raises: [E, ValueError].}
1513- # # Retrieves the value of ``future``. Future must be finished otherwise
1514- # # this function will fail with a ``ValueError`` exception.
1579+ proc read * [E](fut: InternalRaisesFuture [void , E]) = # {.raises: [E].}
1580+ # # Checks that `fut` completed.
15151581 # #
1516- # # If the result of the future is an error then that error will be raised.
1517- if not future.finished ():
1518- # TODO : Make a custom exception type for this?
1519- raise newException (ValueError , " Future still in progress." )
1582+ # # If the future failed or was cancelled, the corresponding exception will be
1583+ # # raised.
1584+ # #
1585+ # # If the future is still pending, `FuturePendingError` will be raised.
1586+ if not fut.finished ():
1587+ raiseFuturePendingError (fut)
15201588
1521- internalCheckComplete (future, E)
1522- future.internalValue
1589+ internalCheckComplete (fut, E)
15231590
1524- proc read * [ E](future : InternalRaisesFuture [void , E]) = # {.raises: [E, CancelledError]. }
1525- # # Retrieves the value of ``future``. Future must be finished otherwise
1526- # # this function will fail with a ``ValueError`` exception .
1591+ proc waitFor * [T: not void ; E](fut : InternalRaisesFuture [T , E]): lent T = # {.raises: [E] }
1592+ # # Blocks the current thread of execution until `fut` has finished, returning
1593+ # # its value .
15271594 # #
1528- # # If the result of the future is an error then that error will be raised.
1529- if future.finished ():
1530- internalCheckComplete (future)
1531- else :
1532- # TODO : Make a custom exception type for this?
1533- raise newException (ValueError , " Future still in progress." )
1595+ # # If the future failed or was cancelled, the corresponding exception will be
1596+ # # raised.
1597+ # #
1598+ # # Must not be called recursively (from inside `async` procedures).
1599+ # #
1600+ # # See also `await`, `Future.read`
1601+ pollFor (fut).readFinished ()
1602+
1603+ proc waitFor * [E](fut: InternalRaisesFuture [void , E]) = # {.raises: [E]}
1604+ # # Blocks the current thread of execution until `fut` has finished.
1605+ # #
1606+ # # If the future failed or was cancelled, the corresponding exception will be
1607+ # # raised.
1608+ # #
1609+ # # Must not be called recursively (from inside `async` procedures).
1610+ # #
1611+ # # See also `await`, `Future.read`
1612+ pollFor (fut).internalCheckComplete (E)
15341613
15351614proc `or` * [T, Y, E1, E2 ] (
15361615 fut1: InternalRaisesFuture [T, E1 ],
0 commit comments