summaryrefslogtreecommitdiffstats
path: root/control.c
diff options
context:
space:
mode:
authornicm <nicm>2020-06-01 09:43:00 +0000
committernicm <nicm>2020-06-01 09:43:00 +0000
commita54a88edd6fd893d4370feb9f9136e13096b891c (patch)
tree1e42bf42d42c31e1ef3c059aaf1ae25ae6ef3bd5 /control.c
parent175e45005f4572b19a4aa34094f9a8c69ced5475 (diff)
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.
Diffstat (limited to 'control.c')
-rw-r--r--control.c476
1 files changed, 384 insertions, 92 deletions
diff --git a/control.c b/control.c
index 7e1a869b..c1e0b7eb 100644
--- a/control.c
+++ b/control.c
@@ -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;
+
+ TAILQ_ENTRY(control_block) entry;
+ TAILQ_ENTRY(control_block) all_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
- struct window_pane_offset offset;
- int flags;
-#define CONTROL_OFFSET_OFF 0x1
+ int pending_flag;
+ TAILQ_ENTRY(control_pane) pending_entry;
- RB_ENTRY(control_offset) 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;
+
+ TAILQ_HEAD(, control_pane) pending_list;
+ u_int pending_count;
- struct bufferevent *read_event;
- struct bufferevent *write_event;
+ 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;
+
+ cp = control_get_pane(c, wp);
+ if (cp != NULL)
+ return (cp);
+
+ cp = xcalloc(1, sizeof *cp);
+ cp->pane = wp->id;
+ RB_INSERT(control_panes, &cs->panes, cp);
- co = control_get_offset(c, wp);
- if (co != NULL)
- return (co);
+ memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
+ memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
+ TAILQ_INIT(&cp->blocks);
- 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);
+ 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,70 +211,104 @@ 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. */
-void
-control_write(struct client *c, const char *fmt, ...)
+static void
+control_vwrite(struct client *c, const char *fmt, va_list ap)
{
struct control_state *cs = c->control_state;
- va_list ap;
char *s;
- va_start(ap, fmt);
xvasprintf(&s, fmt, ap);
- va_end(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. */
+void
+control_write(struct client *c, const char *fmt, ...)
+{
+ struct control_state *cs = c->control_state;
+ struct control_block *cb;
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ 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. */
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;
-
- message = evbuffer_new();
- if (message == NULL)
- fatalx("out of memory");
- evbuffer_add_printf(message, "%%output %%%u ", wp->id);
-
- 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]);
+ window_pane_update_used_data(wp, &cp->queued, new_size);
+
+ cb = xcalloc(1, sizeof *cb);
+ cb->size = new_size;
+ TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
+
+ 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_write_buffer(cs->write_event, message);
- evbuffer_free(message);
+ bufferevent_enable(cs->write_event, EV_WRITE);
+ return;
- 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);
}