From 114aa6808218c68e69dcd0730d67567fe44681ef Mon Sep 17 00:00:00 2001 From: nicm Date: Mon, 3 Aug 2026 20:18:20 +0000 Subject: [PATCH] Do not let a stuck client prevent the server from exiting - give up after 10 seconds. GitHub issue 5444 from Ben Maurer. --- control.c | 19 ++++++++++++++++++- server-client.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- tmux.h | 5 ++++- 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/control.c b/control.c index d3d078dd0..036a25882 100644 --- a/control.c +++ b/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.63 2026/08/03 13:38:42 nicm Exp $ */ +/* $OpenBSD: control.c,v 1.64 2026/08/03 20:18:20 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott @@ -968,6 +968,23 @@ control_discard(struct client *c) bufferevent_disable(cs->read_event, EV_READ); } +/* + * Discard all output for a client, including output which has already been + * queued to be written. + */ +void +control_discard_all(struct client *c) +{ + struct control_state *cs = c->control_state; + struct control_block *cb, *cb1; + struct evbuffer *evb = cs->write_event->output; + + control_discard(c); + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) + control_free_block(cs, cb); + evbuffer_drain(evb, EVBUFFER_LENGTH(evb)); +} + /* Stop control mode. */ void control_stop(struct client *c) diff --git a/server-client.c b/server-client.c index d389dacef..a7270e88f 100644 --- a/server-client.c +++ b/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.500 2026/07/28 13:17:45 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.501 2026/08/03 20:18:20 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -42,6 +42,7 @@ static key_code server_client_check_mouse(struct client *, struct key_event *); static void server_client_repeat_timer(int, short, void *); static void server_client_click_timer(int, short, void *); static void server_client_check_exit(struct client *); +static void server_client_exit_timer(int, short, void *); static void server_client_check_redraw(struct client *); static void server_client_check_modes(struct client *); static void server_client_set_title(struct client *); @@ -314,6 +315,7 @@ server_client_create(int fd) evtimer_set(&c->repeat_timer, server_client_repeat_timer, c); evtimer_set(&c->click_timer, server_client_click_timer, c); + evtimer_set(&c->exit_timer, server_client_exit_timer, c); c->click_wp = -1; @@ -530,6 +532,7 @@ server_client_lost(struct client *c) evtimer_del(&c->repeat_timer); evtimer_del(&c->click_timer); + evtimer_del(&c->exit_timer); if (event_initialized(&c->cycle_timer)) evtimer_del(&c->cycle_timer); @@ -2290,6 +2293,39 @@ server_client_click_timer(__unused int fd, __unused short events, void *data) c->flags &= ~(CLIENT_DOUBLECLICK|CLIENT_TRIPLECLICK); } +/* Start client exit timer. */ +static void +server_client_start_exit_timer(struct client *c) +{ + struct timeval tv = { .tv_sec = 10 }; + + if (!evtimer_pending(&c->exit_timer, NULL)) + evtimer_add(&c->exit_timer, &tv); +} + +/* Exit timer has expired: stop waiting for the client. */ +static void +server_client_exit_timer(__unused int fd, __unused short events, void *data) +{ + struct client *c = data; + struct client_file *cf; + + if (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) + return; + + if (c->flags & CLIENT_EXITED) { + log_debug("%s: %s took too long to exit", __func__, c->name); + server_client_lost(c); + } else if (c->flags & CLIENT_EXIT) { + log_debug("%s: %s took too long to flush", __func__, c->name); + if (c->flags & CLIENT_CONTROL) + control_discard_all(c); + RB_FOREACH(cf, client_files, &c->files) + evbuffer_drain(cf->buffer, EVBUFFER_LENGTH(cf->buffer)); + server_client_check_exit(c); + } +} + /* Check if client should be exited. */ static void server_client_check_exit(struct client *c) @@ -2306,15 +2342,22 @@ server_client_check_exit(struct client *c) if (c->flags & CLIENT_CONTROL) { control_discard(c); - if (!control_all_done(c)) + if (!control_all_done(c)) { + server_client_start_exit_timer(c); return; + } } RB_FOREACH(cf, client_files, &c->files) { - if (EVBUFFER_LENGTH(cf->buffer) != 0) + if (EVBUFFER_LENGTH(cf->buffer) != 0) { + server_client_start_exit_timer(c); return; + } } c->flags |= CLIENT_EXITED; + evtimer_del(&c->exit_timer); + server_client_start_exit_timer(c); + switch (c->exit_type) { case CLIENT_EXIT_RETURN: if (c->exit_message != NULL) diff --git a/tmux.h b/tmux.h index d732b55e8..83fa06381 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1420 2026/08/03 13:38:42 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1421 2026/08/03 20:18:20 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -2202,6 +2202,8 @@ struct client { struct event click_timer; int click_loc; int click_wp; + + struct event exit_timer; u_int click_button; struct mouse_event click_event; @@ -3970,6 +3972,7 @@ time_t monitor_get_fire_time(struct monitor_set *, const char *); /* control.c */ void control_discard(struct client *); +void control_discard_all(struct client *); void control_start(struct client *); void control_ready(struct client *); void control_stop(struct client *);