diff --git a/ssh.c b/ssh.c index 4d1737f0..bca83198 100644 --- a/ssh.c +++ b/ssh.c @@ -63,7 +63,29 @@ struct Ssh { int conn_throttle_count; int overall_bufsize; bool throttled_all; - bool frozen; + + /* + * logically_frozen is true if we're not currently _processing_ + * data from the SSH socket (e.g. because a higher layer has asked + * us not to due to ssh_throttle_conn). socket_frozen is true if + * we're not even _reading_ data from the socket (i.e. it should + * always match the value we last passed to sk_set_frozen). + * + * The two differ in that socket_frozen can also become + * temporarily true because of a large backlog in the in_raw + * bufchain, to force no further plug_receive events until the BPP + * input function has had a chance to run. (Some front ends, like + * GTK, can persistently call the network and never get round to + * the toplevel callbacks.) If we've stopped reading from the + * socket for that reason, we absolutely _do_ want to carry on + * processing our input bufchain, because that's the only way + * it'll ever get cleared! + * + * ssh_check_frozen() resets socket_frozen, and should be called + * whenever either of logically_frozen and the bufchain size + * changes. + */ + bool logically_frozen, socket_frozen; /* in case we find these out before we have a ConnectionLayer to tell */ int term_width, term_height; @@ -295,6 +317,29 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv, ssh_bpp_free(old_bpp); } +static void ssh_check_frozen(Ssh *ssh) +{ + if (!ssh->s) + return; + + bool prev_frozen = ssh->socket_frozen; + ssh->socket_frozen = (ssh->logically_frozen || + bufchain_size(&ssh->in_raw) > SSH_MAX_BACKLOG); + sk_set_frozen(ssh->s, ssh->socket_frozen); + if (prev_frozen && !ssh->socket_frozen && ssh->bpp) { + /* + * If we've just unfrozen, process any SSH connection data + * that was stashed in our queue while we were frozen. + */ + queue_idempotent_callback(&ssh->bpp->ic_in_raw); + } +} + +void ssh_conn_processed_data(Ssh *ssh) +{ + ssh_check_frozen(ssh); +} + static void ssh_bpp_output_raw_data_callback(void *vctx) { Ssh *ssh = (Ssh *)vctx; @@ -321,6 +366,8 @@ static void ssh_bpp_output_raw_data_callback(void *vctx) } } + ssh_check_frozen(ssh); + if (ssh->pending_close) { sk_close(ssh->s); ssh->s = NULL; @@ -536,8 +583,10 @@ static void ssh_receive(Plug *plug, int urgent, char *data, int len) 0, NULL, NULL, 0, NULL); bufchain_add(&ssh->in_raw, data, len); - if (!ssh->frozen && ssh->bpp) + if (!ssh->logically_frozen && ssh->bpp) queue_idempotent_callback(&ssh->bpp->ic_in_raw); + + ssh_check_frozen(ssh); } static void ssh_sent(Plug *plug, int bufsize) @@ -753,17 +802,8 @@ void ssh_throttle_conn(Ssh *ssh, int adjust) return; /* don't change current frozen state */ } - ssh->frozen = frozen; - - if (ssh->s) { - sk_set_frozen(ssh->s, frozen); - - /* - * Now process any SSH connection data that was stashed in our - * queue while we were frozen. - */ - queue_idempotent_callback(&ssh->bpp->ic_in_raw); - } + ssh->logically_frozen = frozen; + ssh_check_frozen(ssh); } /* diff --git a/ssh.h b/ssh.h index ef502834..309bde4d 100644 --- a/ssh.h +++ b/ssh.h @@ -378,6 +378,9 @@ void ssh_got_exitcode(Ssh *ssh, int status); void ssh_ldisc_update(Ssh *ssh); void ssh_got_fallback_cmd(Ssh *ssh); +/* Communications back to ssh.c from the BPP */ +void ssh_conn_processed_data(Ssh *ssh); + /* Functions to abort the connection, for various reasons. */ void ssh_remote_error(Ssh *ssh, const char *fmt, ...); void ssh_remote_eof(Ssh *ssh, const char *fmt, ...); diff --git a/sshcommon.c b/sshcommon.c index 89763e8e..7d23775d 100644 --- a/sshcommon.c +++ b/sshcommon.c @@ -822,7 +822,11 @@ void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text) static void ssh_bpp_input_raw_data_callback(void *context) { BinaryPacketProtocol *bpp = (BinaryPacketProtocol *)context; + Ssh *ssh = bpp->ssh; /* in case bpp is about to get freed */ ssh_bpp_handle_input(bpp); + /* If we've now cleared enough backlog on the input connection, we + * may need to unfreeze it. */ + ssh_conn_processed_data(ssh); } static void ssh_bpp_output_packet_callback(void *context) diff --git a/sshserver.c b/sshserver.c index ba976886..f1bcd9a7 100644 --- a/sshserver.c +++ b/sshserver.c @@ -201,6 +201,13 @@ void ssh_throttle_conn(Ssh *ssh, int adjust) } } +void ssh_conn_processed_data(Ssh *ssh) +{ + /* FIXME: we could add the same check_frozen_state system as we + * have in ssh.c, but because that was originally added to work + * around a peculiarity of the GUI event loop, I haven't yet. */ +} + static const PlugVtable ssh_server_plugvt = { server_socket_log, server_closing,