acquireSpan_ is emplaced on the acquiring thread and reset() on a JtLedgerData
worker. SpanGuard is now thread-free (owns no thread-local Scope), so it can be
created here and destroyed on the worker with no scope to strip — dropping the
.detached() call the old scoped SpanGuard required.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reflects the C1 fix: rpc.command.* stays unscoped (its dispatch wraps
doRipplePathFind which yields), so pathfind.request nests under rpc.process.
The request -> compute -> discover sub-tree nests correctly via ScopedSpanGuard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ledger.build was left as unscoped SpanGuard after the type split, so tx.apply
(created synchronously during applyTxs on the same JtAccept worker) no longer
nested under it. buildLedgerImpl runs synchronously with no yield, so
ScopedSpanGuard is safe and restores the ledger.build -> tx.apply edge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pathfind.request (PathFind.cpp) and pathfind.compute (PathRequest.cpp doUpdate)
were left as unscoped SpanGuard after the type split, so pathfind.compute and
pathfind.discover no longer nested under them. Both handlers run synchronously
with no coroutine yield, so ScopedSpanGuard is safe and restores the
request -> compute -> discover sub-tree. rpc.command.* stays unscoped (callMethod
wraps doRipplePathFind which holds across a yield), so pathfind.request parents
to rpc.process.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document that deterministic-trace_id spans (tx.* apply pipeline, tx.process,
tx.receive, consensus.round) are now genuine trace roots with empty
parent_span_id via the custom DeterministicIdGenerator, superseding the old
synthetic-parent behavior that showed 'root span not yet received' in Tempo.
Also update the fresh-root note: peer entry spans use ScopedSpanGuard::freshRoot().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These peer-level entry spans (introduced on phase-6) used the removed
SpanGuard::rootSpan(). They are scoped on the peer thread and end there, so
use ScopedSpanGuard::freshRoot() — a scoped fresh trace root that does not
inherit any span left active on the peer thread.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With unscoped SpanGuard + spanContext() (own-span capture), the consensus
round/establish/accept sites just capture and drop the detach dance;
openSpan + peer proposal/validation receive are thread-free handoffs (no
detach); rootSpan->freshRoot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OTel distinguishes a span's own context from the thread's
current/ambient context; SpanGuard is unscoped so spanContext()
(own span, default) is what cross-thread childSpan parenting needs,
and threadLocalContext() (static) snapshots RuntimeContext::GetCurrent()
for propagation. Renames captureContext.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SpanGuard is unscoped (never pushed onto the thread-local context stack), so
RuntimeContext::GetCurrent() returned an unrelated ambient span. Build the
captured context from impl_->span so captureContext() is correct for every
caller regardless of scoping — childSpan(name, ctx) then parents explicitly to
this span across threads. Matches getTraceBytes()'s own-context behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The generator's doc said it was 'dormant, caller lands on a later branch'.
Reworded to branch-agnostic language: hashSpan() is the primary caller that
forces the root branch to mint deterministic trace roots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SpanGuard is now thread-free (holds no Scope), so the tx.receive (PeerImp)
and tx.process (NetworkOPs) handoff sites no longer need .detached() before
being stored and ended on a worker thread — just construct the guard. The
stale "Scope leak" comments are replaced accordingly.
Make the six txq.* spans ScopedSpanGuard so their sub-spans nest via the
ambient context: txq.apply_direct/batch_clear under txq.enqueue, and
txq.accept_tx under txq.accept. All six are verified synchronous, ended at
scope, and never moved/handed off, so scoping is safe. applyDirect's span
is pure RAII (no method calls), so it is declared const.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The single-arg hashSpan fabricated a synthetic DefaultSpan parent (random
span_id) to carry the deterministic trace_id. That left every standalone
hash span with a phantom parent_span_id pointing at a span that is never
exported, so Tempo reported "root span not yet received".
Inject the deterministic trace_id through the DeterministicIdGenerator
instead: start the span on the SDK root branch (Context{kIsRootSpanKey,true})
with a PendingTraceId pinning hashData[0:16]. The SDK's no-valid-parent
branch calls GenerateTraceId(), which returns the pinned id, yielding a
TRUE root (empty parent_span_id) whose trace_id == hashData[0:16].
The deterministic bytes are unchanged (still raw hashData[0:16]). The
cross-node overload (real remote parent, remote=true) is untouched and must
keep producing a child of the sender's span. Drop the now-unused
<xrpl/basics/random.h> include; add <array> and DeterministicIdGenerator.h.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite SpanGuardScope.cpp for the unscoped SpanGuard / scoped
ScopedSpanGuard split and the DeterministicIdGenerator: install the
generator in the TestTelemetry mock (4-arg TracerProviderFactory), drop the
obsolete detached()/detachInPlace() tests, and add freshRoot, scoped-ambient,
scope-handoff, ends-once, deterministic-root, and cross-thread death tests
with exact assertions.
Fix the last detached() caller: RipplePathFind now uses SpanGuard::freshRoot
(SpanGuard is thread-free, so it is held across the coroutine yield and ended
on resume with no scope to strip).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add MetricMacros.h exposing 13 call-site metric macros covering all 7 OTel
instrument kinds (Counter INC/ADD, UpDownCounter, Histogram, sync Gauge, and
Observable Counter/UpDownCounter/Gauge), each with a no-op branch when
XRPL_ENABLE_TELEMETRY is undefined. Sync macros lazily create-once via
std::call_once and cache the instrument at the call site; observable macros
register a callback eagerly. Sync Gauge is ABI-v2-gated with an actionable
static_assert under this repo's ABI v1 build.
Add GTest coverage (8 tests) driving the macros against a bare SDK
MeterProvider via a duck-typed FakeApp/FakeMetricsRegistry, asserting exact
create-once and disabled-gating behavior. No CMakeLists change needed: the
telemetry test-module glob already discovers the new source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>