Skip to content
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

Add scheduled invocation to the RPC stubs #1309

Open
vigoo opened this issue Feb 10, 2025 · 2 comments
Open

Add scheduled invocation to the RPC stubs #1309

vigoo opened this issue Feb 10, 2025 · 2 comments
Assignees
Milestone

Comments

@vigoo
Copy link
Contributor

vigoo commented Feb 10, 2025

Using the new scheduled invocation host function directly from user code is inconvenient, so we want to generate scheduled invocation variants of the RPC functions in our wasm-rpc implementation.

We already generate two functions for each exported function in our genrerated stub resources:

  • one for blocking invocation
  • one for async invocation (and if it is a function with no return value, it is just enqueuing a pending invocation and returns)

We can add a third one that does scheduled invocation (or 4 if we want it with and without cancellation - to be decided).

Although we move to stubless RPC we did not fully deprecate the generated stubs yet, so this ticket should cover the following areas:

  • Adding the scheduled invocation function calls into the generated WIT (in wasm-rpc-stubgen)
  • Implement them in the generated Rust stub code (in wasm-rpc-stubgen) similar to how the existing invocation wrappers are implemented
  • Implement the same thing in the dynamic linker in worker-executor-base
  • Have worker executor tests both linked to stub and "stubless"

It is probably easier to first implement the generated stub version with a test and add support in the linker for the stubless mode after.

@vigoo vigoo added this to the Golem 1.2 milestone Feb 10, 2025
@jdegoes
Copy link
Contributor

jdegoes commented Feb 10, 2025

@vigoo

Another approach: let's say the user exposes:

add-todo: (name: string, description: string, due-date: option<date>) -> todo-id;

Then instead of having a variation (schedule-add-todo) in the RPC version, we could make a data structure for the invocation itself:

record add-todo {
  name: string,
  description: string,
  due-date: option<date>,
}

Then we could expose a single host function (together with this data structure):

schedule: (worker-id: worker-id, function: add-todo) -> result<_, error>;

In the common case where there is more than 1 choice of function to invoke, the function would be described by a variant.

Why might this be better?

Extensibility: we already have added 2 functions for each 1 export. Scheduling will be a 3rd variant. Down the road, there may be even more variants. For example: testing and mocking across RPC boundaries.

Perhaps even invoking can be simplified (invoke(AddTodo{...})), but I don't think so because WIT is too primitive (no generics, no way to describe the output is somehow dependent on the input).

I am not sure this is a good idea, but just wanted to mention it now before we find ourselves with generating 10 functions for every export. 😆

@vigoo
Copy link
Contributor Author

vigoo commented Feb 12, 2025

I have a few objections against this idea :)

  • Smaller one: Although it is not used now, the RPC internals were designed so that if you create the host resource targeting a given worker, that can keep some direct communication channel open in the host. I wanted to use this as an optimization in the future (right now non-local RPC calls are going through worker service per request).
  • Bigger one: I think even your proposal requires some kind of dependent typing which we don't have otherwise we loose the simple case of "invoke and await for its result"; We have different return types for functions returning with something. Returning the result itself (blocking invoke), returning nothing (invoke and scheduled invoke), returning a cancellation token (schedule with cancellation, if we do it), returning a pollable (or future in wasip3) for async invoke.
  • The discoverability of these generated interfaces are much better when there are separate methods with clearly defined parameters in return types.
  • If the result type is also a variant (because of the previous point) that is very inconvenient to use in practice.
  • Because these things are generated, it's cheap - easy to add now ones to the generator, for the user it is the same amount of WIT but in my opinion easier to read, and they also get generated in the bindings from the generated WIT. So does not cause any extra manual code to be written anywhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants