For example, try this which rate limits input from each pane down to 19200 bps.
You'll notice that interactive commands like "top" will noticeably pause. But it will have no real impact on a fast output command such as "yes" - this is because scrolling is expensive so "yes" can do a lot of damage with very little output, especially on a small terminal. Bumping the rate in window.c from 19200 up to a more reasonable 115200 or so will reduce the effect even further. And of course it eats CPU because it has to spin through the read callback. Could maybe mitigate this with a timer if it was worth it though. Rate limiting output to the tty has different problems, for example you are then limiting tmux itself as well, and a fast pane can still wash out slower. Index: cmd-paste-buffer.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/cmd-paste-buffer.c,v retrieving revision 1.16 diff -u -p -r1.16 cmd-paste-buffer.c --- cmd-paste-buffer.c 4 Jan 2011 00:42:47 -0000 1.16 +++ cmd-paste-buffer.c 19 Dec 2011 22:59:49 -0000 @@ -112,11 +112,11 @@ cmd_paste_buffer_filter( seplen = strlen(sep); while ((lf = memchr(data, '\n', end - data)) != NULL) { if (lf != data) - bufferevent_write(wp->event, data, lf - data); - bufferevent_write(wp->event, sep, seplen); + window_pane_write(wp, data, lf - data); + window_pane_write(wp, sep, seplen); data = lf + 1; } if (end != data) - bufferevent_write(wp->event, data, end - data); + window_pane_write(wp, data, end - data); } Index: cmd-pipe-pane.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/cmd-pipe-pane.c,v retrieving revision 1.21 diff -u -p -r1.21 cmd-pipe-pane.c --- cmd-pipe-pane.c 27 Oct 2011 22:41:03 -0000 1.21 +++ cmd-pipe-pane.c 19 Dec 2011 22:58:56 -0000 @@ -120,7 +120,7 @@ cmd_pipe_pane_exec(struct cmd *self, str close(pipe_fd[1]); wp->pipe_fd = pipe_fd[0]; - wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); + wp->pipe_off = EVBUFFER_LENGTH(wp->read_buf); wp->pipe_event = bufferevent_new(wp->pipe_fd, NULL, NULL, cmd_pipe_pane_error_callback, wp); Index: input-keys.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/input-keys.c,v retrieving revision 1.23 diff -u -p -r1.23 input-keys.c --- input-keys.c 30 Jul 2011 18:01:26 -0000 1.23 +++ input-keys.c 19 Dec 2011 22:59:55 -0000 @@ -151,9 +151,9 @@ input_key(struct window_pane *wp, int ke */ if (key != KEYC_NONE && (key & ~KEYC_ESCAPE) < 0x100) { if (key & KEYC_ESCAPE) - bufferevent_write(wp->event, "\033", 1); + window_pane_write(wp, "\033", 1); ch = key & ~KEYC_ESCAPE; - bufferevent_write(wp->event, &ch, 1); + window_pane_write(wp, &ch, 1); return; } @@ -163,7 +163,7 @@ input_key(struct window_pane *wp, int ke */ if (options_get_number(&wp->window->options, "xterm-keys")) { if ((out = xterm_keys_lookup(key)) != NULL) { - bufferevent_write(wp->event, out, strlen(out)); + window_pane_write(wp, out, strlen(out)); xfree(out); return; } @@ -194,8 +194,8 @@ input_key(struct window_pane *wp, int ke /* Prefix a \033 for escape. */ if (key & KEYC_ESCAPE) - bufferevent_write(wp->event, "\033", 1); - bufferevent_write(wp->event, ike->data, dlen); + window_pane_write(wp, "\033", 1); + window_pane_write(wp, ike->data, dlen); } /* Translate mouse and output. */ @@ -220,7 +220,7 @@ input_mouse(struct window_pane *wp, stru buf[len++] = m->x + 33; buf[len++] = m->y + 33; } - bufferevent_write(wp->event, buf, len); + window_pane_write(wp, buf, len); } else if ((m->b & MOUSE_BUTTON) != MOUSE_2) { value = options_get_number(&wp->window->options, "mode-mouse"); if (value == 1 && Index: input.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/input.c,v retrieving revision 1.41 diff -u -p -r1.41 input.c --- input.c 23 Oct 2011 10:16:14 -0000 1.41 +++ input.c 19 Dec 2011 23:00:10 -0000 @@ -696,7 +696,7 @@ input_parse(struct window_pane *wp) { struct input_ctx *ictx = &wp->ictx; const struct input_transition *itr; - struct evbuffer *evb = wp->event->input; + struct evbuffer *evb = wp->read_buf; u_char *buf; size_t len, off; @@ -819,7 +819,7 @@ input_reply(struct input_ctx *ictx, cons vasprintf(&reply, fmt, ap); va_end(ap); - bufferevent_write(ictx->wp->event, reply, strlen(reply)); + window_pane_write(ictx->wp, reply, strlen(reply)); xfree(reply); } Index: server-fn.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/server-fn.c,v retrieving revision 1.52 diff -u -p -r1.52 server-fn.c --- server-fn.c 23 Oct 2011 10:16:14 -0000 1.52 +++ server-fn.c 19 Dec 2011 23:00:32 -0000 @@ -337,8 +337,10 @@ server_destroy_pane(struct window_pane * old_fd = wp->fd; if (wp->fd != -1) { + event_del(&wp->read_ev); + if (wp->flags & PANE_WRITING) + event_del(&wp->write_ev); close(wp->fd); - bufferevent_free(wp->event); wp->fd = -1; } Index: tmux.h =================================================================== RCS file: /cvs/src/usr.bin/tmux/tmux.h,v retrieving revision 1.301 diff -u -p -r1.301 tmux.h --- tmux.h 9 Dec 2011 16:28:18 -0000 1.301 +++ tmux.h 19 Dec 2011 23:34:55 -0000 @@ -814,6 +814,7 @@ struct window_pane { int flags; #define PANE_REDRAW 0x1 +#define PANE_WRITING 0x2 char *cmd; char *shell; @@ -823,7 +824,11 @@ struct window_pane { char tty[TTY_NAME_MAX]; int fd; - struct bufferevent *event; + struct event read_ev; + struct evbuffer* read_buf; + struct timeval read_last; + struct event write_ev; + struct evbuffer* write_buf; struct input_ctx ictx; @@ -1933,6 +1938,8 @@ void window_set_active_pane(struct win struct window_pane *window_add_pane(struct window *, u_int); void window_resize(struct window *, u_int, u_int); void window_remove_pane(struct window *, struct window_pane *); +void window_pane_queue_write(struct window_pane *); +void window_pane_write(struct window_pane *, const void *, size_t); struct window_pane *window_pane_at_index(struct window *, u_int); struct window_pane *window_pane_next_by_number(struct window *, struct window_pane *, u_int); Index: window-copy.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/window-copy.c,v retrieving revision 1.76 diff -u -p -r1.76 window-copy.c --- window-copy.c 4 Dec 2011 16:18:01 -0000 1.76 +++ window-copy.c 19 Dec 2011 23:01:30 -0000 @@ -174,8 +174,11 @@ window_copy_init(struct window_pane *wp) data->searchtype = WINDOW_COPY_OFF; data->searchstr = NULL; - if (wp->fd != -1) - bufferevent_disable(wp->event, EV_READ|EV_WRITE); + if (wp->fd != -1) { + event_del(&wp->read_ev); + if (wp->flags & PANE_WRITING) + event_del(&wp->write_ev); + } data->jumptype = WINDOW_COPY_OFF; data->jumpchar = '\0'; @@ -237,8 +240,11 @@ window_copy_free(struct window_pane *wp) { struct window_copy_mode_data *data = wp->modedata; - if (wp->fd != -1) - bufferevent_enable(wp->event, EV_READ|EV_WRITE); + if (wp->fd != -1) { + event_add(&wp->read_ev, NULL); + if (wp->flags & PANE_WRITING) + event_add(&wp->write_ev, NULL); + } if (data->searchstr != NULL) xfree(data->searchstr); Index: window.c =================================================================== RCS file: /cvs/src/usr.bin/tmux/window.c,v retrieving revision 1.69 diff -u -p -r1.69 window.c --- window.c 15 Nov 2011 23:19:51 -0000 1.69 +++ window.c 19 Dec 2011 23:33:59 -0000 @@ -60,8 +60,8 @@ struct windows windows; struct window_pane_tree all_window_panes; u_int next_window_pane; -void window_pane_read_callback(struct bufferevent *, void *); -void window_pane_error_callback(struct bufferevent *, short, void *); +void window_pane_read_callback(int, short, void *); +void window_pane_write_callback(int, short, void *); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); @@ -579,7 +579,6 @@ window_pane_create(struct window *w, u_i wp->cwd = NULL; wp->fd = -1; - wp->event = NULL; wp->mode = NULL; @@ -611,8 +610,10 @@ window_pane_destroy(struct window_pane * window_pane_reset_mode(wp); if (wp->fd != -1) { + event_del(&wp->read_ev); + if (wp->flags & PANE_WRITING) + event_del(&wp->write_ev); close(wp->fd); - bufferevent_free(wp->event); } input_free(wp); @@ -647,8 +648,10 @@ window_pane_spawn(struct window_pane *wp struct termios tio2; if (wp->fd != -1) { + event_del(&wp->read_ev); + if (wp->flags & PANE_WRITING) + event_del(&wp->write_ev); close(wp->fd); - bufferevent_free(wp->event); } if (cmd != NULL) { if (wp->cmd != NULL) @@ -719,30 +722,81 @@ window_pane_spawn(struct window_pane *wp setblocking(wp->fd, 0); - wp->event = bufferevent_new(wp->fd, - window_pane_read_callback, NULL, window_pane_error_callback, wp); - bufferevent_enable(wp->event, EV_READ|EV_WRITE); + event_set(&wp->read_ev, + wp->fd, EV_READ|EV_PERSIST, window_pane_read_callback, wp); + if ((wp->read_buf = evbuffer_new ()) == NULL) + fatalx("evbuffer_new failed"); + + event_set(&wp->write_ev, + wp->fd, EV_WRITE|EV_PERSIST, window_pane_write_callback, wp); + if ((wp->write_buf = evbuffer_new ()) == NULL) + fatalx("evbuffer_new failed"); + + if (gettimeofday (&wp->read_last, NULL) != 0) + fatal("gettimeofday failed"); + event_add(&wp->read_ev, NULL); return (0); } +void +window_pane_write(struct window_pane *wp, const void *buf, size_t len) +{ + evbuffer_add(wp->write_buf, buf, len); + window_pane_queue_write(wp); +} + +void +window_pane_queue_write(struct window_pane *wp) +{ + if (wp->flags & PANE_WRITING) + return; + event_add(&wp->write_ev, NULL); + wp->flags |= PANE_WRITING; +} + /* ARGSUSED */ void -window_pane_read_callback(unused struct bufferevent *bufev, void *data) +window_pane_read_callback(unused int fd, unused short events, void* data) { struct window_pane *wp = data; + struct timeval tv; + struct timeval tvdiff; + double last; + size_t size; char *new_data; size_t new_size; - new_size = EVBUFFER_LENGTH(wp->event->input) - wp->pipe_off; + if (gettimeofday(&tv, NULL) != 0) + fatal("gettimeofday failed"); + + timersub(&tv, &wp->read_last, &tvdiff); + last = tvdiff.tv_sec + (tvdiff.tv_usec / 1e6); + +#define BYTES_MAX 19200 + size = BYTES_MAX * last; + log_debug("time %g, can read at most %zu bytes (of %zu)", + last, size, EVBUFFER_LENGTH(wp->read_buf)); + if (size > BYTES_MAX) + size = BYTES_MAX; + if (size == 0) + return; + + memcpy(&wp->read_last, &tv, sizeof wp->read_last); + if (evbuffer_read(wp->read_buf, wp->fd, size) <= 0) { + server_destroy_pane(wp); + return; + } + + new_size = EVBUFFER_LENGTH(wp->read_buf) - wp->pipe_off; if (wp->pipe_fd != -1 && new_size > 0) { - new_data = EVBUFFER_DATA(wp->event->input); + new_data = EVBUFFER_DATA(wp->read_buf); bufferevent_write(wp->pipe_event, new_data, new_size); } input_parse(wp); - wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); + wp->pipe_off = EVBUFFER_LENGTH(wp->read_buf); /* * If we get here, we're not outputting anymore, so set the silence @@ -755,12 +809,19 @@ window_pane_read_callback(unused struct /* ARGSUSED */ void -window_pane_error_callback( - unused struct bufferevent *bufev, unused short what, void *data) +window_pane_write_callback(unused int fd, unused short events, void* data) { struct window_pane *wp = data; - server_destroy_pane(wp); + if (evbuffer_write(wp->write_buf, wp->fd) <= 0) { + server_destroy_pane(wp); + return; + } + + if (EVBUFFER_LENGTH(wp->write_buf) == 0) { + event_del(&wp->write_ev); + wp->flags &= ~PANE_WRITING; + } } void On Mon, Dec 19, 2011 at 11:20:29PM +0000, Nicholas Marriott wrote: > This is actually quite a hard problem. > > The issue is that it is difficult on a fast machine to rate limit vast, > continuous amounts of data quickly enough and to a slow enough rate that > tmux can remain responsive, without affecting interactive programs which > need to send a lot of data quickly in bursts > > If tmux takes too long to start rate limiting, then a whole lot of stuff > is already in the terminal buffers and you have to wait. If it is too > aggressive, you'll see full screen programs pause halfway through > drawing the screen. > > > > On Mon, Dec 19, 2011 at 11:03:59AM -0800, Robin Lee Powell wrote: > > > > I have literally been watching the output of "perl -d:Trace" for > > more than 10 minutes, waiting to be granted control of my terminal > > again. :( I can't do any tmux commands at all. > > > > -Robin > > > > -- > > http://singinst.org/ : Our last, best hope for a fantastic future. > > Lojban (http://www.lojban.org/): The language in which "this parrot > > is dead" is "ti poi spitaki cu morsi", but "this sentence is false" > > is "na nei". My personal page: http://www.digitalkingdom.org/rlp/ > > > > ------------------------------------------------------------------------------ > > Learn Windows Azure Live! Tuesday, Dec 13, 2011 > > Microsoft is holding a special Learn Windows Azure training event for > > developers. It will provide a great way to learn Windows Azure and what it > > provides. You can attend the event by watching it streamed LIVE online. > > Learn more at http://p.sf.net/sfu/ms-windowsazure > > _______________________________________________ > > tmux-users mailing list > > tmux-users@lists.sourceforge.net > > https://lists.sourceforge.net/lists/listinfo/tmux-users ------------------------------------------------------------------------------ Write once. Port to many. Get the SDK and tools to simplify cross-platform app development. Create new or port existing apps to sell to consumers worldwide. Explore the Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join http://p.sf.net/sfu/intel-appdev _______________________________________________ tmux-users mailing list tmux-users@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/tmux-users