mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 15:10:34 +00:00
feat(telemetry): coroutine-aware OTel context storage
Backs the OTel active-context stack with xrpl::LocalValue so the ambient context follows a JobQueue::Coro across yield/resume. Not yet installed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -243,6 +243,7 @@ xrpl.server > xrpl.resource
|
||||
xrpl.shamap > xrpl.basics
|
||||
xrpl.shamap > xrpl.nodestore
|
||||
xrpl.shamap > xrpl.protocol
|
||||
xrpl.telemetry > xrpl.basics
|
||||
xrpl.telemetry > xrpl.config
|
||||
xrpl.tx > xrpl.basics
|
||||
xrpl.tx > xrpl.core
|
||||
|
||||
121
include/xrpl/telemetry/CoroAwareContextStorage.h
Normal file
121
include/xrpl/telemetry/CoroAwareContextStorage.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* A coroutine-aware OpenTelemetry runtime-context storage.
|
||||
*
|
||||
* OpenTelemetry's default ThreadLocalContextStorage keeps the active-context
|
||||
* stack in a plain static thread_local, so the ambient span does NOT follow a
|
||||
* JobQueue::Coro across yield/resume — a scope pushed on one worker is stranded
|
||||
* when the coroutine resumes on another. This storage instead keeps the stack
|
||||
* in an xrpl::LocalValue, which JobQueue::Coro::resume() swaps in and out with
|
||||
* the coroutine (see Coro.ipp). The active context therefore rides the
|
||||
* coroutine: scopes held across yield are safe, and per-line log-trace
|
||||
* correlation (Log.cpp reads RuntimeContext::GetCurrent()) is retained.
|
||||
*
|
||||
* Off a coroutine, LocalValue transparently provides a per-thread store, so
|
||||
* behaviour is identical to the default thread-local storage.
|
||||
*
|
||||
* +-------------------------------------------------+
|
||||
* | CoroAwareContextStorage |
|
||||
* | (opentelemetry RuntimeContextStorage) |
|
||||
* +-------------------------------------------------+
|
||||
* | - stack_ : LocalValue<vector<Context>> |
|
||||
* +-------------------------------------------------+
|
||||
* | + GetCurrent() : Context |
|
||||
* | + Attach(ctx) : unique_ptr<Token> |
|
||||
* | + Detach(token): bool |
|
||||
* +-------------------------------------------------+
|
||||
* | backed by (coro-aware)
|
||||
* +------------------------+
|
||||
* | xrpl::LocalValue store |
|
||||
* | (swapped by Coro) |
|
||||
* +------------------------+
|
||||
*
|
||||
* Install once at telemetry start via
|
||||
* opentelemetry::context::RuntimeContext::SetRuntimeContextStorage(), BEFORE
|
||||
* any span is created (SDK requirement).
|
||||
*
|
||||
* @note Thread-safety: each thread/coroutine sees its own LocalValue store, so
|
||||
* the stack is never shared across threads — no locking needed. The storage
|
||||
* object itself is stateless apart from the LocalValue handle.
|
||||
* @note Known limitation: install once, before the first span; resetting the
|
||||
* storage while spans exist is undefined behaviour (SDK). A span created on a
|
||||
* raw worker thread and later moved onto a coroutine does not retro-attach to
|
||||
* the coroutine's store — not a pattern here (spans are created inside their
|
||||
* own coro/job body).
|
||||
*
|
||||
* Example 1 — install at telemetry start (primary use):
|
||||
* @code
|
||||
* using opentelemetry::context::RuntimeContext;
|
||||
* RuntimeContext::SetRuntimeContextStorage(
|
||||
* opentelemetry::nostd::shared_ptr<
|
||||
* opentelemetry::context::RuntimeContextStorage>(
|
||||
* new xrpl::telemetry::CoroAwareContextStorage()));
|
||||
* @endcode
|
||||
*
|
||||
* Example 2 — a scope held across a coroutine yield stays correct:
|
||||
* @code
|
||||
* // Inside a JobQueue::Coro body:
|
||||
* auto span = ScopedSpanGuard(TraceCategory::Rpc, "rpc", "process");
|
||||
* context.coro->yield(); // may resume on another worker
|
||||
* // span is still the ambient context here — the storage rode the coro.
|
||||
* @endcode
|
||||
*
|
||||
* Example 3 — edge case: off a coroutine, behaves like thread-local storage:
|
||||
* @code
|
||||
* // On a plain worker thread (no coro): each thread has its own stack,
|
||||
* // identical to opentelemetry's default ThreadLocalContextStorage.
|
||||
* auto span = ScopedSpanGuard(TraceCategory::Ledger, "ledger", "build");
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
|
||||
#include <xrpl/basics/LocalValue.h>
|
||||
|
||||
#include <opentelemetry/context/context.h>
|
||||
#include <opentelemetry/context/runtime_context.h>
|
||||
#include <opentelemetry/nostd/unique_ptr.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::telemetry {
|
||||
|
||||
class CoroAwareContextStorage : public opentelemetry::context::RuntimeContextStorage
|
||||
{
|
||||
public:
|
||||
CoroAwareContextStorage() = default;
|
||||
|
||||
/**
|
||||
* @return the current (top-of-stack) context for this coro/thread.
|
||||
*/
|
||||
opentelemetry::context::Context
|
||||
GetCurrent() noexcept override;
|
||||
|
||||
/**
|
||||
* Push a context frame onto this coro/thread's stack.
|
||||
* @param context the context to make current.
|
||||
* @return a token that Detach() uses to pop back to the prior frame.
|
||||
*/
|
||||
opentelemetry::nostd::unique_ptr<opentelemetry::context::Token>
|
||||
Attach(opentelemetry::context::Context const& context) noexcept override;
|
||||
|
||||
/**
|
||||
* Pop the stack back through the frame the token refers to.
|
||||
* @param token a token returned by Attach().
|
||||
* @return true if the frame was found and detached.
|
||||
*/
|
||||
bool
|
||||
Detach(opentelemetry::context::Token& token) noexcept override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The active-context stack, stored coro-locally. LocalValue hands back the
|
||||
* stack for whichever store (coro or thread) is currently installed.
|
||||
*/
|
||||
LocalValue<std::vector<opentelemetry::context::Context>> stack_;
|
||||
};
|
||||
|
||||
} // namespace xrpl::telemetry
|
||||
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
68
src/libxrpl/telemetry/CoroAwareContextStorage.cpp
Normal file
68
src/libxrpl/telemetry/CoroAwareContextStorage.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Implementation of CoroAwareContextStorage.
|
||||
*
|
||||
* Mirrors opentelemetry's ThreadLocalContextStorage stack semantics, but the
|
||||
* stack lives in an xrpl::LocalValue so it follows a JobQueue::Coro across
|
||||
* yield/resume. All OpenTelemetry types stay confined to this translation unit.
|
||||
*
|
||||
* @see CoroAwareContextStorage (CoroAwareContextStorage.h)
|
||||
*/
|
||||
|
||||
#ifdef XRPL_ENABLE_TELEMETRY
|
||||
|
||||
#include <xrpl/telemetry/CoroAwareContextStorage.h>
|
||||
|
||||
#include <opentelemetry/context/context.h>
|
||||
#include <opentelemetry/context/runtime_context.h>
|
||||
#include <opentelemetry/nostd/unique_ptr.h>
|
||||
|
||||
namespace xrpl::telemetry {
|
||||
|
||||
opentelemetry::context::Context
|
||||
CoroAwareContextStorage::GetCurrent() noexcept
|
||||
{
|
||||
auto& stack = *stack_;
|
||||
return stack.empty() ? opentelemetry::context::Context{} : stack.back();
|
||||
}
|
||||
|
||||
opentelemetry::nostd::unique_ptr<opentelemetry::context::Token>
|
||||
CoroAwareContextStorage::Attach(opentelemetry::context::Context const& context) noexcept
|
||||
{
|
||||
stack_->push_back(context);
|
||||
return CreateToken(context);
|
||||
}
|
||||
|
||||
bool
|
||||
CoroAwareContextStorage::Detach(opentelemetry::context::Token& token) noexcept
|
||||
{
|
||||
auto& stack = *stack_;
|
||||
// Fast path: the token's frame is on top (the common LIFO case).
|
||||
if (!stack.empty() && token == stack.back())
|
||||
{
|
||||
stack.pop_back();
|
||||
return true;
|
||||
}
|
||||
// Fallback: token not on top — verify it is present, then pop down to and
|
||||
// including it (mirrors ThreadLocalContextStorage::Detach; also detaches
|
||||
// any child frames left above it).
|
||||
bool found = false;
|
||||
for (auto const& frame : stack)
|
||||
{
|
||||
if (token == frame)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return false;
|
||||
while (!stack.empty() && !(token == stack.back()))
|
||||
stack.pop_back();
|
||||
if (!stack.empty())
|
||||
stack.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl::telemetry
|
||||
|
||||
#endif // XRPL_ENABLE_TELEMETRY
|
||||
Reference in New Issue
Block a user