fix(telemetry): detach consensus receive spans and root standalone fallbacks

The consensus.proposal.receive / consensus.validation.receive guards are
moved into checkPropose/checkValidation jobs, so detach their Scope on the
peer thread before the job hand-off. The standalone (no-propagated-context)
fallbacks in ConsensusReceiveTracing now use rootSpan() so they start a
fresh trace root instead of inheriting an ambient span; the hashSpan
propagated-context branches are unchanged (cross-node linking preserved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pratik Mankawde
2026-07-17 23:10:17 +01:00
parent 17c17901dc
commit 97da89c3e9
2 changed files with 25 additions and 16 deletions

View File

@@ -1843,7 +1843,10 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMProposeSet> const& m)
// Create a receive span that links to the sender's trace context
// (if propagated). shared_ptr keeps it alive across the job boundary.
auto span = std::make_shared<telemetry::SpanGuard>(telemetry::proposalReceiveSpan(set));
// Detach the guard's Scope on this peer thread so it is not popped on the
// job worker thread (which would leak this thread's context stack).
auto span =
std::make_shared<telemetry::SpanGuard>(telemetry::proposalReceiveSpan(set).detached());
span->setAttribute(telemetry::consensus::span::attr::proposalTrusted, isTrusted);
span->setAttribute(
telemetry::consensus::span::attr::round, static_cast<int64_t>(set.proposeseq()));
@@ -2438,7 +2441,10 @@ PeerImp::onMessage(std::shared_ptr<protocol::TMValidation> const& m)
// Create a receive span that links to the sender's trace context
// (if propagated). shared_ptr keeps it alive across the job boundary.
auto span = std::make_shared<telemetry::SpanGuard>(telemetry::validationReceiveSpan(*m));
// Detach the guard's Scope on this peer thread so it is not popped on
// the job worker thread (which would leak this thread's context stack).
auto span =
std::make_shared<telemetry::SpanGuard>(telemetry::validationReceiveSpan(*m).detached());
span->setAttribute(telemetry::consensus::span::attr::validationTrusted, isTrusted);
if (val->isFieldPresent(sfLedgerSequence))
{

View File

@@ -1,6 +1,7 @@
#pragma once
/** Helper functions for creating consensus receive trace spans.
/**
* Helper functions for creating consensus receive trace spans.
*
* Encapsulates the logic for creating SpanGuard instances for incoming
* proposal and validation messages with optional protobuf parent
@@ -18,8 +19,8 @@
* +--- has trace_context? ----+
* | yes | no
* v v
* SpanGuard::span() with SpanGuard::span()
* extracted parent context (standalone span)
* SpanGuard::hashSpan() with SpanGuard::rootSpan()
* extracted parent context (fresh trace root)
*
* When XRPL_ENABLE_TELEMETRY is not defined, the functions return
* no-op SpanGuard instances (zero overhead, zero dependencies).
@@ -31,7 +32,7 @@
* span.setAttribute(...);
* @endcode
*
* @note Span names come from the canonical constants in
* @note Span names come from the canonical constants in
* ConsensusSpanNames.h (consensus::span::proposalReceive /
* validationReceive) so they stay in sync with the rest of Phase 4.
*/
@@ -47,14 +48,15 @@
namespace xrpl::telemetry {
/** Create a "consensus.proposal.receive" span for an incoming proposal.
/**
* Create a "consensus.proposal.receive" span for an incoming proposal.
*
* If the message carries a TraceContext with a valid span_id, the
* receive span is created with the sender's context as parent.
* Otherwise a standalone span is created.
*
* @param msg The incoming TMProposeSet protobuf message.
* @return An active SpanGuard, or a null guard if tracing is disabled.
* @param msg The incoming TMProposeSet protobuf message.
* @return An active SpanGuard, or a null guard if tracing is disabled.
*/
inline SpanGuard
proposalReceiveSpan([[maybe_unused]] protocol::TMProposeSet const& msg)
@@ -82,19 +84,20 @@ proposalReceiveSpan([[maybe_unused]] protocol::TMProposeSet const& msg)
}
}
#endif
// No propagated context — create a standalone span.
return SpanGuard::span(
// No propagated context — start a fresh trace root (not an ambient child).
return SpanGuard::rootSpan(
TraceCategory::Consensus, seg::consensus, consensus::span::op::proposalReceive);
}
/** Create a "consensus.validation.receive" span for an incoming validation.
/**
* Create a "consensus.validation.receive" span for an incoming validation.
*
* If the message carries a TraceContext with a valid span_id, the
* receive span is created with the sender's context as parent.
* Otherwise a standalone span is created.
*
* @param msg The incoming TMValidation protobuf message.
* @return An active SpanGuard, or a null guard if tracing is disabled.
* @param msg The incoming TMValidation protobuf message.
* @return An active SpanGuard, or a null guard if tracing is disabled.
*/
inline SpanGuard
validationReceiveSpan([[maybe_unused]] protocol::TMValidation const& msg)
@@ -119,8 +122,8 @@ validationReceiveSpan([[maybe_unused]] protocol::TMValidation const& msg)
}
}
#endif
// No propagated context — create a standalone span.
return SpanGuard::span(
// No propagated context — start a fresh trace root (not an ambient child).
return SpanGuard::rootSpan(
TraceCategory::Consensus, seg::consensus, consensus::span::op::validationReceive);
}