Function: _reaches in lamport-clocks/lamport.py

Date: 2026-05-29

Time: 13:15

_reaches(source, target) — Causal Reachability via BFS

Purpose

reaches determines whether one event causally precedes another in a distributed system's event graph. It implements the core reachability check that backs the happensbefore relation — Lamport's "→" operator. Without this function, you can compare timestamps, but timestamps alone can't distinguish causally related events from concurrent ones (that's the whole point of Lamport's formalism).

Contract

Preconditions:

Postconditions:

Invariant: The function never modifies the event graph.

Parameters

| Parameter | Type | Meaning |

|-----------|------|---------|

| source | Event | The candidate predecessor — "did this happen first?" |

| target | Event | The candidate successor — we walk backward from here |

Edge cases:

Return Value

boolTrue means source → target (source causally precedes target). False means either target precedes source, or the events are concurrent. The caller must invoke _reaches in both directions to disambiguate.

Algorithm

The function performs a backward BFS from target toward the roots of the event graph:

1. Initialize a visited set (using id() of event objects) and a queue seeded with target.

2. Dequeue the next event. If it is source (identity check, not equality), we've found a causal path — return True.

3. Mark visited using the object's id() to avoid revisiting the same event through different paths (a receive event has both a parent and a cause, so two paths can converge on the same ancestor).

4. Enqueue predecessors: the event's parent (previous event on the same node) and cause (the send event that triggered this receive, crossing a node boundary).

5. Repeat until the queue is empty. If exhausted without finding source, return False.

The key insight: the event graph has two kinds of edges — process-local edges (parent: sequential events on one node) and cross-node edges (cause: a send→receive link). Walking both edge types backward traces the full causal history, which is exactly Lamport's happens-before relation.

Side Effects

None. Pure function — reads the event graph but never mutates it. The visited set is local.

Error Handling

No explicit error handling. The function assumes:

Usage Patterns

Called exclusively by happens_before, which wraps two calls:


if _reaches(event_a, event_b):   # a → b?
    return True
if _reaches(event_b, event_a):   # b → a?
    return False
return None                       # concurrent

The caller is responsible for interpreting the three-valued result. _reaches itself is a private helper (underscore prefix) — not part of the public API.

Dependencies

Design Note

Using id() for the visited set rather than the events themselves is deliberate. Event is a @dataclass, which auto-generates _eq based on field values. Two different event objects with the same nodeid, eventtype, timestamp, and description would compare equal under eq_, but they're distinct nodes in the causal graph. The id() check ensures we track actual object identity, matching the is check used for the source comparison.

Topics to Explore

Beliefs