Unsubscribing from inside a delivery (L4 graph)

Applies to reclaim_local_t only

This is the one example whose subject is policy-dependent. It demonstrates a shape that the default reclaim_local_t supports and that reclaim_strict_t forbids outright, so it runs its body under the default binding and skips under reclaim_strict_t, printing skipped: this build forbids unsubscribing from inside a delivery and passing. The policy is a build-time type rather than a CMake option (ADR-0068), so the target is always built and always linked — it follows the binding with if constexpr, exactly as core/tests/reclaim_test.cpp does for its own re-entrant cases. Every other example on this site applies to both policies.

A subscriber callback that retires its own subscription is the one case where “the edge is gone” and “the {fn, ctx} pair is dead” are not the same moment: the fan-out that invoked the callback is still walking a snapshot that names that pair. The default reclaim_local_t policy parks the retired pair and runs its release hook when this thread’s dispatch stack unwinds to depth 0 — before the write() that started the delivery hands control back (ADR-0080).

What to notice

  • Retirement is immediate; release is not. The example’s second write reaches nobody — the next snapshot already skips the slot — yet the hook has not run when the callback returns. Both halves are asserted, and the run prints the flag as observed inside the callback.

  • The library decides which grace point applies, and the caller never asks. Outside a delivery the same call releases inline (see unsubscribe); inside one it defers. The signal is the hook in both cases — there is no poll and no verb the embedder must remember.

  • The policy is a build-time type, not a runtime flag. It is named by default_config_t::reclaim_policy_t and bound in libtracer/config_override.hpp, on the same seam pattern as the ACL policy and the LKV slot. The example prints reclaim_policy_t::kName so a run says which one it exercised.

  • reclaim_strict_t forbids this shape outright. It is the opt-in zero-cost mode for a deployment that provably never unsubscribes from inside a dispatch; a debug build asserts, an NDEBUG build cannot see it. The example branches on kReentrantUnsubscribe with if constexpr and skips its body there — the constant is the portable way to ask “may I do this on this target?”, and asking it is the point. Note what it is not: an example that does not apply to a binding must skip, never fail to compile, so the branch is a runtime skip rather than a static_assert.

  • The guarantee is stated over one thread’s dispatch domain — the NARROW/MCU target. An embedder dispatching from several threads and unsubscribing from another needs a grace period spanning every thread. That is reclaim_qsbr, ADR-0080’s third policy, which is not implemented (#894) and is deliberately not even declared as an alias.

  • Parking is bounded. Retired pairs sit in a per-thread array sized by kDeferredReleaseSlots; if every slot is taken the pair is dropped and its hook never runs — a deliberate leak in preference to a use-after-free — and graph_t::deferred_release_drops() counts it.

Source

  1/*
  2 * SPDX-License-Identifier: Apache-2.0
  3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
  4 */
  5
  6/**
  7 * @file
  8 * @brief Unsubscribing from INSIDE a delivery — the deferred grace point (ADR-0080).
  9 *
 10 * A callback that retires its own subscription cannot be told "the pair is dead" on the
 11 * spot: the fan-out that called it is still walking a snapshot naming that `{fn, ctx}`
 12 * pair. Under the default `reclaim_local_t` the retired pair is PARKED and the release
 13 * hook runs when this thread's dispatch stack unwinds to depth 0 — i.e. before the
 14 * `write()` that started the delivery returns. The retirement itself is immediate: the
 15 * next fan-out skips the slot.
 16 *
 17 * `reclaim_strict_t` forbids this shape outright — it debug-asserts on a re-entrant
 18 * unsubscribe — so THIS EXAMPLE APPLIES TO `reclaim_local_t` ONLY. Both policies live in
 19 * `libtracer/reclaim.hpp`, and the binding is a build-time type, so the example follows it
 20 * with `if constexpr` and SKIPS its body where the policy forbids the shape (the same way
 21 * `core/tests/reclaim_test.cpp` drops its re-entrant cases). A policy the example does not
 22 * apply to must not break the build — every example compiles under every binding.
 23 *
 24 * Runs under ctest as `example_sub_unsubscribe_from_dispatch`; it self-checks and returns
 25 * non-zero on any mismatch. Under `reclaim_strict_t` it prints a skip line and passes.
 26 */
 27
 28#include <cstddef>
 29#include <cstdint>
 30#include <cstdio>
 31
 32#include "libtracer/tracer.hpp"
 33
 34namespace {
 35
 36using tr::graph::path_t;
 37using tr::graph::role_t;
 38
 39/** @brief The subscriber's `ctx`: its own state plus what it needs to retire itself. */
 40struct sink_t {
 41    tr::graph::graph_t* g = nullptr; /**< @brief The graph to unsubscribe from. */
 42    tr::graph::subscription_t sub{}; /**< @brief This subscription's handle. */
 43    int seen = 0;                    /**< @brief Deliveries observed. */
 44    bool released = false;           /**< @brief Set by the release hook, exactly once. */
 45    bool released_inside_cb = false; /**< @brief Was it already set when the callback left? */
 46};
 47
 48/** @brief The release hook — libtracer calls it once, at the policy's grace point. */
 49void on_release(void* ctx) { static_cast<sink_t*>(ctx)->released = true; }
 50
 51/** @brief A delivery that retires its own subscription (the re-entrant case). */
 52void on_delivery(void* ctx, const tr::view::rope_t&) {
 53    auto* s = static_cast<sink_t*>(ctx);
 54    ++s->seen;
 55    (void)s->g->unsubscribe(s->sub, &on_release);
 56    s->released_inside_cb = s->released;  // still parked here — dispatch has not unwound
 57}
 58
 59/** @brief A one-byte VALUE view over @p b (one heap segment). */
 60tr::view::view_t value_byte(std::uint8_t b) {
 61    tr::view::segment_ptr_t seg = tr::view::heap_alloc(1);
 62    seg->bytes[0] = std::byte{b};
 63    return tr::view::view_t::over(std::move(seg));
 64}
 65
 66/** @brief Record a failed expectation on @p ok and report it. */
 67void check(bool& ok, bool cond, const char* what) {
 68    if (!cond) {
 69        std::printf("  [FAIL] %s\n", what);
 70        ok = false;
 71    }
 72}
 73
 74/**
 75 * @brief The example proper — retire a subscription from inside its own delivery.
 76 *
 77 * Written unguarded, because under `reclaim_local_t` every line of it is valid; the ONE
 78 * policy branch lives in `main`, so what a reader studies here is the pattern itself.
 79 *
 80 * @return True when every expectation held.
 81 */
 82bool run_reentrant_unsubscribe() {
 83    tr::graph::graph_t g;
 84    const tr::graph::vertex_handle_t src =
 85        g.register_vertex(path_t("/sensor/temp"), role_t::STORED_VALUE);
 86
 87    sink_t sink;
 88    sink.g = &g;
 89    sink.sub = *g.subscribe(path_t("/sensor/temp"), &on_delivery, &sink);
 90
 91    (void)g.write(src, value_byte(0x01));  // delivers, and the callback retires itself
 92    std::printf("after write #1: seen=%d released=%d (inside the callback it was %d)\n", sink.seen,
 93                static_cast<int>(sink.released), static_cast<int>(sink.released_inside_cb));
 94    (void)g.write(src, value_byte(0x02));  // the slot is already retired
 95
 96    bool ok = true;
 97    check(ok, sink.seen == 1, "the retire took effect at once — the second write reached nobody");
 98    check(ok, sink.released, "the hook ran before write() returned");
 99    check(ok, !sink.released_inside_cb,
100          "and not one moment earlier — the fan-out was still walking the snapshot");
101    return ok;
102}
103
104}  // namespace
105
106int main() {
107    const auto policy = tr::graph::reclaim_policy_t::kName;
108    std::printf("reclamation policy bound by this build: %.*s\n", static_cast<int>(policy.size()),
109                policy.data());
110
111    bool ok = true;
112    if constexpr (tr::graph::reclaim_policy_t::kReentrantUnsubscribe) {
113        ok = run_reentrant_unsubscribe();
114    } else {
115        // `reclaim_strict_t` FORBIDS the shape this whole example is about, and debug-asserts
116        // on it — running the body would be asserting that an abort happens. The example is
117        // therefore skipped, not failed, and certainly not made to break the build: it simply
118        // does not apply to this binding. Read it against the default policy.
119        std::printf("skipped: this build forbids unsubscribing from inside a delivery\n");
120    }
121    std::printf("RESULT %s\n", ok ? "ok" : "FAILED");
122    return ok ? 0 : 1;
123}

See also: reclamation policy · configuration · graph module.