diff --git a/format.c b/format.c index 633be101..269d506a 100644 --- a/format.c +++ b/format.c @@ -1154,7 +1154,9 @@ format_defaults_client(struct format_tree *ft, struct client *c) format_add_tv(ft, "client_created", &c->creation_time); format_add_tv(ft, "client_activity", &c->activity_time); - format_add(ft, "client_written", "%zu", tty->written); + + format_add(ft, "client_written", "%zu", c->written); + format_add(ft, "client_discarded", "%zu", c->discarded); name = server_client_get_key_table(c); if (strcmp(c->keytable->name, name) == 0) diff --git a/server-client.c b/server-client.c index 2279d43a..f4fc915b 100644 --- a/server-client.c +++ b/server-client.c @@ -1269,8 +1269,8 @@ server_client_check_redraw(struct client *c) screen_redraw_update(c); /* will adjust flags */ } - flags = tty->flags & (TTY_FREEZE|TTY_NOCURSOR); - tty->flags = (tty->flags & ~TTY_FREEZE) | TTY_NOCURSOR; + flags = tty->flags & (TTY_BLOCK|TTY_FREEZE|TTY_NOCURSOR); + tty->flags = (tty->flags & ~(TTY_BLOCK|TTY_FREEZE)) | TTY_NOCURSOR; if (c->flags & CLIENT_REDRAW) { tty_update_mode(tty, tty->mode, NULL); diff --git a/tmux.1 b/tmux.1 index d4330d18..681c2381 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3508,6 +3508,7 @@ The following variables are available, where appropriate: .It Li "client_activity" Ta "" Ta "Integer time client last had activity" .It Li "client_created" Ta "" Ta "Integer time client created" .It Li "client_control_mode" Ta "" Ta "1 if client is in control mode" +.It Li "client_discarded" Ta "" Ta "Bytes discarded when client behind" .It Li "client_height" Ta "" Ta "Height of client" .It Li "client_key_table" Ta "" Ta "Current key table" .It Li "client_last_session" Ta "" Ta "Name of the client's last session" diff --git a/tmux.h b/tmux.h index 3387798f..ac279123 100644 --- a/tmux.h +++ b/tmux.h @@ -1043,7 +1043,8 @@ struct tty { struct evbuffer *in; struct event event_out; struct evbuffer *out; - size_t written; + struct event timer; + size_t discarded; struct termios tio; @@ -1059,6 +1060,7 @@ struct tty { #define TTY_STARTED 0x10 #define TTY_OPENED 0x20 #define TTY_FOCUS 0x40 +#define TTY_BLOCK 0x80 int flags; struct tty_term *term; @@ -1306,6 +1308,9 @@ struct client { char *ttyname; struct tty tty; + size_t written; + size_t discarded; + void (*stdin_callback)(struct client *, int, void *); void *stdin_callback_data; struct evbuffer *stdin_data; diff --git a/tty.c b/tty.c index 73aeb91f..362cd36c 100644 --- a/tty.c +++ b/tty.c @@ -79,6 +79,10 @@ static void tty_default_attributes(struct tty *, const struct window_pane *, #define tty_pane_full_width(tty, ctx) \ ((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx) +#define TTY_BLOCK_INTERVAL (100000 /* 100 milliseconds */) +#define TTY_BLOCK_START(tty) (1 + ((tty)->sx * (tty)->sy) * 8) +#define TTY_BLOCK_STOP(tty) (1 + ((tty)->sx * (tty)->sy) / 8) + void tty_create_log(void) { @@ -171,6 +175,51 @@ tty_read_callback(__unused int fd, __unused short events, void *data) ; } +static void +tty_timer_callback(__unused int fd, __unused short events, void *data) +{ + struct tty *tty = data; + struct client *c = tty->client; + struct timeval tv = { .tv_usec = TTY_BLOCK_INTERVAL }; + + log_debug("%s: %zu discarded", c->name, tty->discarded); + + c->flags |= CLIENT_REDRAW; + c->discarded += tty->discarded; + + if (tty->discarded < TTY_BLOCK_STOP(tty)) { + tty->flags &= ~TTY_BLOCK; + tty_invalidate(tty); + return; + } + tty->discarded = 0; + evtimer_add(&tty->timer, &tv); +} + +static int +tty_block_maybe(struct tty *tty) +{ + struct client *c = tty->client; + size_t size = EVBUFFER_LENGTH(tty->out); + struct timeval tv = { .tv_usec = TTY_BLOCK_INTERVAL }; + + if (size < TTY_BLOCK_START(tty)) + return (0); + + if (tty->flags & TTY_BLOCK) + return (1); + tty->flags |= TTY_BLOCK; + + log_debug("%s: can't keep up, %zu discarded", c->name, size); + + evbuffer_drain(tty->out, size); + c->discarded += size; + + tty->discarded = 0; + evtimer_add(&tty->timer, &tv); + return (1); +} + static void tty_write_callback(__unused int fd, __unused short events, void *data) { @@ -184,6 +233,9 @@ tty_write_callback(__unused int fd, __unused short events, void *data) return; log_debug("%s: wrote %d bytes (of %zu)", c->name, nwrite, size); + if (tty_block_maybe(tty)) + return; + if (EVBUFFER_LENGTH(tty->out) != 0) event_add(&tty->event_out, NULL); } @@ -198,7 +250,7 @@ tty_open(struct tty *tty, char **cause) } tty->flags |= TTY_OPENED; - tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE); + tty->flags &= ~(TTY_NOCURSOR|TTY_FREEZE|TTY_BLOCK|TTY_TIMER); event_set(&tty->event_in, tty->fd, EV_PERSIST|EV_READ, tty_read_callback, tty); @@ -207,6 +259,8 @@ tty_open(struct tty *tty, char **cause) event_set(&tty->event_out, tty->fd, EV_WRITE, tty_write_callback, tty); tty->out = evbuffer_new(); + evtimer_set(&tty->timer, tty_timer_callback, tty); + tty_start_tty(tty); tty_keys_build(tty); @@ -273,6 +327,9 @@ tty_stop_tty(struct tty *tty) return; tty->flags &= ~TTY_STARTED; + event_del(&tty->timer); + tty->flags &= ~TTY_BLOCK; + event_del(&tty->event_in); event_del(&tty->event_out); @@ -425,9 +482,14 @@ tty_add(struct tty *tty, const char *buf, size_t len) { struct client *c = tty->client; + if (tty->flags & TTY_BLOCK) { + tty->discarded += len; + return; + } + evbuffer_add(tty->out, buf, len); log_debug("%s: %.*s", c->name, (int)len, (const char *)buf); - tty->written += len; + c->written += len; if (tty_log_fd != -1) write(tty_log_fd, buf, len);