mirror of
https://github.com/tmux/tmux.git
synced 2024-11-17 18:08:51 +00:00
a54a88edd6
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.
603 lines
15 KiB
C
603 lines
15 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
* Copyright (c) 2012 George Nachman <tmux@georgester.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <event.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/*
|
|
* 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
|
|
|
|
int pending_flag;
|
|
TAILQ_ENTRY(control_pane) pending_entry;
|
|
|
|
TAILQ_HEAD(, control_block) blocks;
|
|
|
|
RB_ENTRY(control_pane) entry;
|
|
};
|
|
RB_HEAD(control_panes, control_pane);
|
|
|
|
/* Control client state. */
|
|
struct control_state {
|
|
struct control_panes panes;
|
|
|
|
TAILQ_HEAD(, control_pane) pending_list;
|
|
u_int pending_count;
|
|
|
|
TAILQ_HEAD(, control_block) all_blocks;
|
|
|
|
struct bufferevent *read_event;
|
|
struct bufferevent *write_event;
|
|
};
|
|
|
|
/* 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_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
|
|
{
|
|
if (cp1->pane < cp2->pane)
|
|
return (-1);
|
|
if (cp1->pane > cp2->pane)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
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_pane *
|
|
control_get_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_pane cp = { .pane = wp->id };
|
|
|
|
return (RB_FIND(control_panes, &cs->panes, &cp));
|
|
}
|
|
|
|
/* Add pane offsets for this client. */
|
|
static struct control_pane *
|
|
control_add_pane(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
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);
|
|
|
|
memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
|
|
memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
|
|
TAILQ_INIT(&cp->blocks);
|
|
|
|
return (cp);
|
|
}
|
|
|
|
/* Reset control offsets. */
|
|
void
|
|
control_reset_offsets(struct client *c)
|
|
{
|
|
struct control_state *cs = c->control_state;
|
|
struct control_pane *cp, *cp1;
|
|
|
|
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_state *cs = c->control_state;
|
|
struct control_pane *cp;
|
|
|
|
if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
|
|
*off = 0;
|
|
return (NULL);
|
|
}
|
|
|
|
cp = control_get_pane(c, wp);
|
|
if (cp == NULL) {
|
|
*off = 0;
|
|
return (NULL);
|
|
}
|
|
if (cp->flags & CONTROL_PANE_OFF) {
|
|
*off = 1;
|
|
return (NULL);
|
|
}
|
|
*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_pane *cp;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* Set pane as off. */
|
|
void
|
|
control_set_pane_off(struct client *c, struct window_pane *wp)
|
|
{
|
|
struct control_pane *cp;
|
|
|
|
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. */
|
|
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_pane *cp;
|
|
struct control_block *cb;
|
|
size_t new_size;
|
|
|
|
if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
|
|
return;
|
|
|
|
if (c->flags & CONTROL_IGNORE_FLAGS) {
|
|
cp = control_get_pane(c, wp);
|
|
if (cp != NULL)
|
|
goto ignore;
|
|
return;
|
|
}
|
|
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);
|
|
|
|
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++;
|
|
}
|
|
bufferevent_enable(cs->write_event, EV_WRITE);
|
|
return;
|
|
|
|
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. */
|
|
static enum cmd_retval
|
|
control_error(struct cmdq_item *item, void *data)
|
|
{
|
|
struct client *c = cmdq_get_client(item);
|
|
char *error = data;
|
|
|
|
cmdq_guard(item, "begin", 1);
|
|
control_write(c, "parse error: %s", error);
|
|
cmdq_guard(item, "error", 1);
|
|
|
|
free(error);
|
|
return (CMD_RETURN_NORMAL);
|
|
}
|
|
|
|
/* Control client error callback. */
|
|
static void
|
|
control_error_callback(__unused struct bufferevent *bufev,
|
|
__unused short what, void *data)
|
|
{
|
|
struct client *c = data;
|
|
|
|
c->flags |= CLIENT_EXIT;
|
|
}
|
|
|
|
/* Control client input callback. Read lines and fire commands. */
|
|
static void
|
|
control_read_callback(__unused struct bufferevent *bufev, void *data)
|
|
{
|
|
struct client *c = data;
|
|
struct control_state *cs = c->control_state;
|
|
struct evbuffer *buffer = cs->read_event->input;
|
|
char *line, *error;
|
|
struct cmdq_state *state;
|
|
enum cmd_parse_status status;
|
|
|
|
for (;;) {
|
|
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
|
|
if (line == NULL)
|
|
break;
|
|
log_debug("%s: %s: %s", __func__, c->name, line);
|
|
if (*line == '\0') { /* empty line detach */
|
|
free(line);
|
|
c->flags |= CLIENT_EXIT;
|
|
break;
|
|
}
|
|
|
|
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
|
|
status = cmd_parse_and_append(line, NULL, c, state, &error);
|
|
if (status == CMD_PARSE_ERROR)
|
|
cmdq_append(c, cmdq_get_callback(control_error, error));
|
|
cmdq_free_state(state);
|
|
|
|
free(line);
|
|
}
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
struct control_state *cs;
|
|
|
|
if (c->flags & CLIENT_CONTROLCONTROL) {
|
|
close(c->out_fd);
|
|
c->out_fd = -1;
|
|
} else
|
|
setblocking(c->out_fd, 0);
|
|
setblocking(c->fd, 0);
|
|
|
|
cs = c->control_state = xcalloc(1, sizeof *cs);
|
|
RB_INIT(&cs->panes);
|
|
TAILQ_INIT(&cs->pending_list);
|
|
TAILQ_INIT(&cs->all_blocks);
|
|
|
|
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,
|
|
control_write_callback, control_error_callback, c);
|
|
}
|
|
bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
|
|
0);
|
|
|
|
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. */
|
|
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);
|
|
|
|
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
|
|
control_free_block(cs, cb);
|
|
control_reset_offsets(c);
|
|
|
|
free(cs);
|
|
}
|