File: two-phase-commit/twophasecommit.py

Date: 2026-05-29

Time: 09:08

two-phase-commit/twophasecommit.py

Purpose

This file implements the Two-Phase Commit (2PC) distributed transaction protocol as described in DDIA Chapter 9. It provides a self-contained simulation of a coordinator and multiple participant nodes that can atomically commit or abort distributed transactions across participants. The file owns the entire 2PC protocol lifecycle: transaction initiation, prepare/vote collection, commit/abort decision, lock management, and crash recovery.

Key Components

Participant

A node that holds a local key-value store and participates in distributed transactions.

Coordinator

Orchestrates the 2PC protocol across participants.

TwoPhaseCommitSystem

Convenience facade that bundles a Coordinator and its Participant instances. Exposes execute(operations) which auto-generates a transaction ID and runs the full protocol. Also provides getallstates() for inspecting all participants' stores.

Patterns

In-process simulation — The coordinator directly holds references to participant objects rather than using network calls. Availability is simulated via _available flags, and "timeouts" are modeled as availability checks rather than actual time-based logic.

Write-ahead logging — Both coordinator and participants append state transitions to self.log before and after performing actions. This enables the recover() methods to replay incomplete transactions. The log is append-only; entries are never modified.

Pessimistic locking — Participants use exclusive key-level locks (self.locks maps key → tx_id). Locks are acquired during prepare and released during commit or abort. There is no deadlock detection — conflicts result in an immediate "no" vote.

All-or-nothing voting — The coordinator requires unanimous "yes" votes. A single "no" from any participant causes the entire transaction to abort.

Dependencies

Imports: Only itertools (for itertools.count — monotonic transaction ID generation). No external dependencies.

Imported by: test2pc.py and testertest_2pc.py — the test suite and its meta-test validator.

Flow

A typical transaction proceeds as:

1. TwoPhaseCommitSystem.execute(ops) — entry point. Calls begintransaction(), then executetransaction().

2. Phase 1 (Prepare): For each participant in participantoperations, the coordinator calls participant.prepare(txid, ops). Each participant checks availability, checks for lock conflicts, acquires locks, stashes the operations in _pending, and votes "yes" or "no".

3. Decision: The coordinator checks if all(v == "yes" for v in votes.values()).

4. Phase 2 (Commit or Abort): The coordinator broadcasts commit() or abort() to each available participant. Committed participants apply mutations to their store and release locks. Aborted participants discard pending ops and release locks.

5. Return: The outcome dict contains tx_id, outcome ("committed" or "aborted"), the vote map, and an abort reason if applicable.

Invariants

Error Handling

Errors are returned as data, never raised as exceptions. Every method returns a dict with status/reason fields:

Unavailable participants during Phase 2 are silently skipped — the coordinator does not retry or block. This is where recover() becomes important: it re-sends decisions to participants that missed them.

There is no timeout implementation despite the timeout parameter on Coordinator._init_ — it's stored but never used, since availability is modeled as a boolean flag rather than actual network timeouts.

Topics to Explore

Beliefs