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 => {
|
None if std::time::Instant::now() >= deadline => {
|
||||||
let _ = child.kill();
|
let _ = child.kill();
|
||||||
let _ = child.wait();
|
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");
|
bail!("the wayland socket probe did not answer and was killed");
|
||||||
}
|
}
|
||||||
None => std::thread::sleep(std::time::Duration::from_millis(25)),
|
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");
|
bail!("this binary does not dispatch {WAYLAND_DISPLAY_PROBE_ARG}; probe disabled");
|
||||||
}
|
}
|
||||||
if !status.success() {
|
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}");
|
||||||
}
|
}
|
||||||
let displays: Vec<WaylandDisplayInfo> = serde_json::from_str(lines.next().unwrap_or_default())?;
|
bail!("wayland socket probe failed ({status}): {detail}");
|
||||||
|
}
|
||||||
|
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
|
// The child already refuses an empty list; refuse it here too, so a truncated pipe cannot
|
||||||
// become a cached-for-life empty enumeration.
|
// become a cached-for-life empty enumeration.
|
||||||
if displays.is_empty() {
|
if displays.is_empty() {
|
||||||
@@ -579,6 +601,32 @@ fn wayland_displays_from_runtime_dir(named_endpoint: bool) -> ResultType<Vec<Way
|
|||||||
Ok(displays)
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
fn probe_runtime_dir(dir: &Path) -> ResultType<Vec<WaylandDisplayInfo>> {
|
fn probe_runtime_dir(dir: &Path) -> ResultType<Vec<WaylandDisplayInfo>> {
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
|
|||||||
Reference in New Issue
Block a user