mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 00:02:23 +00:00
fix(client): stop timeout setup from masking the daemon's refusal
On macOS, setsockopt against a socket whose peer has already closed fails with EINVAL. The daemon answers a bad request by writing one Error frame and hanging up at once, so PaneSession's `set_recv_timeout(...)?` would fail before the refusal was ever read — turning "no such pane 42", already sitting in the buffer, into "Invalid argument". Bounding the reply wait is an optimisation, not a correctness requirement, so it is now best effort in both attach/observe and spawn. Nothing can hang as a result: a closed peer returns EOF immediately, and a live peer is exactly the case where setsockopt succeeds. This is what made client_lib's reattach test red.
This commit is contained in:
@@ -118,7 +118,15 @@ impl PaneClient {
|
||||
owner: Option<String>,
|
||||
workspace: Option<String>,
|
||||
) -> io::Result<PaneSession> {
|
||||
PaneSession::spawn_over(self.open()?, cwd, size, shell, owner, workspace, OPEN_REPLY_WAIT)
|
||||
PaneSession::spawn_over(
|
||||
self.open()?,
|
||||
cwd,
|
||||
size,
|
||||
shell,
|
||||
owner,
|
||||
workspace,
|
||||
OPEN_REPLY_WAIT,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn attach(&self, pane_id: u64, size: WinSize) -> io::Result<PaneSession> {
|
||||
@@ -155,9 +163,12 @@ impl PaneSession {
|
||||
}
|
||||
.encode(&mut stream)?;
|
||||
let mut session = PaneSession::over(stream, 0)?;
|
||||
session.set_recv_timeout(Some(reply_wait))?;
|
||||
// Best effort, deliberately not `?`: see `checked` — a daemon that has
|
||||
// already refused and hung up makes setsockopt fail, and that must not
|
||||
// become the error the caller sees instead of the refusal itself.
|
||||
let _ = session.set_recv_timeout(Some(reply_wait));
|
||||
let first = session.recv();
|
||||
session.set_recv_timeout(None)?;
|
||||
let _ = session.set_recv_timeout(None);
|
||||
match first {
|
||||
Ok(DaemonMsg::Spawned { pane_id }) => {
|
||||
session.input.pane_id = pane_id;
|
||||
@@ -202,9 +213,17 @@ impl PaneSession {
|
||||
reply_wait: Duration,
|
||||
) -> io::Result<PaneSession> {
|
||||
let mut session = PaneSession::over(stream, pane_id)?;
|
||||
session.set_recv_timeout(Some(reply_wait))?;
|
||||
// Best effort, deliberately not `?`. The daemon answers a bad request by
|
||||
// writing one Error frame and closing immediately; on macOS setsockopt
|
||||
// against a socket whose peer is already gone fails with EINVAL. Letting
|
||||
// that propagate replaces "no such pane 42" — which is sitting in our
|
||||
// buffer right now — with "Invalid argument", the least useful thing we
|
||||
// could tell the caller. If the timeout does not take, the read below
|
||||
// still cannot hang: a closed peer returns EOF at once, and a peer
|
||||
// that is alive is exactly the case where setsockopt succeeds.
|
||||
let _ = session.set_recv_timeout(Some(reply_wait));
|
||||
let verdict = session.output.refusal_check(request, pane_id);
|
||||
session.set_recv_timeout(None)?;
|
||||
let _ = session.set_recv_timeout(None);
|
||||
verdict?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
@@ -249,8 +249,7 @@ fn control_requests_build_the_tree_and_events_reach_the_other_client() {
|
||||
);
|
||||
match watcher.next_event(remaining) {
|
||||
Some(ControlEvent::Layout { workspace, delta })
|
||||
if workspace == key
|
||||
&& matches!(delta, LayoutDelta::WorkspaceCreated { .. }) =>
|
||||
if workspace == key && matches!(delta, LayoutDelta::WorkspaceCreated { .. }) =>
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user