mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 08:01:06 +00:00
fix: preserve unix socket error semantics
This commit is contained in:
+37
-6
@@ -448,6 +448,33 @@ fn requested_keybindings() -> ClientKeybindings {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_handshake_recv_timeout(
|
||||
stream: &LocalStream,
|
||||
timeout: Option<Duration>,
|
||||
context: &'static str,
|
||||
) -> Result<(), ClientError> {
|
||||
match stream.set_recv_timeout(timeout) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) if err.kind() == io::ErrorKind::Unsupported => {
|
||||
debug!(err = %err, context, "client socket receive timeout unavailable");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(ClientError::ConnectionFailed(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn set_handshake_recv_timeout(
|
||||
stream: &LocalStream,
|
||||
timeout: Option<Duration>,
|
||||
_context: &'static str,
|
||||
) -> Result<(), ClientError> {
|
||||
stream
|
||||
.set_recv_timeout(timeout)
|
||||
.map_err(ClientError::ConnectionFailed)
|
||||
}
|
||||
|
||||
/// Performs the client→server handshake.
|
||||
///
|
||||
/// Sends Hello with the terminal size and protocol version, reads the Welcome
|
||||
@@ -484,13 +511,17 @@ fn do_handshake(
|
||||
.map_err(|e| ClientError::ConnectionFailed(io::Error::other(e.to_string())))?;
|
||||
|
||||
// Read Welcome.
|
||||
if let Err(err) = stream.set_recv_timeout(Some(Duration::from_secs(5))) {
|
||||
debug!(err = %err, "client handshake read timeout unavailable");
|
||||
}
|
||||
set_handshake_recv_timeout(
|
||||
stream,
|
||||
Some(Duration::from_secs(5)),
|
||||
"client handshake read timeout unavailable",
|
||||
)?;
|
||||
let welcome: ServerMessage = protocol::read_message(stream, MAX_FRAME_SIZE)?;
|
||||
if let Err(err) = stream.set_recv_timeout(None) {
|
||||
debug!(err = %err, "failed to clear client handshake read timeout");
|
||||
}
|
||||
set_handshake_recv_timeout(
|
||||
stream,
|
||||
None,
|
||||
"failed to clear client handshake read timeout",
|
||||
)?;
|
||||
|
||||
match welcome {
|
||||
ServerMessage::Welcome {
|
||||
|
||||
+23
-9
@@ -79,14 +79,7 @@ pub(crate) fn prepare_socket_path(
|
||||
Ok(_) => {
|
||||
return Err(io::Error::new(io::ErrorKind::AddrInUse, busy_message(path)));
|
||||
}
|
||||
Err(err)
|
||||
if matches!(
|
||||
err.kind(),
|
||||
io::ErrorKind::ConnectionRefused
|
||||
| io::ErrorKind::NotFound
|
||||
| io::ErrorKind::TimedOut
|
||||
| io::ErrorKind::WouldBlock
|
||||
) => {}
|
||||
Err(err) if stale_socket_connect_error(err.kind()) => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
|
||||
@@ -99,6 +92,13 @@ pub(crate) fn prepare_socket_path(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stale_socket_connect_error(kind: io::ErrorKind) -> bool {
|
||||
matches!(
|
||||
kind,
|
||||
io::ErrorKind::ConnectionRefused | io::ErrorKind::NotFound | io::ErrorKind::TimedOut
|
||||
) || (cfg!(windows) && kind == io::ErrorKind::WouldBlock)
|
||||
}
|
||||
|
||||
pub(crate) fn socket_file_identity(path: &Path) -> io::Result<SocketFileIdentity> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
@@ -159,11 +159,24 @@ pub(crate) fn restrict_socket_permissions(_path: &Path, _mode: u32) -> io::Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows))]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[cfg(windows)]
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn stale_socket_connect_errors_keep_unix_would_block_strict() {
|
||||
assert!(stale_socket_connect_error(io::ErrorKind::ConnectionRefused));
|
||||
assert!(stale_socket_connect_error(io::ErrorKind::NotFound));
|
||||
assert!(stale_socket_connect_error(io::ErrorKind::TimedOut));
|
||||
assert_eq!(
|
||||
stale_socket_connect_error(io::ErrorKind::WouldBlock),
|
||||
cfg!(windows)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn remove_socket_file_if_owned_compares_windows_marker_contents() {
|
||||
let path = temp_socket_marker_path("same-len-marker");
|
||||
@@ -180,6 +193,7 @@ mod tests {
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn temp_socket_marker_path(name: &str) -> PathBuf {
|
||||
std::env::temp_dir().join(format!("herdr-{name}-{}.sock", std::process::id()))
|
||||
}
|
||||
|
||||
@@ -153,6 +153,33 @@ fn input_events_within_limits(events: &[ClientInputEvent]) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn set_client_recv_timeout(
|
||||
stream: &LocalStream,
|
||||
timeout: Option<Duration>,
|
||||
context: &'static str,
|
||||
client_id: u64,
|
||||
) -> io::Result<()> {
|
||||
match stream.set_recv_timeout(timeout) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) if err.kind() == io::ErrorKind::Unsupported => {
|
||||
debug!(client_id, err = %err, context, "client socket receive timeout unavailable");
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn set_client_recv_timeout(
|
||||
stream: &LocalStream,
|
||||
timeout: Option<Duration>,
|
||||
_context: &'static str,
|
||||
_client_id: u64,
|
||||
) -> io::Result<()> {
|
||||
stream.set_recv_timeout(timeout)
|
||||
}
|
||||
|
||||
/// Handles the client handshake on a blocking thread.
|
||||
///
|
||||
/// Reads the `Hello` message, validates the version, sends `Welcome`,
|
||||
@@ -167,9 +194,12 @@ pub(crate) fn handle_client_handshake(
|
||||
// the handshake thread needs blocking I/O for read_message/write_message.
|
||||
stream.set_nonblocking(false)?;
|
||||
|
||||
if let Err(err) = stream.set_recv_timeout(Some(HANDSHAKE_TIMEOUT)) {
|
||||
debug!(client_id, err = %err, "client handshake read timeout unavailable");
|
||||
}
|
||||
set_client_recv_timeout(
|
||||
&stream,
|
||||
Some(HANDSHAKE_TIMEOUT),
|
||||
"client handshake read timeout unavailable",
|
||||
client_id,
|
||||
)?;
|
||||
|
||||
// Read the Hello message.
|
||||
let hello: ClientMessage = match protocol::read_message(&mut stream, MAX_FRAME_SIZE) {
|
||||
@@ -268,9 +298,12 @@ pub(crate) fn handle_client_handshake(
|
||||
};
|
||||
protocol::write_message(&mut stream, &welcome).map_err(|e| io::Error::other(e.to_string()))?;
|
||||
|
||||
if let Err(err) = stream.set_recv_timeout(None) {
|
||||
debug!(client_id, err = %err, "failed to clear client handshake read timeout");
|
||||
}
|
||||
set_client_recv_timeout(
|
||||
&stream,
|
||||
None,
|
||||
"failed to clear client handshake read timeout",
|
||||
client_id,
|
||||
)?;
|
||||
|
||||
// Create separate channels for reliable control messages and droppable renders.
|
||||
let (control_tx, control_rx) = std::sync::mpsc::channel::<Vec<u8>>();
|
||||
|
||||
Reference in New Issue
Block a user