# How df.cancel() Interacts with the Duroxide Runtime in pg_durable: Cancellation Flow Explained

> Understand the df.cancel() interaction with Duroxide runtime in pg_durable. Learn the cancellation flow from PL/pgSQL to Rust and Tokio for graceful worker shutdowns. Explore the repository now.

- Repository: [Microsoft/pg_durable](https://github.com/microsoft/pg_durable)
- Tags: internals
- Published: 2026-06-08

---

**When you invoke `df.cancel()` in pg_durable, a PL/pgSQL wrapper calls the Rust `cancel_durable_function` in [`src/client.rs`](https://github.com/microsoft/pg_durable/blob/main/src/client.rs), which uses a cached Tokio runtime to request a graceful Duroxide worker shutdown, and then updates `df.instances.status` to `'cancelled'`.**

In the `microsoft/pg_durable` extension for PostgreSQL, stopping a running durable orchestration requires precise coordination between SQL, Rust, and the **Duroxide background worker**. The **`df.cancel()`** function serves as the user-facing entry point that triggers this cross-language cancellation flow. Understanding how `df.cancel()` interacts with the Duroxide runtime helps operators safely terminate orchestrations without leaving stale state.

## SQL Wrapper and Ownership Check

The `df.cancel(instance_id TEXT, reason TEXT DEFAULT 'Cancelled by user')` signature is defined in the extension’s SQL catalog. When executed, a PL/pgSQL wrapper first verifies that the calling role owns the target instance, then calls into the Rust side of pg_durable. According to the source code documented in [`docs/rls.md`](https://github.com/microsoft/pg_durable/blob/main/docs/rls.md), this ownership check is critical because the subsequent `UPDATE` runs under the caller’s role and relies on **Row-Level Security (RLS)** filtering; non-owned rows silently affect zero rows.

## Rust Client Call in src/client.rs

After the ownership check passes, PostgreSQL dispatches to the Rust-implemented function `cancel_durable_function` exposed via pgrx. Inside [`src/client.rs`](https://github.com/microsoft/pg_durable/blob/main/src/client.rs), this function obtains the cached **Tokio runtime** via `get_client_runtime()` and the cached Duroxide client via `get_duroxide_client()`. It then blocks on an asynchronous request to `client.cancel_instance(instance_id, reason)`, returning a formatted error string to PostgreSQL if the Duroxide request fails.

```rust
// Inside src/client.rs – the Rust side that the PL/pgSQL wrapper calls
pub fn cancel_durable_function(instance_id: &str, reason: &str) -> Result<(), String> {
    let rt = get_client_runtime();
    let client = get_duroxide_client()?;
    rt.block_on(async {
        client
            .cancel_instance(instance_id, reason)
            .await
            .map_err(|e| format!("Failed to cancel durable function: {e:?}"))?;
        Ok(())
    })
}

```

## Duroxide Worker Graceful Shutdown

Once the Rust client forwards the request, the Duroxide background worker performs a graceful shutdown of the target orchestration. The worker stops any pending timers, cancels currently running activities, and propagates the cancellation signal to any child instances. This ensures that the entire durable function tree terminates cleanly before PostgreSQL finalizes the row state.

## RLS Bypass and Final Status Update

After the Duroxide client call returns, the PL/pgSQL wrapper executes `UPDATE df.instances SET status = 'cancelled'` through PostgreSQL’s SPI interface. Because the Duroxide worker operates as a super-user-level **worker role**, it bypasses RLS policies that would otherwise restrict the calling role. If the instance was already terminal or not owned by the caller, the `UPDATE` affects zero rows and the preceding client call is effectively a no-op, making `df.cancel()` idempotent.

## Using df.cancel() in Practice

Users interact with the flow entirely through SQL, with user-facing semantics documented in [`docs/api-reference.md`](https://github.com/microsoft/pg_durable/blob/main/docs/api-reference.md). A standard call targets a running instance by its unique ID, while repeated calls against a finished instance leave the status unchanged. End-to-end coverage in [`tests/e2e/sql/03_loops.sql`](https://github.com/microsoft/pg_durable/blob/main/tests/e2e/sql/03_loops.sql) verifies that the cancellation is reflected instantly in `df.instances.status`, and the function returns the instance ID on success or an *instance not found or access denied* message when checks fail.

```sql
-- Cancel a specific orchestration
SELECT df.cancel('a1b2c3d4', 'Manual stop');

-- Cancelling an already-completed instance is a no-op (status unchanged)
SELECT df.cancel('finished-id', 'Ignored reason');

```

## Summary

- **`df.cancel()`** is a PL/pgSQL wrapper that bridges SQL, Rust in [`src/client.rs`](https://github.com/microsoft/pg_durable/blob/main/src/client.rs), and the Duroxide background worker.
- The Rust function `cancel_durable_function` forwards requests through a cached Tokio runtime and Duroxide client.
- The Duroxide worker performs a graceful shutdown of timers, activities, and child instances.
- A final `UPDATE` on `df.instances` persists `status = 'cancelled'` and is protected by a worker-role RLS bypass.
- Ownership checks make the operation idempotent for non-owned or already-terminal orchestrations.

## Frequently Asked Questions

### What happens if I call df.cancel() on an orchestration I do not own?

The ownership check causes the subsequent `UPDATE df.instances` to affect zero rows, and the earlier Duroxide client call becomes a no-op because the instance is filtered out. The function returns an *instance not found or access denied* message without modifying any state.

### Is df.cancel() idempotent for already-completed instances?

Yes. Invoking `df.cancel()` on an instance that is already terminal is a safe no-op; the status remains unchanged and the Duroxide worker handles the redundant request gracefully.

### Which PostgreSQL role performs the final df.instances status update?

The Duroxide background worker runs as a dedicated worker role with elevated privileges, allowing it to bypass Row-Level Security policies and persist the `status = 'cancelled'` update even when the calling role would normally be restricted.

### How does the Duroxide runtime stop in-flight activities during cancellation?

The Duroxide worker performs a graceful shutdown that stops pending timers, cancels currently running activities, and propagates the cancellation signal downward to any child instances in the orchestration tree.