status-im / nim-chronos

Chronos - An efficient library for asynchronous programming
https://status-im.github.io/nim-chronos/docs/chronos
Apache License 2.0
353 stars 51 forks source link

async raises of #327

Open Menduist opened 1 year ago

Menduist commented 1 year ago

Note: this PR targets https://github.com/status-im/nim-chronos/pull/251

While working on https://github.com/status-im/nim-chronos/pull/298, I quickly realized that we need such a system, for instance for wait:

proc wait*[T](fut: Future[T], timeout = InfiniteDuration): Future[T] {.asyncraisesof: [fut].}

This PR adds the possibility to do "async raises of" (similar to "effectsOf"):

    proc test44 {.asyncraises: [ValueError], async.} = discard
    proc testOther {.asyncraises: [IOError], async.} = discard

    proc wrapper1(fut: Future[void]) {.asyncraises: [CancelledError], async, asyncraisesof: [fut].} =
      await fut

    proc test55 {.asyncraises: [ValueError, CancelledError], async.} =
      await wrapper1(test44())
    waitFor(test55())

    checkNotCompiles:
      proc wrapper2(fut: Future[void]) {.asyncraises: [CancelledError], async, asyncraisesof: [fut].} =
        await testOther()
      waitFor wrapper2(test44())

(this also works with "manual async", see tests)

This is a bit complex, but essentially relies on two things:


wrapper1 will become a generic taking the error types as generic parameters:

proc wrapper1[Err1_469768925](fut: RaiseTrackingFuture[void, Err1_469768925]): auto {.
    stackTrace: off, raises: [], gcsafe.} =

The return type will then be rewritten using a typed macro (that will have the generic types instantiated):

  result = RaiseTrackingFuture[void,
                               combineAsyncExceptions(CancelledError, Err1)](nil)

(note that instead of auto & result =, a typed macro could directly rewrite the return type, but did this here for simplicity)

This will give the correct return type to the procedure once instantiated.


For .async. procedures, a second type macro will make sure that the iterator can only raise the correct exceptions:

  asyncinternalraises(iterator wrapper1_469768926(
      chronosInternalRetFuture: Future[void]): FutureBase {.closure, gcsafe,
      gcsafe.} =
        [...]
          , [CancelledError, Err1])

At instantiation, will rewrite to:

iterator wrapper1_469769166(chronosInternalRetFuture: Future[void]): FutureBase {.
    closure, gcsafe, gcsafe, raises: [CancelledError, ValueError].} =

I'm not very happy with the code ATM hence the draft, but it works in basic cases at least