Retire drops a producer’s subscriptions (L4 graph)

retire(vh) marks a vertex and its whole subtree logically absent and re-virginizes each one: the previous owner’s :acl, value seam, stored value, history, app-field table, :subscribers[], owner-side storage declarations and delivery mode are all cleared, so a later revive of the same address inherits nothing of the retired owner (RFC-0009 §B). This example subscribes both on a leaf and on its parent, retires the leaf, revives it, and shows which edge survived.

What to notice

  • Retirement delivers nothing and wakes no await (§B.5). A subscriber learns a producer went away from the absence of writes, not from a retirement event — the example keeps an ancestor subscriber in place specifically to catch a spurious delivery.

  • Edges on an ancestor are untouched. They belong to a vertex that was not retired, so the parent’s subscription still observes the revived leaf’s writes. The leaf’s own edge does not come back.

  • The allocation is not freed. A vertex_handle_t never dangles — the vertex map is pinned and insert-only (ADR-0057) — so the vertex is emptied, not erased. retire_generation is the stamp a cached resolution carries and re-reads: a mismatch means the path was retired, and possibly re-created for a different owner, since the resolution.

  • There is no wire operation that reaches here. Retirement is owner-side; a peer goes through the device’s own logic, which is what calls it. It is idempotent, and the root cannot be retired.

  • This is the only owner-side eviction that exists today. ⚠️ The per-subscriber heartbeat described in 04 §Liveness loss is a statement of intent: no :liveness.* field is implemented in either direction and no wire spelling for the refresh is ratified (#586). The transport-level counterpart — evicting every edge that fanned out over a departed link, in one sweep, without retiring the target vertices (RFC-0009 §D) — is graph_t::evict_link_edges, which needs a live link and so is not shown here.

Source

 1/*
 2 * SPDX-License-Identifier: Apache-2.0
 3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
 4 */
 5
 6/**
 7 * @file
 8 * @brief Retiring a producer drops its subscriptions — and delivers nothing (RFC-0009 §B).
 9 *
10 * `retire(vh)` makes a vertex logically absent and **re-virginizes** it (§B.6): its `:acl`,
11 * stored value, history and `:subscribers[]` are cleared, so a later revive of the same
12 * address inherits nothing of the retired owner. It wakes no `await` and fans out nothing
13 * (§B.5) — a subscriber learns a producer went away from the absence of writes, not from a
14 * retirement event. Edges that live on an ANCESTOR are untouched: they belong to a vertex
15 * that was not retired.
16 *
17 * This is the only owner-side eviction the reference implementation has. The per-subscriber
18 * heartbeat that reference 04 §Liveness loss describes is NOT implemented — no `:liveness.*`
19 * field exists in either direction (#586) — and the transport-level counterpart is
20 * `graph_t::evict_link_edges` (RFC-0009 §D), which needs a link and so is not shown here.
21 *
22 * Runs under ctest as `example_sub_retire`; it self-checks and returns non-zero on any
23 * mismatch.
24 */
25
26#include <cstddef>
27#include <cstdint>
28#include <cstdio>
29
30#include "libtracer/tracer.hpp"
31
32namespace {
33
34using tr::graph::path_t;
35using tr::graph::role_t;
36
37/** @brief A one-byte VALUE view over @p b (one heap segment). */
38tr::view::view_t value_byte(std::uint8_t b) {
39    tr::view::segment_ptr_t seg = tr::view::heap_alloc(1);
40    seg->bytes[0] = std::byte{b};
41    return tr::view::view_t::over(std::move(seg));
42}
43
44/** @brief Record a failed expectation on @p ok and report it. */
45void check(bool& ok, bool cond, const char* what) {
46    if (!cond) {
47        std::printf("  [FAIL] %s\n", what);
48        ok = false;
49    }
50}
51
52}  // namespace
53
54int main() {
55    tr::graph::graph_t g;
56    (void)g.register_vertex(path_t("/p"), role_t::STORED_VALUE);
57    tr::graph::vertex_handle_t leaf = g.register_vertex(path_t("/p/leaf"), role_t::STORED_VALUE);
58
59    int on_leaf_seen = 0, on_parent_seen = 0;
60    auto on_leaf = [&](const tr::view::rope_t&) { ++on_leaf_seen; };
61    auto on_parent = [&](const tr::view::rope_t&) { ++on_parent_seen; };
62    (void)g.subscribe(path_t("/p/leaf"), on_leaf);  // an edge ON the doomed vertex
63    (void)g.subscribe(path_t("/p"), on_parent);     // an edge on its surviving ancestor
64    (void)g.write(leaf, value_byte(0x01));
65
66    const std::uint32_t gen_before = g.retire_generation(leaf);
67    (void)g.retire(leaf);
68    const int leaf_after_retire = on_leaf_seen, parent_after_retire = on_parent_seen;
69
70    // Revive the same address and write again: the retired owner's edge is gone, the
71    // ancestor's is not.
72    (void)g.try_register_vertex(path_t("/p/leaf"), role_t::STORED_VALUE);
73    (void)g.write(path_t("/p/leaf"), value_byte(0x02));
74    std::printf("leaf edge: %d -> %d deliveries; ancestor edge: %d -> %d; generation %u -> %u\n",
75                leaf_after_retire, on_leaf_seen, parent_after_retire, on_parent_seen, gen_before,
76                g.retire_generation(leaf));
77
78    bool ok = true;
79    check(ok, leaf_after_retire == 1 && parent_after_retire == 1, "retirement delivered nothing");
80    check(ok, g.own_subs(leaf) == 0, "the revived vertex carries none of the retired subscribers");
81    check(ok, on_leaf_seen == 1, "the retired vertex's own edge did not survive the revive");
82    check(ok, on_parent_seen == 2, "the ancestor's edge did — it was never retired");
83    check(ok, g.retire_generation(leaf) != gen_before, "the retirement generation moved");
84    std::printf("RESULT %s\n", ok ? "ok" : "FAILED");
85    return ok ? 0 : 1;
86}

See also: graph model · communication flows · graph module.