Welcome to the new Golem Cloud Docs! 👋
Common Guides
Worker to Worker Communication

Worker to Worker communication

See the Worker to Worker communication page for a general overview of how workers can invoke each other in Golem.

Setting up

For doing worker to worker communication between Golem components, all the components involved should be defined in the same application created with the golem app new command. Once the application is created the components can be added using golem component new.

Creating a project with two components

A project building two Golem components where one can call the other can be set up with the following primary steps:

Create the two components

First create two components using the techniques described in the "defining components" page for the chosen language.

In the following example snippets we will use the following component names:

  • example:caller the component that will call another component
  • example:provider the component that provides some API that example:caller will call

Set up the dependencies between the components

In the root golem.yaml file add a dependencies section:

dependencies:
  example:caller:
    - target: example:provider
      type: wasm-rpc

The type field can be one of the following:

  • wasm-rpc: Worker to Worker communication using dynamic linking. This is the default and recommended option. The resulting component will import client functions for connecting to the other worker, and these imports will be resolved by Golem automatically when the worker starts.
  • static-wasm-rpc: Worker to Worker communication using static linking. In this case the client gets generated as Rust source code, and compiled and linked statically with your component. Using this option can be useful for debugging but otherwise deprecated and requires a working Rust toolchain on your computer.

Check it works by running golem app build in the root directory of the application, which should build all the components.

Sometimes it can be useful for a component to depend on itself:

  • Different workers of the same component can communicate with each other
  • A worker can schedule a call for later for another worker, or even itself

For this just use the same component name in the target field of the dependencies section.

Import the generated client in the caller component

When defining RPC dependencies like above, the golem app build command will automatically generate client WIT interfaces for all the target components, and import them into the components that are dependent on them.

For example with the above setup, in the end the following import would be added in the example:caller component's WIT world:

package example:caller;
 
world example-caller {
    import example:provider-client/example-provider-client;
    // ...
}

Note that the wit directory of each component remains untouched and should only be modified by the user. These automatic imports are generated in the wit-generated directory and should not be modified manually.

The exact name to be imported depends on what package and component names were used when creating the target component.

In this example the package name was example:provider. This gives the generated client package example:provider-client with an exported interface example-provider-client.

Start implementing the components

With this the workspace is set up, and the generated clients can be used from the caller component to call the target component, as it is described in the next section.

Once the code is written, just use golem app build to build all the components.

Writing blocking remote calls

For each exported function in the target component, the generated client contains two exported variants:

  • A blocking variant, prefixed with blocking-, which does not return until the remote worker finished processing the call.
  • A non-blocking variant, which returns a pollable result immediately (unless for remote functions without any return value, in which case it just triggers the invocation but does not return anything)

To use the blocking variants we need to construct a resource from the generated client, passing the remote Golem URI of the target worker to the constructor.

Taking the default template as the target component, which has the following exported interface:

package example:provider;
 
interface example-providerapi {
  add: func(value: u64);
  get: func() -> u64;
}
 
world example-provider {
  export example-provider-api;
}

The generated client will contain the following functions:

package example:provider-client;
 
interface example-provider-client {
  use wasi:io/poll@0.2.0.{ pollable as wasi-io-pollable };
  use wasi:clocks/wall-clock@0.2.0.{ datetime as wasi-clocks-datetime };
  use golem:rpc/types@0.2.0.{ worker-id as golem-rpc-worker-id, cancellation-token as golem-rpc-cancellation-token };
 
  resource future-get-result {
    subscribe: func() -> pollable;
    get: func() -> option<u64>;
  }
  resource example-provider-api {
    constructor(worker-name: string);
    custom: static func(worker-id: golem-rpc-worker-id) -> example-provider-api;
 
    blocking-add: func(value: u64);
    add: func(value: u64);
    schedule-add: func(value: u64, scheduled-for: wasi-clocks-datetime) -> golem-rpc-cancellation-token;
 
    blocking-get: func() -> u64;
    get: func() -> future-get-result;
    schedule-get: func(scheduled-for: wasi-clocks-datetime) -> golem-rpc-cancellation-token;
  }
 
}
 
world wasm-rpc-client-example-provider {
  export example-provider-client;
}

If the targeted component is ephemeral, the interface is slightly different: there is no need to pass a worker name to the constructor, and the custom constructor just takes a component-id.

To use this generated example-providerapi resource, the following steps are needed in the caller component (example:caller in the example):

Import the generated types

The generated bindings for the generated stubs need to be imported in the module where the remote procedure calls are made.

As this import is automatically added to the caller component, we can import the generated bindings in our source code to be able to call the example:provider workers:

use crate::bindings::example::provider_client::example_provider_client::ExampleProviderApi;

Deciding which constructor to use

Each generated client resource has two constructors:

  • The default one (new) takes a worker name string for durable components, and nothing for ephemeral components.
  • The custom one (custom) is a static method that takes a worker ID for durable components, or a component ID for ephemeral components.

When using the default constructors, Golem assumes that the target component's name is the one specified in the app manifest (in our example: example:provider). This is always true if the application was deployed using the golem app deploy command. The custom constructor is there for advanced use cases where you want to target a separately deployed component with a different name and you know its identifier.

Note that if the client is for a remote resource (and not for the remote component's top-level exports), the constructors will also require passing the resource's constructors beside the ones defining the RPC target, such as the worker name.

Construct and use the API resource

In our example the example:provider is a durable component, so the ExampleProviderApi resource can be constructed by passing a worker name and then used to call the remote functions.

let remote_api = ExampleProviderApi::new("worker-1");
remote_api.blocking_add(3);

Writing non-blocking remote calls

Using the non-blocking variants of the generated stub functions requires the same steps as the blocking variants, but the returned value is a special future which needs to be subscribed to and polled using the low-level WASI poll interface.

The following snippet demonstrates how to get the value of three counters simultaneously, assuming that counter1, counter2 and counter3 are all insteances of the generated stub's Api resource:

let remote_api1 = new ExampleRustApi("worker-1");
let remote_api2 = new ExampleRustApi("worker-2");
let remote_api3 = new ExampleRustApi("worker-3");
 
// Making the non-blocking calls
let future_value1 = remote_api1.get();
let future_value2 = remote_api2.get();
let future_value3 = remote_api3.get();
 
let futures = &[&future_value1, &future_value2, &future_value3];
 
// Subscribing to get the results
let poll_value1 = future_value1.subscribe();
let poll_value2 = future_value2.subscribe();
let poll_value3 = future_value3.subscribe();
 
let mut values = [0u64; 3];
let mut remaining = vec![&poll_value1, &poll_value2, &poll_value3];
let mut mapping = vec![0, 1, 2];
 
// Repeatedly poll the futures until all of them are ready
while !remaining.is_empty() {
    let poll_result = bindings::wasi::io::poll::poll(&remaining);
 
    // poll_result is a list of indexes of the futures that are ready
    for idx in &poll_result {
        let counter_idx = mapping[*idx as usize];
        let future = futures[counter_idx];
        let value = future
            .get()
            .expect("future did not return a value because after marked as completed");
        values[counter_idx] = value;
    }
 
    // Removing the completed futures from the list
    remaining = remaining
        .into_iter()
        .enumerate()
        .filter_map(|(idx, item)| {
            if poll_result.contains(&(idx as u32)) {
                None
            } else {
                Some(item)
            }
        })
        .collect();
 
    // Updating the index mapping
    mapping = mapping
        .into_iter()
        .enumerate()
        .filter_map(|(idx, item)| {
            if poll_result.contains(&(idx as u32)) {
                None
            } else {
                Some(item)
            }
        })
        .collect();
}
 
// values contains the results of the three calls

Deploying the resulting components

To build deployable WASM files of the involved components, use

golem app build

and upload them using

golem app deploy