mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Proper buffer management between terminal and backend.
The return value of term_data() is used as the return value from the GUI-terminal versions of the Seat output method, which means backends will take it to be the amount of standard-output data currently buffered, and exert back-pressure on the remote peer if it gets too big (e.g. by ceasing to extend the window in that particular SSH-2 channel). Historically, as a comment in term_data() explained, we always just returned 0 from that function, on the basis that we were processing all the terminal data through our terminal emulation code immediately, and never retained any of it in the buffer at all. If the terminal emulation code were to start running slowly, then it would slow down the _whole_ PuTTY system, due to single-threadedness, and back-pressure of a sort would be exerted on the remote by it simply failing to get round to reading from the network socket. But by the time we got back to the top level of term_data(), we'd have finished reading all the data we had, so it was still appropriate to return 0. That comment is still correct if you're thinking about the limiting factor on terminal data processing being the CPU usage in term_out(). But now that's no longer the whole story, because sometimes we leave data in term->inbuf without having processed it: during drag-selects in the terminal window, and (just introduced) while waiting for the response to a pending window resize request. For both those reasons, we _don't_ always have a buffer size of zero when we return from term_data(). So now that hole in our buffer size management is filled in: term_data() returns the true size of the remaining unprocessed terminal output, so that back-pressure will be exerted if the terminal is currently not consuming it. And when processing resumes and we start to clear our backlog, we call backend_unthrottle to let the backend know it can relax the back-pressure if necessary.
This commit is contained in:
parent
cfc9023616
commit
bc91a39670
7
putty.h
7
putty.h
@ -1593,6 +1593,11 @@ struct TermWinVtable {
|
||||
* object, because that doesn't happen until term_init
|
||||
* returns. */
|
||||
void (*palette_get_overrides)(TermWin *, Terminal *);
|
||||
|
||||
/* Notify the front end that the terminal's buffer of unprocessed
|
||||
* output has reduced. (Front ends will likely pass this straight
|
||||
* on to backend_unthrottle.) */
|
||||
void (*unthrottle)(TermWin *, size_t bufsize);
|
||||
};
|
||||
|
||||
static inline bool win_setup_draw_ctx(TermWin *win)
|
||||
@ -1649,6 +1654,8 @@ static inline void win_palette_set(
|
||||
{ win->vt->palette_set(win, start, ncolours, colours); }
|
||||
static inline void win_palette_get_overrides(TermWin *win, Terminal *term)
|
||||
{ win->vt->palette_get_overrides(win, term); }
|
||||
static inline void win_unthrottle(TermWin *win, size_t size)
|
||||
{ win->vt->unthrottle(win, size); }
|
||||
|
||||
/*
|
||||
* Global functions not specific to a connection instance.
|
||||
|
@ -98,7 +98,7 @@ static void deselect(Terminal *);
|
||||
static void term_print_finish(Terminal *);
|
||||
static void scroll(Terminal *, int, int, int, bool);
|
||||
static void parse_optionalrgb(optionalrgb *out, unsigned *values);
|
||||
static void term_added_data(Terminal *term);
|
||||
static void term_added_data(Terminal *term, bool);
|
||||
static void term_update_raw_mouse_mode(Terminal *term);
|
||||
static void term_out_cb(void *);
|
||||
|
||||
@ -3491,7 +3491,7 @@ static inline void term_keyinput_internal(
|
||||
int true_len = len >= 0 ? len : strlen(buf);
|
||||
|
||||
bufchain_add(&term->inbuf, buf, true_len);
|
||||
term_added_data(term);
|
||||
term_added_data(term, false);
|
||||
}
|
||||
if (interactive)
|
||||
term_bracketed_paste_stop(term);
|
||||
@ -3639,7 +3639,7 @@ unsigned long term_translate(
|
||||
* in-memory display. There's a big state machine in here to
|
||||
* process escape sequences...
|
||||
*/
|
||||
static void term_out(Terminal *term)
|
||||
static void term_out(Terminal *term, bool called_from_term_data)
|
||||
{
|
||||
unsigned long c;
|
||||
int unget;
|
||||
@ -5589,6 +5589,9 @@ static void term_out(Terminal *term)
|
||||
|
||||
bufchain_consume(&term->inbuf, nchars_used);
|
||||
|
||||
if (!called_from_term_data)
|
||||
win_unthrottle(term->win, bufchain_size(&term->inbuf));
|
||||
|
||||
term_print_flush(term);
|
||||
if (term->logflush && term->logctx)
|
||||
logflush(term->logctx);
|
||||
@ -5597,7 +5600,7 @@ static void term_out(Terminal *term)
|
||||
/* Wrapper on term_out with the right prototype to be a toplevel callback */
|
||||
void term_out_cb(void *ctx)
|
||||
{
|
||||
term_out((Terminal *)ctx);
|
||||
term_out((Terminal *)ctx, false);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -7292,7 +7295,7 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
|
||||
* should make sure to write any pending output if one has just
|
||||
* finished.
|
||||
*/
|
||||
term_out(term);
|
||||
term_out(term, false);
|
||||
term_schedule_update(term);
|
||||
}
|
||||
|
||||
@ -7605,15 +7608,15 @@ void term_lost_clipboard_ownership(Terminal *term, int clipboard)
|
||||
* should make sure to write any pending output if one has just
|
||||
* finished.
|
||||
*/
|
||||
term_out(term);
|
||||
term_out(term, false);
|
||||
}
|
||||
|
||||
static void term_added_data(Terminal *term)
|
||||
static void term_added_data(Terminal *term, bool called_from_term_data)
|
||||
{
|
||||
if (!term->in_term_out) {
|
||||
term->in_term_out = true;
|
||||
term_reset_cblink(term);
|
||||
term_out(term);
|
||||
term_out(term, called_from_term_data);
|
||||
term->in_term_out = false;
|
||||
}
|
||||
}
|
||||
@ -7621,28 +7624,8 @@ static void term_added_data(Terminal *term)
|
||||
size_t term_data(Terminal *term, const void *data, size_t len)
|
||||
{
|
||||
bufchain_add(&term->inbuf, data, len);
|
||||
term_added_data(term);
|
||||
|
||||
/*
|
||||
* term_out() always completely empties inbuf. Therefore,
|
||||
* there's no reason at all to return anything other than zero
|
||||
* from this function, because there _can't_ be a question of
|
||||
* the remote side needing to wait until term_out() has cleared
|
||||
* a backlog.
|
||||
*
|
||||
* This is a slightly suboptimal way to deal with SSH-2 - in
|
||||
* principle, the window mechanism would allow us to continue
|
||||
* to accept data on forwarded ports and X connections even
|
||||
* while the terminal processing was going slowly - but we
|
||||
* can't do the 100% right thing without moving the terminal
|
||||
* processing into a separate thread, and that might hurt
|
||||
* portability. So we manage stdout buffering the old SSH-1 way:
|
||||
* if the terminal processing goes slowly, the whole SSH
|
||||
* connection stops accepting data until it's ready.
|
||||
*
|
||||
* In practice, I can't imagine this causing serious trouble.
|
||||
*/
|
||||
return 0;
|
||||
term_added_data(term, true);
|
||||
return bufchain_size(&term->inbuf);
|
||||
}
|
||||
|
||||
void term_provide_logctx(Terminal *term, LogContext *logctx)
|
||||
|
@ -91,6 +91,7 @@ static void fuzz_set_zorder(TermWin *tw, bool top) {}
|
||||
static void fuzz_palette_set(TermWin *tw, unsigned start, unsigned ncolours,
|
||||
const rgb *colours) {}
|
||||
static void fuzz_palette_get_overrides(TermWin *tw, Terminal *term) {}
|
||||
static void fuzz_unthrottle(TermWin *tw, size_t size) {}
|
||||
|
||||
static const TermWinVtable fuzz_termwin_vt = {
|
||||
.setup_draw_ctx = fuzz_setup_draw_ctx,
|
||||
@ -115,6 +116,7 @@ static const TermWinVtable fuzz_termwin_vt = {
|
||||
.set_zorder = fuzz_set_zorder,
|
||||
.palette_set = fuzz_palette_set,
|
||||
.palette_get_overrides = fuzz_palette_get_overrides,
|
||||
.unthrottle = fuzz_unthrottle,
|
||||
};
|
||||
|
||||
void ldisc_send(Ldisc *ldisc, const void *buf, int len, bool interactive) {}
|
||||
|
@ -326,6 +326,13 @@ static size_t gtk_seat_output(Seat *seat, SeatOutputType type,
|
||||
return term_data(inst->term, data, len);
|
||||
}
|
||||
|
||||
static void gtkwin_unthrottle(TermWin *win, size_t bufsize)
|
||||
{
|
||||
GtkFrontend *inst = container_of(win, GtkFrontend, termwin);
|
||||
if (inst->backend)
|
||||
backend_unthrottle(inst->backend, bufsize);
|
||||
}
|
||||
|
||||
static bool gtk_seat_eof(Seat *seat)
|
||||
{
|
||||
/* GtkFrontend *inst = container_of(seat, GtkFrontend, seat); */
|
||||
@ -5156,6 +5163,7 @@ static const TermWinVtable gtk_termwin_vt = {
|
||||
.set_zorder = gtkwin_set_zorder,
|
||||
.palette_set = gtkwin_palette_set,
|
||||
.palette_get_overrides = gtkwin_palette_get_overrides,
|
||||
.unthrottle = gtkwin_unthrottle,
|
||||
};
|
||||
|
||||
void new_session_window(Conf *conf, const char *geometry_string)
|
||||
|
@ -259,6 +259,7 @@ static void wintw_move(TermWin *, int x, int y);
|
||||
static void wintw_set_zorder(TermWin *, bool top);
|
||||
static void wintw_palette_set(TermWin *, unsigned, unsigned, const rgb *);
|
||||
static void wintw_palette_get_overrides(TermWin *, Terminal *);
|
||||
static void wintw_unthrottle(TermWin *win, size_t bufsize);
|
||||
|
||||
static const TermWinVtable windows_termwin_vt = {
|
||||
.setup_draw_ctx = wintw_setup_draw_ctx,
|
||||
@ -284,6 +285,7 @@ static const TermWinVtable windows_termwin_vt = {
|
||||
.set_zorder = wintw_set_zorder,
|
||||
.palette_set = wintw_palette_set,
|
||||
.palette_get_overrides = wintw_palette_get_overrides,
|
||||
.unthrottle = wintw_unthrottle,
|
||||
};
|
||||
|
||||
static TermWin wintw[1];
|
||||
@ -5774,6 +5776,12 @@ static size_t win_seat_output(Seat *seat, SeatOutputType type,
|
||||
return term_data(term, data, len);
|
||||
}
|
||||
|
||||
static void wintw_unthrottle(TermWin *win, size_t bufsize)
|
||||
{
|
||||
if (backend)
|
||||
backend_unthrottle(backend, bufsize);
|
||||
}
|
||||
|
||||
static bool win_seat_eof(Seat *seat)
|
||||
{
|
||||
return true; /* do respond to incoming EOF with outgoing */
|
||||
|
Loading…
Reference in New Issue
Block a user