Instead of sending all data to control mode clients as fast as possible,

add a limit of how much data will be sent to the client and try to use
it for panes with some degree of fairness. GitHub issue 2217, with
George Nachman.
This commit is contained in:
nicm
2020-06-01 09:43:00 +00:00
parent 175e45005f
commit a54a88edd6
11 changed files with 490 additions and 176 deletions

474
control.c
View File

@ -27,112 +27,183 @@
#include "tmux.h"
/* Control client offset. */
struct control_offset {
u_int pane;
/*
* Block of data to output. Each client has one "all" queue of blocks and
* another queue for each pane (in struct client_offset). %output blocks are
* added to both queues and other output lines (notifications) added only to
* the client queue.
*
* When a client becomes writeable, data from blocks on the pane queue are sent
* up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written,
* it is removed from both pane and client queues and if this means non-%output
* blocks are now at the head of the client queue, they are written.
*
* This means a %output block holds up any subsequent non-%output blocks until
* it is written which enforces ordering even if the client cannot accept the
* entire block in one go.
*/
struct control_block {
size_t size;
char *line;
struct window_pane_offset offset;
int flags;
#define CONTROL_OFFSET_OFF 0x1
TAILQ_ENTRY(control_block) entry;
TAILQ_ENTRY(control_block) all_entry;
};
RB_ENTRY(control_offset) entry;
/* Control client pane. */
struct control_pane {
u_int pane;
/*
* Offsets into the pane data. The first (offset) is the data we have
* written; the second (queued) the data we have queued (pointed to by
* a block).
*/
struct window_pane_offset offset;
struct window_pane_offset queued;
int flags;
#define CONTROL_PANE_OFF 0x1
int pending_flag;
TAILQ_ENTRY(control_pane) pending_entry;
TAILQ_HEAD(, control_block) blocks;
RB_ENTRY(control_pane) entry;
};
RB_HEAD(control_offsets, control_offset);
RB_HEAD(control_panes, control_pane);
/* Control client state. */
struct control_state {
struct control_offsets offsets;
struct control_panes panes;
struct bufferevent *read_event;
struct bufferevent *write_event;
TAILQ_HEAD(, control_pane) pending_list;
u_int pending_count;
TAILQ_HEAD(, control_block) all_blocks;
struct bufferevent *read_event;
struct bufferevent *write_event;
};
/* Compare client offsets. */
/* Low watermark. */
#define CONTROL_BUFFER_LOW 512
#define CONTROL_BUFFER_HIGH 8192
/* Minimum to write to each client. */
#define CONTROL_WRITE_MINIMUM 32
/* Flags to ignore client. */
#define CONTROL_IGNORE_FLAGS \
(CLIENT_CONTROL_NOOUTPUT| \
CLIENT_UNATTACHEDFLAGS)
/* Compare client panes. */
static int
control_offset_cmp(struct control_offset *co1, struct control_offset *co2)
control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
{
if (co1->pane < co2->pane)
if (cp1->pane < cp2->pane)
return (-1);
if (co1->pane > co2->pane)
if (cp1->pane > cp2->pane)
return (1);
return (0);
}
RB_GENERATE_STATIC(control_offsets, control_offset, entry, control_offset_cmp);
RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
/* Free a block. */
static void
control_free_block(struct control_state *cs, struct control_block *cb)
{
free(cb->line);
TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
free(cb);
}
/* Get pane offsets for this client. */
static struct control_offset *
control_get_offset(struct client *c, struct window_pane *wp)
static struct control_pane *
control_get_pane(struct client *c, struct window_pane *wp)
{
struct control_state *cs = c->control_state;
struct control_offset co = { .pane = wp->id };
struct control_pane cp = { .pane = wp->id };
return (RB_FIND(control_offsets, &cs->offsets, &co));
return (RB_FIND(control_panes, &cs->panes, &cp));
}
/* Add pane offsets for this client. */
static struct control_offset *
control_add_offset(struct client *c, struct window_pane *wp)
static struct control_pane *
control_add_pane(struct client *c, struct window_pane *wp)
{
struct control_state *cs = c->control_state;
struct control_offset *co;
struct control_pane *cp;
co = control_get_offset(c, wp);
if (co != NULL)
return (co);
cp = control_get_pane(c, wp);
if (cp != NULL)
return (cp);
co = xcalloc(1, sizeof *co);
co->pane = wp->id;
RB_INSERT(control_offsets, &cs->offsets, co);
memcpy(&co->offset, &wp->offset, sizeof co->offset);
return (co);
cp = xcalloc(1, sizeof *cp);
cp->pane = wp->id;
RB_INSERT(control_panes, &cs->panes, cp);
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
TAILQ_INIT(&cp->blocks);
return (cp);
}
/* Free control offsets. */
/* Reset control offsets. */
void
control_free_offsets(struct client *c)
control_reset_offsets(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_offset *co, *co1;
struct control_pane *cp, *cp1;
RB_FOREACH_SAFE(co, control_offsets, &cs->offsets, co1) {
RB_REMOVE(control_offsets, &cs->offsets, co);
free(co);
RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
RB_REMOVE(control_panes, &cs->panes, cp);
free(cp);
}
TAILQ_INIT(&cs->pending_list);
cs->pending_count = 0;
}
/* Get offsets for client. */
struct window_pane_offset *
control_pane_offset(struct client *c, struct window_pane *wp, int *off)
{
struct control_offset *co;
struct control_state *cs = c->control_state;
struct control_pane *cp;
if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
*off = 0;
return (NULL);
}
co = control_get_offset(c, wp);
if (co == NULL) {
cp = control_get_pane(c, wp);
if (cp == NULL) {
*off = 0;
return (NULL);
}
if (co->flags & CONTROL_OFFSET_OFF) {
if (cp->flags & CONTROL_PANE_OFF) {
*off = 1;
return (NULL);
}
return (&co->offset);
*off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW);
return (&cp->offset);
}
/* Set pane as on. */
void
control_set_pane_on(struct client *c, struct window_pane *wp)
{
struct control_offset *co;
struct control_pane *cp;
co = control_get_offset(c, wp);
if (co != NULL) {
co->flags &= ~CONTROL_OFFSET_OFF;
memcpy(&co->offset, &wp->offset, sizeof co->offset);
cp = control_get_pane(c, wp);
if (cp != NULL) {
cp->flags &= ~CONTROL_PANE_OFF;
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
}
}
@ -140,10 +211,27 @@ control_set_pane_on(struct client *c, struct window_pane *wp)
void
control_set_pane_off(struct client *c, struct window_pane *wp)
{
struct control_offset *co;
struct control_pane *cp;
co = control_add_offset(c, wp);
co->flags |= CONTROL_OFFSET_OFF;
cp = control_add_pane(c, wp);
cp->flags |= CONTROL_PANE_OFF;
}
/* Write a line. */
static void
control_vwrite(struct client *c, const char *fmt, va_list ap)
{
struct control_state *cs = c->control_state;
char *s;
xvasprintf(&s, fmt, ap);
log_debug("%s: %s: writing line: %s", __func__, c->name, s);
bufferevent_write(cs->write_event, s, strlen(s));
bufferevent_write(cs->write_event, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
free(s);
}
/* Write a line. */
@ -151,16 +239,25 @@ void
control_write(struct client *c, const char *fmt, ...)
{
struct control_state *cs = c->control_state;
struct control_block *cb;
va_list ap;
char *s;
va_start(ap, fmt);
xvasprintf(&s, fmt, ap);
va_end(ap);
bufferevent_write(cs->write_event, s, strlen(s));
bufferevent_write(cs->write_event, "\n", 1);
free(s);
if (TAILQ_EMPTY(&cs->all_blocks)) {
control_vwrite(c, fmt, ap);
va_end(ap);
return;
}
cb = xcalloc(1, sizeof *cb);
xvasprintf(&cb->line, fmt, ap);
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
bufferevent_enable(cs->write_event, EV_WRITE);
va_end(ap);
}
/* Write output from a pane. */
@ -168,42 +265,50 @@ void
control_write_output(struct client *c, struct window_pane *wp)
{
struct control_state *cs = c->control_state;
struct control_offset *co;
struct evbuffer *message;
u_char *new_data;
size_t new_size, i;
struct control_pane *cp;
struct control_block *cb;
size_t new_size;
if (c->flags & CLIENT_CONTROL_NOOUTPUT)
return;
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
return;
co = control_add_offset(c, wp);
if (co->flags & CONTROL_OFFSET_OFF) {
window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1);
if (c->flags & CONTROL_IGNORE_FLAGS) {
cp = control_get_pane(c, wp);
if (cp != NULL)
goto ignore;
return;
}
new_data = window_pane_get_new_data(wp, &co->offset, &new_size);
cp = control_add_pane(c, wp);
if (cp->flags & CONTROL_PANE_OFF)
goto ignore;
window_pane_get_new_data(wp, &cp->queued, &new_size);
if (new_size == 0)
return;
window_pane_update_used_data(wp, &cp->queued, new_size);
message = evbuffer_new();
if (message == NULL)
fatalx("out of memory");
evbuffer_add_printf(message, "%%output %%%u ", wp->id);
cb = xcalloc(1, sizeof *cb);
cb->size = new_size;
TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
for (i = 0; i < new_size; i++) {
if (new_data[i] < ' ' || new_data[i] == '\\')
evbuffer_add_printf(message, "\\%03o", new_data[i]);
else
evbuffer_add_printf(message, "%c", new_data[i]);
TAILQ_INSERT_TAIL(&cp->blocks, cb, entry);
log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name,
cb->size, wp->id);
if (!cp->pending_flag) {
log_debug("%s: %s: %%%u now pending", __func__, c->name,
wp->id);
TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry);
cp->pending_flag = 1;
cs->pending_count++;
}
evbuffer_add(message, "\n", 1);
bufferevent_enable(cs->write_event, EV_WRITE);
return;
bufferevent_write_buffer(cs->write_event, message);
evbuffer_free(message);
window_pane_update_used_data(wp, &co->offset, new_size, 1);
ignore:
log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id);
window_pane_update_used_data(wp, &cp->offset, SIZE_MAX);
window_pane_update_used_data(wp, &cp->queued, SIZE_MAX);
}
/* Control client error callback. */
@ -246,8 +351,8 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
if (line == NULL)
break;
log_debug("%s: %s", __func__, line);
if (*line == '\0') { /* empty line exit */
log_debug("%s: %s: %s", __func__, c->name, line);
if (*line == '\0') { /* empty line detach */
free(line);
c->flags |= CLIENT_EXIT;
break;
@ -263,6 +368,168 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
}
}
/* Does this control client have outstanding data to write? */
int
control_all_done(struct client *c)
{
struct control_state *cs = c->control_state;
if (!TAILQ_EMPTY(&cs->all_blocks))
return (0);
return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
}
/* Flush all blocks until output. */
static void
control_flush_all_blocks(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_block *cb, *cb1;
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) {
if (cb->size != 0)
break;
log_debug("%s: %s: flushing line: %s", __func__, c->name,
cb->line);
bufferevent_write(cs->write_event, cb->line, strlen(cb->line));
bufferevent_write(cs->write_event, "\n", 1);
control_free_block(cs, cb);
}
}
/* Append data to buffer. */
static struct evbuffer *
control_append_data(struct control_pane *cp, struct evbuffer *message,
struct window_pane *wp, size_t size)
{
u_char *new_data;
size_t new_size;
u_int i;
if (message == NULL) {
message = evbuffer_new();
if (message == NULL)
fatalx("out of memory");
evbuffer_add_printf(message, "%%output %%%u ", wp->id);
}
new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
if (new_size < size)
fatalx("not enough data: %zu < %zu", new_size, size);
for (i = 0; i < size; i++) {
if (new_data[i] < ' ' || new_data[i] == '\\')
evbuffer_add_printf(message, "\\%03o", new_data[i]);
else
evbuffer_add_printf(message, "%c", new_data[i]);
}
window_pane_update_used_data(wp, &cp->offset, size);
return (message);
}
/* Write buffer. */
static void
control_write_data(struct client *c, struct evbuffer *message)
{
struct control_state *cs = c->control_state;
log_debug("%s: %s: %.*s", __func__, c->name,
(int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message));
evbuffer_add(message, "\n", 1);
bufferevent_write_buffer(cs->write_event, message);
evbuffer_free(message);
}
/* Write output to client. */
static int
control_write_pending(struct client *c, struct control_pane *cp, size_t limit)
{
struct control_state *cs = c->control_state;
struct session *s = c->session;
struct window_pane *wp = NULL;
struct evbuffer *message = NULL;
size_t used = 0, size;
struct control_block *cb, *cb1;
if (s == NULL ||
(wp = window_pane_find_by_id(cp->pane)) == NULL ||
winlink_find_by_window(&s->windows, wp->window) == NULL) {
TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1)
control_free_block(cs, cb);
control_flush_all_blocks(c);
return (0);
}
while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
cb = TAILQ_FIRST(&cp->blocks);
log_debug("%s: %s: output block %zu for %%%u (used %zu/%zu)",
__func__, c->name, cb->size, cp->pane, used, limit);
size = cb->size;
if (size > limit - used)
size = limit - used;
used += size;
message = control_append_data(cp, message, wp, size);
cb->size -= size;
if (cb->size == 0) {
TAILQ_REMOVE(&cp->blocks, cb, entry);
control_free_block(cs, cb);
cb = TAILQ_FIRST(&cs->all_blocks);
if (cb != NULL && cb->size == 0) {
if (wp != NULL && message != NULL) {
control_write_data(c, message);
message = NULL;
}
control_flush_all_blocks(c);
}
}
}
if (message != NULL)
control_write_data(c, message);
return (!TAILQ_EMPTY(&cp->blocks));
}
/* Control client write callback. */
static void
control_write_callback(__unused struct bufferevent *bufev, void *data)
{
struct client *c = data;
struct control_state *cs = c->control_state;
struct control_pane *cp, *cp1;
struct evbuffer *evb = cs->write_event->output;
size_t space, limit;
control_flush_all_blocks(c);
while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) {
if (cs->pending_count == 0)
break;
space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb);
log_debug("%s: %s: %zu bytes available, %u panes", __func__,
c->name, space, cs->pending_count);
limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */
if (limit < CONTROL_WRITE_MINIMUM)
limit = CONTROL_WRITE_MINIMUM;
TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) {
if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH)
break;
if (control_write_pending(c, cp, limit))
continue;
TAILQ_REMOVE(&cs->pending_list, cp, pending_entry);
cp->pending_flag = 0;
cs->pending_count--;
}
}
if (EVBUFFER_LENGTH(evb) == 0)
bufferevent_disable(cs->write_event, EV_WRITE);
}
/* Initialize for control mode. */
void
control_start(struct client *c)
@ -277,22 +544,43 @@ control_start(struct client *c)
setblocking(c->fd, 0);
cs = c->control_state = xcalloc(1, sizeof *cs);
RB_INIT(&cs->offsets);
RB_INIT(&cs->panes);
TAILQ_INIT(&cs->pending_list);
TAILQ_INIT(&cs->all_blocks);
cs->read_event = bufferevent_new(c->fd, control_read_callback, NULL,
control_error_callback, c);
cs->read_event = bufferevent_new(c->fd, control_read_callback,
control_write_callback, control_error_callback, c);
bufferevent_enable(cs->read_event, EV_READ);
if (c->flags & CLIENT_CONTROLCONTROL)
cs->write_event = cs->read_event;
else {
cs->write_event = bufferevent_new(c->out_fd, NULL, NULL,
control_error_callback, c);
cs->write_event = bufferevent_new(c->out_fd, NULL,
control_write_callback, control_error_callback, c);
}
bufferevent_enable(cs->write_event, EV_WRITE);
bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
0);
if (c->flags & CLIENT_CONTROLCONTROL)
control_write(c, "\033P1000p");
if (c->flags & CLIENT_CONTROLCONTROL) {
bufferevent_write(cs->write_event, "\033P1000p", 7);
bufferevent_enable(cs->write_event, EV_WRITE);
}
}
/* Flush all output for a client that is detaching. */
void
control_flush(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_pane *cp;
struct control_block *cb, *cb1;
RB_FOREACH(cp, control_panes, &cs->panes) {
TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
TAILQ_REMOVE(&cp->blocks, cb, entry);
control_free_block(cs, cb);
}
}
}
/* Stop control mode. */
@ -300,11 +588,15 @@ void
control_stop(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_block *cb, *cb1;
if (~c->flags & CLIENT_CONTROLCONTROL)
bufferevent_free(cs->write_event);
bufferevent_free(cs->read_event);
control_free_offsets(c);
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
control_reset_offsets(c);
free(cs);
}