diff --git a/changelog.md b/changelog.md index c2ab70bc..38c19a9e 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ Features * Make the completion interface more responsive using a background thread. * Option to suppress control-d exit behavior. * Better support Truecolor terminals. +* Ability to send app-layer keepalive pings to the server. Bug Fixes diff --git a/mycli/main.py b/mycli/main.py index f099f100..68a62d11 100755 --- a/mycli/main.py +++ b/mycli/main.py @@ -162,6 +162,7 @@ def __init__( self.login_path = login_path self.toolbar_error_message: str | None = None self.prompt_app: PromptSession | None = None + self._keepalive_counter = 0 # self.cnf_files is a class variable that stores the list of mysql # config files to read in at launch. @@ -185,6 +186,7 @@ def __init__( special.set_timing_enabled(c["main"].as_bool("timing")) special.set_show_favorite_query(c["main"].as_bool("show_favorite_query")) self.beep_after_seconds = float(c["main"]["beep_after_seconds"] or 0) + self.default_keepalive_ticks = c['connection'].as_int('default_keepalive_ticks') FavoriteQueries.instance = FavoriteQueries.from_config(self.config) @@ -782,6 +784,7 @@ def handle_editor_command(self, text: str) -> str: while True: try: assert isinstance(self.prompt_app, PromptSession) + # buglet: this prompt() invocation doesn't have an inputhook for keepalive pings text = self.prompt_app.prompt(default=sql) break except KeyboardInterrupt: @@ -986,11 +989,35 @@ def output_res(results: Generator[SQLResult], start: float) -> None: self.echo("") self.output(formatted, status) + def keepalive_hook(_context): + """ + prompt_toolkit shares the event loop with this hook, which seems + to get called a bit faster than once/second on one machine. + + It would be nice to reset the counter whenever user input is made, + but was not clear how to do that with context.input_is_ready(). + + Example at https://github.com/prompt-toolkit/python-prompt-toolkit/blob/main/examples/prompts/inputhook.py + """ + if self.default_keepalive_ticks < 1: + return + self._keepalive_counter += 1 + if self._keepalive_counter > self.default_keepalive_ticks: + self._keepalive_counter = 0 + self.logger.debug('keepalive ping') + try: + assert self.sqlexecute is not None + assert self.sqlexecute.conn is not None + self.sqlexecute.conn.ping(reconnect=False) + except Exception as e: + self.logger.debug('keepalive ping error %r', e) + def one_iteration(text: str | None = None) -> None: + inputhook = keepalive_hook if self.default_keepalive_ticks >= 1 else None if text is None: try: assert self.prompt_app is not None - text = self.prompt_app.prompt() + text = self.prompt_app.prompt(inputhook=inputhook) except KeyboardInterrupt: return @@ -1033,7 +1060,7 @@ def one_iteration(text: str | None = None) -> None: click.echo("---") if special.is_timing_enabled(): click.echo(f"Time: {duration:.2f} seconds") - text = self.prompt_app.prompt(default=sql or '') + text = self.prompt_app.prompt(default=sql or '', inputhook=inputhook) except KeyboardInterrupt: return except special.FinishIteration as e: diff --git a/mycli/myclirc b/mycli/myclirc index 45557953..dc384e09 100644 --- a/mycli/myclirc +++ b/mycli/myclirc @@ -159,6 +159,10 @@ default_character_set = utf8mb4 # whether to enable LOAD DATA LOCAL INFILE for connections without --local-infile being set default_local_infile = False +# How often to send periodic background pings to the server when input is idle. Ticks are +# roughly in seconds, but may be faster. Set to zero to disable. Suggestion: 300. +default_keepalive_ticks = 0 + # Sets the desired behavior for handling secure connections to the database server. # Possible values: # auto = SSL is preferred. Will attempt to connect via SSL, but will fallback to cleartext as needed. diff --git a/test/myclirc b/test/myclirc index 2b9a4454..02e477f3 100644 --- a/test/myclirc +++ b/test/myclirc @@ -157,6 +157,10 @@ default_character_set = utf8mb4 # whether to enable LOAD DATA LOCAL INFILE for connections without --local-infile being set default_local_infile = False +# How often to send periodic background pings to the server when input is idle. Ticks are +# roughly in seconds, but may be faster. Set to zero to disable. Suggestion: 300. +default_keepalive_ticks = 0 + # Sets the desired behavior for handling secure connections to the database server. # Possible values: # auto = SSL is preferred. Will attempt to connect via SSL, but will fallback to cleartext as needed.