💥 WASM Workflows#1239
Conversation
de2ced4 to
f913100
Compare
bd5a737 to
ef99f7e
Compare
chris-olszewski
left a comment
There was a problem hiding this comment.
Took awhile, but got through this. Mostly questions to help me write the release notes when this lands.
| has_init: #has_init, | ||
| init_takes_input: #init_has_input, | ||
| signals: vec![#(#signal_names),*], | ||
| queries: vec![#(#query_names),*], |
There was a problem hiding this comment.
Note to self, groundwork for listing query names on missing query error
| /// `ActivityContext` parameter and have a body of exactly `unimplemented!()`. The macro emits | ||
| /// the same marker structs and inherent consts as `#[activities]` so call sites use the same | ||
| /// `MyActivities::greet` form, but no execution machinery is generated. | ||
| /// | ||
| /// Intended for workflow crates that need typed activity declarations which are implemented | ||
| /// elsewhere by a separate worker crate (or in another language). |
There was a problem hiding this comment.
Maybe split out the references to the marker structs/consts to the bottom. I think we're burying the real usefulness of this macro with implementation details.
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn clear_search_attributes_on_continue_as_new() { |
There was a problem hiding this comment.
Not to self, was this bug or just asserting some behavior?
| let result = handle.get_result(Default::default()).await; | ||
| assert_matches!(result, Err(WorkflowGetResultError::Terminated { .. })); | ||
| reset_handle.get_result(Default::default()).await.unwrap(); | ||
| assert!( |
There was a problem hiding this comment.
Note to self, was there a bug fix for this additional assertion?
| "WASM workflow component file does not exist: {}", | ||
| path.display() | ||
| ); | ||
| } |
There was a problem hiding this comment.
Unsure of the benefit of this check compared to waiting until it fails during workflow registration
There was a problem hiding this comment.
Eh, it's a bit more specific at least. The empty bytes one below is kinda silly tho.
| pub fn from_file( | ||
| component_id: impl Into<String>, | ||
| path: impl Into<PathBuf>, | ||
| ) -> Result<Self, anyhow::Error> { |
There was a problem hiding this comment.
Either in PR or follow up: moving to error enum to avoid anyhow in the public API
There was a problem hiding this comment.
Done now, just using std errors since none of these really seem useful to match on.
1df72e5 to
336ef02
Compare
Remove proto dupe Take more things out of workflow crate Attempting to add interfaces for WASM/Native workflow execution Move more things behind runtime interfaces Keep job sequencing logic on host Consolidate shared data Various cleanup Fix two small regressions Fix rebase issues
Tip
It's easier to review the first two commits separately rather than trying to look at all of it at once. Those two commits contain almost all the main work, the ones after are smaller cleanup.
What was changed
Separated out the workflow APIs into their own crate (as well as the subset of common they need) and enabled the compilation of WASM workflows.
This change shouldn't be breaking for current Rust SDK users. Everything that was moved to new crates is re-exported from its old location. The execution semantics of workflows should remain the same as well, although the logic for driving the workflow future has indeed changed.
Note
Protos are still [de]serialized on either side of the WASM boundary, which is not ideal but avoids adding even more heft to this already-hefty PR. I think the long-term plan would be to codegen WIT types from our Proto definitions (for every language we end up doing WASM workflows for), but I wasn't going to go there right away.
Why?
WASM workflows unlock some very cool potential future use cases but also, at minimum, provide a viable part-compile-time part-runtime sandbox for Rust SDK users.
More extensive AI generated change summary:
Summary
Splits the workflow runtime out of crates/sdk into a new temporalio-workflow crate and adds a WASM execution backend alongside the existing native one.
New crates
dependencies that won't build for wasm32, so guest-relevant code was moved/duplicated here.
WASM workflow execution architecture
The runtime defines a single logical guest/host interface, expressed two ways:
- workflow-guest (exported by the workflow): list-workflows, instantiate-workflow, and a workflow-instance resource with activate and poll-routine.
- workflow-host (imported by the workflow): set-current-details, push-command, etc.
- Combined into the workflow-module world.
no serialization.
Both backends plug into the same WorkflowDefinitions registry via a WorkflowExecutionFactory, so the worker is backend-agnostic.
Native backend: workflows compile into the worker binary; the worker invokes the Rust traits directly in-process. This is the existing path, refactored to sit behind the new trait surface.
WASM backend (crates/sdk/src/workflow_wasm.rs, crates/workflow/src/component.rs):
into the same registry as native workflows.
impls (generated by #[workflow_methods]) into the WIT exports. Workflow authors write the same code as native — the macro is the only extra step.
Polling model: the guest interface is intentionally synchronous — activate and poll-routine return ordinary results, and the guest signals "blocked" by returning
routine-poll-result.made-progress = false. The host re-enters poll-routine after the relevant activation lands. This matches what stable wasmtime + wit-bindgen support today; wit/README.mddocuments the planned WASIp3 / Preview-3 evolution to async func (a major-version WIT bump).
Other changes