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_tand bound inlibtracer/config_override.hpp, on the same seam pattern as the ACL policy and the LKV slot. The example printsreclaim_policy_t::kNameso a run says which one it exercised.reclaim_strict_tforbids 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, anNDEBUGbuild cannot see it. The example branches onkReentrantUnsubscribewithif constexprand 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 astatic_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 — andgraph_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.