await — the readiness plane (L4 graph)

The third data call. await blocks until the vertex’s value is assigned again, or until the deadline expires. It is single-shot and lives wholly in the state plane: it observes assigns at its own vertex and takes no part in propagation (reference 02 §Assign, propagate, and the coalescing sweep). The mental model is epoll on one identity — read/write are the data plane, :field writes the control plane, await the readiness plane, all on one vertex (CONTEXT.md §Field-write).

What to notice

  • await is not subtree-scoped. A subscription observes its vertex and every descendant (vertical bubbling, RFC-0005); await does not. The example writes a descendant first and the waiter stays parked — the write to the vertex itself is what wakes it.

  • A timeout is a distinct answer. The second await expires and returns TIMEOUT, which is what lets a consumer distinguish a quiet vertex from a delivered value. An await carrying a :field selector is refused outright (SCHEMA_NOT_FOUND) rather than silently giving a whole-vertex wakeup — not exercised here, see reference 02 §Owner-declared application fields.

  • Ordering is the caller’s. The example sleeps 50 ms to let the waiter park, then join()s after the write, so every wake is complete and visible when the checks run. There is no stated ordering between a registration and a concurrent operation on the same address (reference 02 §Registration racing a concurrent operation) — a caller that needs one orders it itself.

Source

 1/*
 2 * SPDX-License-Identifier: Apache-2.0
 3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
 4 */
 5
 6/**
 7 * @file
 8 * @brief ONE CONCEPT — the readiness plane: `await` blocks until the next store here.
 9 *
10 * `await` is the third of the three data calls (`CONTEXT.md` §read / write / await). It is
11 * single-shot and lives wholly in the state plane: it observes assigns AT ITS OWN VERTEX and
12 * is not subtree-scoped, so a write to a descendant does not wake it
13 * (`docs/reference/02-graph-model.md` §Assign, propagate). A deadline that expires answers
14 * `TIMEOUT`, which is why a consumer can tell a quiet vertex from a delivered value.
15 *
16 * Runs under ctest as `example_graph_await`; returns non-zero on any failed check.
17 */
18
19#include <chrono>
20#include <cstdio>
21#include <cstring>
22#include <span>
23#include <string_view>
24#include <thread>
25
26#include "libtracer/tracer.hpp"
27
28namespace {
29
30using namespace std::chrono_literals;
31using tr::graph::path_t;
32using tr::graph::role_t;
33using tr::graph::status_t;
34
35/** @brief An owned one-segment view over @p text. */
36tr::view::view_t value_of(std::string_view text) {
37    return *tr::view::over_bytes(std::as_bytes(std::span<const char>(text.data(), text.size())));
38}
39
40/** @brief Report expectation @p what and record a failure on @p ok. */
41void check(bool& ok, bool cond, const char* what) {
42    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
43    ok = ok && cond;
44}
45
46}  // namespace
47
48int main() {
49    tr::graph::graph_t g;
50    bool ok = true;
51
52    const auto temp = g.register_vertex(path_t("/sensor/temp"), role_t::STORED_VALUE);
53    const auto child = g.register_vertex(path_t("/sensor/temp/raw"), role_t::STORED_VALUE);
54
55    // A waiter parks in await(); the writer wakes it with the value it stored.
56    bool woke = false;
57    std::thread waiter([&] {
58        const auto r = g.await(temp, 2s);
59        woke = r && (*r)->only().bytes().size() == 5;
60    });
61    std::this_thread::sleep_for(50ms);  // let the waiter park before the write lands
62
63    (void)g.write(child, value_of("noise"));  // a DESCENDANT write — not this vertex
64    (void)g.write(temp, value_of("21.5C"));
65    waiter.join();
66    check(ok, woke, "await returns the value assigned at its own vertex");
67
68    // await is not subtree-scoped, and the deadline is honoured: nothing writes /sensor/temp
69    // again, so this one expires rather than picking up the descendant write above.
70    const auto quiet = g.await(temp, 20ms);
71    check(ok, !quiet && quiet.error() == status_t::TIMEOUT, "an expired deadline answers TIMEOUT");
72    std::printf("await woke once and timed out once, as expected\n");
73    return ok ? 0 : 1;
74}

See also: graph module · in-process pub/sub (the same primitive alongside subscriptions) · concurrency and scaling.