Skip to content

Commit 31b96c9

Browse files
committed
Fix race condition in file exporter
1 parent 2e75bd8 commit 31b96c9

File tree

1 file changed

+35
-6
lines changed

1 file changed

+35
-6
lines changed

libdd-profiling/src/exporter/file_exporter.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4+
//! File-based HTTP request dumping for testing and debugging.
5+
//!
6+
//! This module implements a workaround to intercept and dump HTTP requests made by reqwest
7+
//! to a local file. It works by spawning a local server (Unix domain socket on Unix,
8+
//! named pipe on Windows) that captures the raw HTTP bytes before writing them to disk.
9+
//!
10+
//! This is primarily used for testing to validate the exact bytes sent over the wire.
11+
//!
12+
//! # Future
13+
//!
14+
//! This module exists as a workaround and will hopefully be replaced once reqwest adds
15+
//! native support for file output: <https://github.com/seanmonstar/reqwest/issues/2883>
16+
417
#[cfg(any(unix, windows))]
518
use std::path::PathBuf;
619

@@ -64,13 +77,22 @@ pub(crate) fn spawn_dump_server(output_path: PathBuf) -> anyhow::Result<PathBuf>
6477

6578
/// Async server loop for Windows named pipes
6679
#[cfg(windows)]
67-
async fn run_dump_server_windows(output_path: PathBuf, pipe_name: String) -> anyhow::Result<()> {
80+
async fn run_dump_server_windows(
81+
output_path: PathBuf,
82+
pipe_name: String,
83+
mut first_server: tokio::net::windows::named_pipe::NamedPipeServer,
84+
) -> anyhow::Result<()> {
6885
use tokio::net::windows::named_pipe::ServerOptions;
6986

87+
// Handle first connection
88+
first_server.connect().await?;
89+
handle_connection_async(first_server, output_path.clone()).await;
90+
91+
// Handle subsequent connections
7092
loop {
71-
// Create server instance
93+
// Create server instance (not the first one)
7294
let server = ServerOptions::new()
73-
.first_pipe_instance(true)
95+
.first_pipe_instance(false)
7496
.create(&pipe_name)?;
7597

7698
// Wait for client connection
@@ -85,6 +107,8 @@ async fn run_dump_server_windows(output_path: PathBuf, pipe_name: String) -> any
85107
/// Returns the named pipe path that the server is listening on
86108
#[cfg(windows)]
87109
pub(crate) fn spawn_dump_server(output_path: PathBuf) -> anyhow::Result<PathBuf> {
110+
use tokio::net::windows::named_pipe::ServerOptions;
111+
88112
// Create a unique named pipe name with randomness to avoid collisions
89113
let random_id: u64 = rand::random();
90114
let pipe_name = format!(
@@ -95,17 +119,22 @@ pub(crate) fn spawn_dump_server(output_path: PathBuf) -> anyhow::Result<PathBuf>
95119
let pipe_path = PathBuf::from(&pipe_name);
96120

97121
let (tx, rx) = std::sync::mpsc::channel();
98-
122+
99123
std::thread::spawn(move || {
100124
// Top-level error handler - all errors logged here
101125
let result = (|| -> anyhow::Result<()> {
102126
let rt = tokio::runtime::Runtime::new()?;
103127
rt.block_on(async {
128+
// Create the first pipe instance before signaling ready
129+
let first_server = ServerOptions::new()
130+
.first_pipe_instance(true)
131+
.create(&pipe_name)?;
132+
104133
tx.send(Ok(()))?;
105-
run_dump_server_windows(output_path, pipe_name).await
134+
run_dump_server_windows(output_path, pipe_name, first_server).await
106135
})
107136
})();
108-
137+
109138
if let Err(e) = result {
110139
eprintln!("[dump-server] Error: {}", e);
111140
let _ = tx.send(Err(e));

0 commit comments

Comments
 (0)