feat(data-pipeline): add fork-safe OTLP gRPC trace transport#2273
feat(data-pipeline): add fork-safe OTLP gRPC trace transport#2273bm1549 wants to merge 1 commit into
Conversation
Adds a plaintext gRPC-over-HTTP/2 (h2c) trace-export transport for OTLP, implemented as a custom tower::Service plugged into tonic's Grpc<T>. It holds no persistent connection or background task: each send opens a fresh connection driven by ephemeral per-request tasks that are torn down when the send completes, so nothing is orphaned across fork(2). Includes a minimal prost codec (tonic 0.14 moved ProstCodec to a separate crate; hand-rolled here to avoid the extra dependency), endpoint validation (plaintext http:// only; https:// rejected), gRPC-status-to-error mapping that recovers nested IO errors, per-request metadata (validated once at build), and unit plus in-process h2 integration tests. This is the transport primitive; the OTLP export path is wired to it in a follow-up change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📚 Documentation Check Results📦
|
🔒 Cargo Deny Results📦
|
🎉 All green!🧪 All tests passed 🎯 Code Coverage (details) 🔗 Commit SHA: b7fb78d | Docs | Datadog PR Page | Give us feedback! |
Artifact Size Benchmark Reportaarch64-alpine-linux-musl
aarch64-unknown-linux-gnu
libdatadog-x64-windows
libdatadog-x86-windows
x86_64-alpine-linux-musl
x86_64-unknown-linux-gnu
|
BenchmarksComparisonBenchmark execution time: 2026-07-24 17:47:41 Comparing candidate commit b7fb78d in PR branch Found 5 performance improvements and 11 performance regressions! Performance is the same for 126 metrics, 0 unstable metrics.
|
|
|
||
| /// Per-request OTLP gRPC trace exporter configuration. | ||
| // Not yet wired to the trace exporter's send loop; exercised by tests only. | ||
| #[allow(dead_code)] |
|
|
||
| /// Validate a gRPC endpoint (plaintext `http://` only) and build the transport. | ||
| // Not yet wired to the trace exporter's send loop; exercised by tests only. | ||
| #[allow(dead_code)] |
|
|
||
| /// Send one OTLP trace export request over gRPC. Bounds connect + RPC with a single timeout. | ||
| // Not yet wired to the trace exporter's send loop; exercised by tests only. | ||
| #[allow(dead_code)] |
What does this PR do?
Adds a plaintext gRPC-over-HTTP/2 (h2c) trace-export transport for OTLP to
libdd-data-pipeline. It's a customtower::Service(H2Service) plugged into tonic's genericGrpc<T>client, so the exporter holds no persistent connection or background task: each send opens a fresh connection driven by ephemeral per-request tasks that are torn down when the send completes.This is the transport primitive only. The OTLP export path is wired to it in a follow-up PR that rebases on top of this one.
Motivation
The earlier OTLP gRPC approach built a tonic
Channel, which spawns a persistent background task. Because libdatadog rebuilds its runtime acrossfork(2), that task dies silently after a fork, which forced aWorkerpluswatch-channel to rebuild it. Making the transport per-request and task-free removes that machinery and is fork-safe by construction.Additional Notes
default-features = falseon tonic (notransport/channel/server); a minimal hand-rolled prost codec replacesProstCodec(which moved to the separatetonic-prostcrate) to avoid the extra dependency.https://endpoints are rejected at build time; terminate TLS in a proxy in front of the endpoint if encryption is needed.How to test the change?
cargo nextest run -p libdd-data-pipelinecovers unit tests plus in-processh2integration tests: a real unary send decoded by a mock gRPC server, and connection-refused, timeout, and post-connect-reset cases that all map toTraceExporterError::Io.cargo clippy -p libdd-data-pipeline --all-targets -- -D warnings,cargo +nightly fmt --check, andcargo check -p libdd-data-pipeline --target wasm32-unknown-unknown --no-default-features(the gRPC path is wasm-gated) pass.