Skip to content

Commit 099e25d

Browse files
committed
fixup! Test: Bind to port 0
1 parent e08a325 commit 099e25d

3 files changed

Lines changed: 31 additions & 6 deletions

File tree

libs/opsqueue_python/tests/conftest.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ def opsqueue_service(
9292
assert process.poll() is None, "Opsqueue process failed to start"
9393
try:
9494
actual_port = int.from_bytes(
95-
os.read(read_fd, 2), byteorder="big", signed=False
95+
read_exact_fd(read_fd, 2),
96+
byteorder="big",
97+
signed=False,
9698
)
9799
wrapper = OpsqueueProcess(port=actual_port, process=process)
98100
yield wrapper
@@ -105,6 +107,18 @@ def opsqueue_service(
105107
os.close(read_fd)
106108

107109

110+
def read_exact_fd(fd: int, num_bytes: int) -> bytes:
111+
data = bytearray()
112+
while len(data) < num_bytes:
113+
chunk = os.read(fd, num_bytes - len(data))
114+
if not chunk:
115+
raise EOFError(
116+
f"Unexpected EOF: expected {num_bytes} bytes, got {len(data)}: {bytes(data)!r}"
117+
)
118+
data.extend(chunk)
119+
return bytes(data)
120+
121+
108122
@contextmanager
109123
def background_process(
110124
function: Callable[..., None],

opsqueue/src/config.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ pub struct Config {
2323
#[arg(short, long, default_value_t = 3999)]
2424
pub port: u16,
2525

26-
/// Optional file descriptor where the final bound TCP port is written.
26+
/// File descriptor where the final bound TCP port is written.
2727
///
28-
/// This is useful when `--port 0` is used and a parent process wants
29-
/// to receive the assigned port without filesystem IO.
30-
#[arg(long, value_parser = parse_report_bound_port_fd)]
28+
/// This is useful when `--port 0` is used and a parent process wants to receive the assigned
29+
/// port without filesystem IO.
30+
///
31+
/// The port is written as a 16-bit (u16) big-endian integer to the given file descriptor which
32+
/// is then closed. On error the file descriptor is closed without writing anything.
33+
#[arg(long, value_parser = parse_report_bound_port_fd, default_value = "-1")]
3134
pub report_bound_port_pipe: ReportBoundPortPipe,
3235

3336
/// Name of the SQLite database file used by this opsqueue.
@@ -144,6 +147,10 @@ fn parse_report_bound_port_fd(value: &str) -> Result<ReportBoundPortPipe, String
144147
.parse::<i32>()
145148
.map_err(|err| format!("invalid file descriptor {value:?}: {err}"))?;
146149

150+
if fd == -1 {
151+
return Ok(ReportBoundPortPipe::default());
152+
}
153+
147154
if fd < 0 {
148155
return Err(format!(
149156
"invalid file descriptor {value:?}: must be non-negative"

opsqueue/src/server.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ pub async fn serve_producer_and_consumer(
6868
})
6969
.retry(retry_policy())
7070
.notify(|e, d| tracing::error!("Error when binding server address: {e:?}, retrying in {d:?}"))
71-
.await
71+
.await.inspect_err(|_|{
72+
// Drop the pipe after the server start retries have been exhausted. This ensures that the
73+
// parent process can safely block on reading from the pipe.
74+
config.report_bound_port_pipe.take();
75+
})
7276
}
7377

7478
#[cfg(feature = "server-logic")]

0 commit comments

Comments
 (0)