mirror of
https://github.com/rustdesk/hbb_common.git
synced 2026-08-27 04:37:35 +00:00
fix: latch an unwired consumer on the timeout path too
A binary that does not dispatch the probe argument runs its normal startup; a long-running one outlives the deadline and was killed before the handshake check could run, so PROBE_UNSUPPORTED never latched and every enumeration cycle spawned a full consumer process again. The timeout path now judges the child by what it already wrote: a real probe prints the magic line first and flushes, so its absence after a whole deadline means this is not a probe. Only the buffered bytes are read, non-blocking, because an EOF-seeking read could hang on a grandchild that inherited the write end of the pipe. Also report the exit status when a failed child left stderr empty, which panic=abort and signals do, and name the malformed-list case in the deserialization error.
This commit is contained in:
+50
-2
@@ -543,6 +543,19 @@ fn wayland_displays_from_runtime_dir(named_endpoint: bool) -> ResultType<Vec<Way
|
||||
None if std::time::Instant::now() >= deadline => {
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
// An unwired binary runs its normal startup, and a long-running one (the
|
||||
// server itself) lands HERE rather than at the handshake check below — latch
|
||||
// on this path too, or every enumeration cycle spawns a full consumer
|
||||
// process. Judged by what the child already wrote: a real probe prints the
|
||||
// magic line first and flushes, so its absence after a whole deadline means
|
||||
// this is not a probe. Only buffered bytes are read — a blocking read could
|
||||
// hang on a grandchild that inherited the write end.
|
||||
if first_buffered_line(child.stdout.take()).as_deref()
|
||||
!= Some(WAYLAND_PROBE_MAGIC)
|
||||
{
|
||||
PROBE_UNSUPPORTED.store(true, Ordering::Release);
|
||||
bail!("the wayland socket probe timed out without the handshake; probe disabled");
|
||||
}
|
||||
bail!("the wayland socket probe did not answer and was killed");
|
||||
}
|
||||
None => std::thread::sleep(std::time::Duration::from_millis(25)),
|
||||
@@ -564,9 +577,18 @@ fn wayland_displays_from_runtime_dir(named_endpoint: bool) -> ResultType<Vec<Way
|
||||
bail!("this binary does not dispatch {WAYLAND_DISPLAY_PROBE_ARG}; probe disabled");
|
||||
}
|
||||
if !status.success() {
|
||||
bail!("wayland socket probe: {}", stderr.trim());
|
||||
let detail = stderr.trim();
|
||||
if detail.is_empty() {
|
||||
// panic=abort or a signal leaves stderr empty; the status is then the only cause.
|
||||
bail!("wayland socket probe failed: {status}");
|
||||
}
|
||||
bail!("wayland socket probe failed ({status}): {detail}");
|
||||
}
|
||||
let displays: Vec<WaylandDisplayInfo> = serde_json::from_str(lines.next().unwrap_or_default())?;
|
||||
let displays: Vec<WaylandDisplayInfo> = match serde_json::from_str(lines.next().unwrap_or_default())
|
||||
{
|
||||
Ok(displays) => displays,
|
||||
Err(err) => bail!("wayland socket probe answered a malformed list: {err}"),
|
||||
};
|
||||
// The child already refuses an empty list; refuse it here too, so a truncated pipe cannot
|
||||
// become a cached-for-life empty enumeration.
|
||||
if displays.is_empty() {
|
||||
@@ -579,6 +601,32 @@ fn wayland_displays_from_runtime_dir(named_endpoint: bool) -> ResultType<Vec<Way
|
||||
Ok(displays)
|
||||
}
|
||||
|
||||
/// The first line already sitting in the pipe buffer, read strictly non-blocking: children of a
|
||||
/// killed consumer can inherit the write end and keep it open, so an EOF-seeking read here could
|
||||
/// hang the enumeration forever.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn first_buffered_line(pipe: Option<std::process::ChildStdout>) -> Option<String> {
|
||||
use std::io::Read;
|
||||
use std::os::fd::AsRawFd;
|
||||
let mut pipe = pipe?;
|
||||
let fd = pipe.as_raw_fd();
|
||||
unsafe {
|
||||
let flags = libc::fcntl(fd, libc::F_GETFL);
|
||||
if flags < 0 || libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) < 0 {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// The magic line is written in one flush and fits many times over; one read is enough.
|
||||
let mut buf = vec![0u8; 256];
|
||||
match pipe.read(&mut buf) {
|
||||
Ok(n) => {
|
||||
buf.truncate(n);
|
||||
String::from_utf8_lossy(&buf).lines().next().map(str::to_owned)
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn probe_runtime_dir(dir: &Path) -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
Reference in New Issue
Block a user