Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/per-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,42 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: cargo integ-test docker_

examples:
name: "Build and run examples"
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: "23.x"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/master' }}
- name: Build examples
run: cargo build --examples -p temporalio-sdk --features examples
- name: Install Temporal CLI
uses: temporalio/setup-temporal@v0
- name: Start Temporal dev server
run: |
temporal server start-dev --headless &
sleep 5
temporal operator search-attribute create --name CustomKeywordField --type Keyword
temporal operator search-attribute create --name CustomIntField --type Int
- name: Run examples
run: |
for dir in crates/sdk/examples/*/; do
sample="$(basename "$dir" | tr '_' '-')"
cargo run -p temporalio-sdk --features examples --example "${sample}-worker" &
WORKER_PID=$!
timeout 20 cargo run -p temporalio-sdk --features examples --example "${sample}-starter"
kill $WORKER_PID 2>/dev/null || true
wait $WORKER_PID 2>/dev/null || true
done

c-bridge-static-link-test:
name: "C bridge static link test"
runs-on: ubuntu-latest
Expand Down
140 changes: 139 additions & 1 deletion crates/sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ repository = "https://github.com/temporalio/sdk-core"
keywords = ["temporal", "workflow"]
categories = ["development-tools"]
readme = "README.md"
autoexamples = false

[dependencies]
async-trait = "0.1"
anyhow = "1.0"
bon = { workspace = true }
derive_more = { workspace = true }
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"] }
futures-util = { version = "0.3", default-features = false, features = [
"async-await-macro",
] }
gethostname = "1.0.2"
parking_lot = { version = "0.12", features = ["send_guard"] }
prost-types = { workspace = true }
Expand Down Expand Up @@ -58,6 +61,141 @@ rstest = "0.26"
[features]
default = []
antithesis_assertions = ["temporalio-sdk-core/antithesis_assertions"]
examples = ["serde/derive", "dep:serde_json"]

[dependencies.serde_json]
version = "1"
optional = true

[[example]]
name = "hello-world-worker"
path = "examples/hello_world/worker.rs"
required-features = ["examples"]

[[example]]
name = "hello-world-starter"
path = "examples/hello_world/starter.rs"
required-features = ["examples"]

[[example]]
name = "activity-heartbeating-worker"
path = "examples/activity_heartbeating/worker.rs"
required-features = ["examples"]

[[example]]
name = "activity-heartbeating-starter"
path = "examples/activity_heartbeating/starter.rs"
required-features = ["examples"]

[[example]]
name = "timer-examples-worker"
path = "examples/timer_examples/worker.rs"
required-features = ["examples"]

[[example]]
name = "timer-examples-starter"
path = "examples/timer_examples/starter.rs"
required-features = ["examples"]

[[example]]
name = "message-passing-worker"
path = "examples/message_passing/worker.rs"
required-features = ["examples"]

[[example]]
name = "message-passing-starter"
path = "examples/message_passing/starter.rs"
required-features = ["examples"]

[[example]]
name = "child-workflows-worker"
path = "examples/child_workflows/worker.rs"
required-features = ["examples"]

[[example]]
name = "child-workflows-starter"
path = "examples/child_workflows/starter.rs"
required-features = ["examples"]

[[example]]
name = "continue-as-new-worker"
path = "examples/continue_as_new/worker.rs"
required-features = ["examples"]

[[example]]
name = "continue-as-new-starter"
path = "examples/continue_as_new/starter.rs"
required-features = ["examples"]

[[example]]
name = "saga-worker"
path = "examples/saga/worker.rs"
required-features = ["examples"]

[[example]]
name = "saga-starter"
path = "examples/saga/starter.rs"
required-features = ["examples"]

[[example]]
name = "local-activities-worker"
path = "examples/local_activities/worker.rs"
required-features = ["examples"]

[[example]]
name = "local-activities-starter"
path = "examples/local_activities/starter.rs"
required-features = ["examples"]

[[example]]
name = "search-attributes-worker"
path = "examples/search_attributes/worker.rs"
required-features = ["examples"]

[[example]]
name = "search-attributes-starter"
path = "examples/search_attributes/starter.rs"
required-features = ["examples"]

[[example]]
name = "updatable-timer-worker"
path = "examples/updatable_timer/worker.rs"
required-features = ["examples"]

[[example]]
name = "updatable-timer-starter"
path = "examples/updatable_timer/starter.rs"
required-features = ["examples"]

[[example]]
name = "polling-worker"
path = "examples/polling/worker.rs"
required-features = ["examples"]

[[example]]
name = "polling-starter"
path = "examples/polling/starter.rs"
required-features = ["examples"]

[[example]]
name = "cancellation-worker"
path = "examples/cancellation/worker.rs"
required-features = ["examples"]

[[example]]
name = "cancellation-starter"
path = "examples/cancellation/starter.rs"
required-features = ["examples"]

[[example]]
name = "schedules-worker"
path = "examples/schedules/worker.rs"
required-features = ["examples"]

[[example]]
name = "schedules-starter"
path = "examples/schedules/starter.rs"
required-features = ["examples"]

[lints]
workspace = true
46 changes: 46 additions & 0 deletions crates/sdk/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Temporal Rust SDK Examples

These examples demonstrate common Temporal patterns using the Rust SDK. Each example is a self-contained directory with its own README, workflow definitions, a worker, and a starter.

## Prerequisites

A running Temporal server. The quickest way to get one locally:

```bash
temporal server start-dev
```

By default, examples connect to `http://localhost:7233` in the `default` namespace.
Override with `TEMPORAL_SERVICE_ADDRESS` and `TEMPORAL_NAMESPACE` environment variables.

## Running an Example

From the `crates/sdk/` directory, each example has a worker and a starter. Run the worker first, then the starter in a separate terminal:

```bash
# Terminal 1: start the worker
cargo run --features examples --example hello-world-worker

# Terminal 2: start the workflow
cargo run --features examples --example hello-world-starter
```

See each example's README for details and expected output.

## Examples

- [Hello World](hello_world/) — Basic workflow that calls a single activity
- [Activity Heartbeating](activity_heartbeating/) — Long-running activity with heartbeating and resume-on-retry
- [Timer Examples](timer_examples/) — Workflow timers, racing timers against activities, and timer cancellation
- [Message Passing](message_passing/) — Signals, queries, and updates on a workflow
- [Child Workflows](child_workflows/) — Starting and collecting results from child workflows
- [Continue-As-New](continue_as_new/) — Long-running workflows via continue-as-new
- [Saga](saga/) — Saga/compensation pattern for distributed transactions
- [Patching](patching/) — Workflow versioning with `ctx.patched()`
- [Local Activities](local_activities/) — Local vs remote activity execution
- [Search Attributes](search_attributes/) — Reading and upserting workflow search attributes
- [Updatable Timer](updatable_timer/) — Timer that can be rescheduled via signals
- [Polling](polling/) — Polling an external condition with activities and timers
- [Cancellation](cancellation/) — Workflow and activity cancellation with cleanup
- [Encryption](encryption/) — Custom `PayloadCodec` for payload encryption
- [Schedules](schedules/) — Creating and managing scheduled workflows
22 changes: 22 additions & 0 deletions crates/sdk/examples/activity_heartbeating/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Activity Heartbeating

This sample shows a long-running activity that periodically heartbeats its progress. On retry, the activity resumes from the last heartbeated checkpoint instead of starting over.

### Running this sample

1. `temporal server start-dev` to start the Temporal server.
2. In another terminal, start the worker:

```bash
cargo run --features examples --example activity-heartbeating-worker
```

3. In another terminal, run the workflow:

```bash
cargo run --features examples --example activity-heartbeating-starter
```

The starter should print:

Workflow result: Completed 10 steps
33 changes: 33 additions & 0 deletions crates/sdk/examples/activity_heartbeating/starter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod workflows;

use temporalio_client::{
Client, ClientOptions, Connection, WorkflowGetResultOptions, WorkflowStartOptions,
envconfig::LoadClientConfigProfileOptions,
};
use workflows::HeartbeatingWorkflow;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn_opts, client_opts) =
ClientOptions::load_from_config(LoadClientConfigProfileOptions::default())?;
let connection = Connection::connect(conn_opts).await?;
let client = Client::new(connection, client_opts)?;

let handle = client
.start_workflow(
HeartbeatingWorkflow::run,
10u32,
WorkflowStartOptions::new("activity-heartbeating", "activity-heartbeating-workflow-id")
.build(),
)
.await?;

println!("Started workflow, run_id: {:?}", handle.run_id());

let result = handle
.get_result(WorkflowGetResultOptions::default())
.await?;
println!("Workflow result: {result}");

Ok(())
}
33 changes: 33 additions & 0 deletions crates/sdk/examples/activity_heartbeating/worker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod workflows;

use temporalio_client::{
Client, ClientOptions, Connection, envconfig::LoadClientConfigProfileOptions,
};
use temporalio_common::telemetry::TelemetryOptions;
use temporalio_sdk::{Worker, WorkerOptions};
use temporalio_sdk_core::{CoreRuntime, RuntimeOptions};
use workflows::{HeartbeatingActivities, HeartbeatingWorkflow};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let runtime = CoreRuntime::new_assume_tokio(
RuntimeOptions::builder()
.telemetry_options(TelemetryOptions::builder().build())
.build()?,
)?;
let (conn_opts, client_opts) =
ClientOptions::load_from_config(LoadClientConfigProfileOptions::default())?;
let connection = Connection::connect(conn_opts).await?;
let client = Client::new(connection, client_opts)?;

let worker_options = WorkerOptions::new("activity-heartbeating")
.register_workflow::<HeartbeatingWorkflow>()
.register_activities(HeartbeatingActivities)
.build();

let mut worker = Worker::new(&runtime, client, worker_options)?;
println!("Worker started on task queue: activity-heartbeating");
worker.run().await?;

Ok(())
}
Loading
Loading