-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coroutines #5
Coroutines #5
Conversation
@achille-roussel I merged the function/coroutine implementations into one I split out two packages:
The top-level exports are looking better now:
Users shouldn't have to interact with the two new packages much (if at all). They can create functions and then use/compose them using the provided methods on If they want more control, or need to do something lower level (like a tail call), or want to do something where it's hard for us to provide a generic API (e.g. awaiting the results of calls with different input/output types) then they can use the packages directly. It's difficult to break down the We could possibly split out a I toyed with using the name Let me know what you think. |
coroutine.go
Outdated
// Close closes the coroutine. | ||
// | ||
// In volatile mode, Close destroys all running instances of the coroutine. | ||
// In durable mode, Close is a noop. | ||
func (c *Coroutine[I, O]) Close() error { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
|
||
for _, fn := range c.instances { | ||
fn.Stop() | ||
fn.Next() | ||
} | ||
clear(c.instances) | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this useful outside of tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, yes. There are no restrictions on whether volatile or durable coroutines can be registered with a Dispatch endpoint. You might want to test volatile coroutines with the Dispatch CLI, for example, in which case the tear down is still applicable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll see if I can rework some of this.
Co-authored-by: Achille <[email protected]>
Co-authored-by: Achille <[email protected]>
Rename Coroutine[I,O] to Function[I,O] and keep the concept of coroutines an implementation detail. Rename the old Function interface to AnyFunction, and hide the exported methods from users. Remove the ability to execute a function via Run, instead forcing users to use dispatchtest.Run(..).
@achille-roussel I had another round of simplifications:
Here's what we have:
Let me know what you think. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like how lean the API has gotten in the latest changes, nice work 👏
Following on from #2, this PR integrates https://github.com/dispatchrun/coroutine.
Dispatch functions are now coroutines. They're identical in terms of their signature and usage (accepting
I any
and returningO any
), the difference is that they're able to yield control to Dispatch during execution.Yielding to Dispatch
A new helper function is available for yielding control to Dispatch:
This
Yield
function can be called anywhere in the call stack of a coroutine.The SDK automatically suspends coroutines at these yield points, returning the
Response
to Dispatch. When Dispatch responds with a newRequest
, the SDK takes care of resuming execution from the point the coroutine was suspended at.Await & Gather
Users probably won't want/need to use the
Yield
function directly, and may instead prefer one of the higher-level helpers.An
Await
function is provided for awaiting the results of one or more calls:Internally,
Await
submits calls to Dispatch and then continuously polls until results are available. The strategy controls how the function handles failure (should it wait for all results but return as soon as any call fails? or return as soon as any call succeeds? this is analogous toPromise.all
vs.Promise.any
from JavaScript).Higher-level methods have also been added to the function/coroutine instances, allowing users to asynchronously make calls and await their result(s). These helper methods have input and output types that match the function/coroutine:
Here's an example showing how functions can be composed using the new helpers:
Durable vs. Volatile Coroutines
Coroutines provided by https://github.com/dispatchrun/coroutine can be run in two modes: volatile mode, where coroutines are suspended in memory using a channel, and durable mode where the coroutines are unwound and can be serialized. Durable coroutines require an extra compilation step: see the
coroc
compiler from https://github.com/dispatchrun/coroutine?tab=readme-ov-file#durable-coroutines.In durable mode, the SDK takes care of serializing a coroutine and sending its serialized state back to Dispatch. In this mode, the coroutine can be resumed in another location. In volatile mode, the SDK instead sends a reference to a suspended coroutine back to Dispatch. The coroutine cannot be resumed in another location. Volatile coroutines are still useful when unit testing coroutines. A
dispatchtest.Run
helper has been provided for running a function/coroutine (and any nested functions/coroutines) entirely in memory.