Course contentsShow
← Rust
Lesson 1124 of 1,91010. Concurrency PrimitivesFree lesson

Transactional Messaging Patterns

You will ensure consistency across multiple channels by implementing two-phase commit flows.

In this lesson, you will learn how to maintain consistency across distributed or decoupled components by implementing a Two-Phase Commit (2PC) flow using Rust's channel primitives.

The Problem: Partial Failure

When you send messages across different channels (e.g., updating a database actor and a logging actor), a failure in the second step leaves your system in an inconsistent state. If the database update succeeds but the notification fails, you have an "orphan" transaction. Transactional messaging solves this by treating a sequence of operations as an atomic unit.

The Two-Phase Commit Pattern

To implement this, we coordinate participants using two distinct phases:

  1. Prepare Phase: The coordinator sends a "Prepare" message to all participants. Participants check if they can perform the action (e.g., verifying capacity or data validity) and reply with a "Vote."
  2. Commit Phase: If all participants vote "Yes," the coordinator sends a "Commit" message. If any participant votes "No" or times out, the coordinator sends an "Abort" message.

Implementation Concept

Using One-Shot Channels for the voting process is the idiomatic way to handle this in Rust, as it ensures each request receives exactly one response.

// Simplified logic for a coordinator
let (tx_vote, rx_vote) = oneshot::channel();

// Phase 1: Prepare
participant_a.send(Message::Prepare { respond_to: tx_vote }).await;

match rx_vote.await {
    Ok(Vote::Yes) => {
        // Phase 2: Commit
        participant_a.send(Message::Commit).await;
    }
    _ => {
        // Phase 2: Abort
        participant_a.send(Message::Abort).await;
    }
}

By decoupling the intent to perform an action from the finalization of that action, you ensure that no participant applies a state change unless every other participant has confirmed their ability to do the same. This pattern transforms "fire and forget" messaging into a robust, synchronized transaction.

Key Takeaway: Transactional messaging ensures system-wide consistency by requiring a unanimous "prepare" vote before broadcasting a final "commit" signal to all channel participants.