How does OpenTelemetry eBPF instrumentation decide which language runtimes it can stamp a trace context on, and how do you verify that a FUSE daemon thread actually receives the inherited context?
A FUSE userspace daemon can inherit OpenTelemetry trace context only when the thread that issued the FUSE operation is a thread OpenTelemetry's eBPF instrumenter (OBI) actually stamped with a trace context, and OBI stamps context for a fixed, version-gated list of runtimes rather than for any language. Go, Node.js, Java, Python, and Ruby each have a named association mechanism; C++ has none. So a FUSE program driven by a C++ client is the one case in the common FUSE language set that gets no language-specific context handoff, and the daemon's lookup for that thread misses unless the C++ code issues the FUSE I/O on the very thread OBI saw serving the request. You verify inheritance by turning on OBI's trace-context map, reading the entry keyed by the caller thread id taken from the FUSE request header, and confirming the trace id and span id match the incoming context; a miss with population on points at the OBI side, not the daemon.
The mechanism
The boundary is between the runtimes OBI can associate a trace context with and the thread that actually issues the FUSE request:
- Runtime support (OBI compatibility matrix). OBI's context propagation is a fixed list. Network-level HTTP/1 and gRPC propagation works across languages, but the per-thread context association is language-specific: Go library-level context propagation on Go 1.18+ (up to six nested goroutine levels); Node.js async hooks on Node.js 8.0+ (custom SIGUSR1 handling can interfere); Java thread-pool handoff on JDK 8+ through the common task APIs; Python asyncio on Python 3.9+ with the uvloop event loop; and Ruby served by Puma 5.0+. C++ does not appear in the matrix, so a C++-only client has no language-specific association mechanism.
- Thread association. Even for a supported runtime, OBI ties the outgoing work to the incoming request using that runtime's hand-off mechanism, and only when that specific match is unavailable does it fall back to language-agnostic signals: the same OS thread handling both the incoming request and the outgoing work, a child thread OBI can relate back to the request thread, or the request thread doing socket setup or live checks before the work completes on another worker thread. OBI does not reconstruct arbitrary application queues or framework context objects.
- The FUSE boundary. The FUSE daemon reads
fuse_in_header.pid, which the kernel fills with the caller thread's thread id in the mount's pid namespace. For the daemon to inherit the context, the thread that issued the FUSE operation must be the thread OBI associated with an active trace context, or one reachable through one of the hand-offs above. If a C++ client dispatches its FUSE I/O to a worker thread different from the thread that received the request, OBI has no language-specific mechanism to follow that hand-off, so the per-thread context entry OBI keys by the process-and-thread pair is empty for the FUSE-issuing thread and the daemon's lookup misses.
Verification and debugging path
- Confirm the client runtime is in the matrix. Check the client's language and version against the list: Go 1.18+, Node.js 8.0+, JDK 8+, Python 3.9+ on uvloop, Ruby on Puma 5.0+. If the client is C++-only, stop here: there is no language-specific association, and you are left with the language-agnostic thread signals only.
- Confirm the FUSE-issuing thread is the request thread, or a reachable hand-off. For a supported runtime, verify the thread that calls the FUSE operation is the same thread serving the incoming request, or a goroutine, async task, or JDK task within the supported hand-off depth. A detached worker pool that OBI cannot relate back to the request thread will miss.
- Turn on the trace-context map and read the entry. Enable OBI's
populate_trace_contextoption, then for the thread id taken fromfuse_in_header.pid, look up the OBI trace-context map entry. A hit whose trace id and span id match the incoming context confirms inheritance; a miss with population on points at the OBI side. - Isolate the two failure modes. A miss with population on and a supported runtime on the right thread points at the association step (a hand-off OBI cannot observe); a miss with population off, or with a C++-only client, points at the support boundary (no language-specific context was ever stamped for that thread).
The limitation
- C++-only clients are not in OBI's context-propagation compatibility matrix, so a FUSE program driven by a C++ application gets no language-specific trace-context hand-off; only the language-agnostic thread signals apply, and those miss whenever the FUSE I/O runs on a thread OBI cannot relate back to the request thread.
- The support list is version-gated (Go 1.18+, Node.js 8.0+, JDK 8+, Python 3.9+ on uvloop, Ruby on Puma 5.0+); other versions may work but are outside the documented scope.
- Even for a supported runtime the hand-off depth is bounded (up to six nested goroutine levels, and a bounded number of JDK task hand-offs); a reactive or event-loop structure outside the supported mechanisms can still miss.
- The language-agnostic fallback is intentionally bounded: OBI does not reconstruct arbitrary application queues, user-space schedulers, or framework context objects, so a thread pool that moves the FUSE work off the request thread without an observed hand-off starts a new trace instead of inheriting.
References
- OBI distributed-traces compatibility matrix - the supported runtimes and versions for OBI context propagation (Go 1.18+, Node.js 8.0+, JDK 8+, Python 3.9+ on uvloop, Ruby Puma 5.0+; C++ not listed).
- OBI trace context association - the runtime-specific and language-agnostic mechanisms OBI uses to associate outgoing work with the incoming request, and the cases that miss.
- OBI trace context map (10-14 entry) - how a FUSE daemon reads the pinned OBI trace-context map keyed by the caller thread.
- kernel include/uapi/linux/fuse.h - the
fuse_in_header.pidfield that carries the caller thread id to the FUSE daemon. - W3C Trace Context - the
traceparentlayout OBI propagates.
Community discussion today
Two opt-in archive channels (the two CNCF OpenTelemetry instrumentation channels) produced eleven messages this run. The thread this page answers: a practitioner asked how feasible it is to add OpenTelemetry trace propagation to a FUSE program by reading OBI's trace context map using the pid and tid, and specifically whether it works for the common FUSE daemons written in Go, Python, and C++, noting that Go and Python with asyncio look supported but C++ is not certain. The archive also carried a maintainer's review-and-bump of an OpenTelemetry semantic-conventions pull request for a unified agent-to-agent interaction model (a review request, not a question), and a maintainer's caveat that the plan should work in theory only as long as the thread setting the trace context is the same thread doing the FUSE work. The open question this page closes: which language runtimes OBI can actually stamp a trace context on, and how to verify that the FUSE-issuing thread is one of them and that it inherits the same context.
Channel coverage for this run: the two opt-in archives supplied eleven messages. The visible-browser-only sources - the eunomia-bpf and sched-ext Discord servers, the bpf mailing list, and r/eBPF - could not be reviewed this run because no visible-browser session was available, so they are marked not-covered, not quiet.
Continue exploring
Back to index
eBPF Q&A
Daily, source-grounded answers to recurring questions about eBPF, Linux observability, profiling, runtime extension, and security.
Previous
Why do eBPF kprobe string reads truncate long open and stat paths, and how do you read the full path?
Because the string read's destination buffer has to live inside the BPF program's 512-byte stack, a single bpfprobereaduserstr call can capture at most 511 characters of a user string, while the kernel allows a full
Next
Why do FUSE daemon lookups in the OBI trace context map miss when the FUSE filesystem runs inside a PID namespace?
Because the two sides of the lookup are keyed in two different PID namespaces. The kernel fills fuseinheader.pid with the calling thread's thread id re-based into the mount's PID namespace, while OBI keys its
- Last updated
- Oct 16, 2026
- First published
- Oct 16, 2026
- Contributors
- Yunwei 123
Was this page helpful?