From 79d8d02f55e242a8d8f48464977be976202d8eb9 Mon Sep 17 00:00:00 2001 From: leonace924 Date: Thu, 18 Dec 2025 10:26:07 -0500 Subject: [PATCH 1/2] fix: resolve SQL syntax error in ProxyAnnouncements and improve event loop management --- bittensor_cli/cli.py | 54 ++++++++++++++++++++++++++-- bittensor_cli/src/bittensor/utils.py | 2 +- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 9fd55345c..ebdeea8fa 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1461,7 +1461,7 @@ def initialize_chain( def _run_command(self, cmd: Coroutine, exit_early: bool = True): """ - Runs the supplied coroutine with `asyncio.run` + Runs the supplied coroutine with proper event loop management. """ async def _run(): @@ -1508,7 +1508,45 @@ async def _run(): if exception_occurred: raise typer.Exit() - return self.event_loop.run_until_complete(_run()) + # Check if there's already a running event loop + try: + asyncio.get_running_loop() + # If we're in an async context, we need to use a different approach + # This shouldn't happen in CLI context, but handle it gracefully + raise RuntimeError( + "Cannot run command: there is already a running event loop. " + "This CLI should be run from a synchronous context." + ) + except RuntimeError: + # No running loop, we can proceed + pass + + # Use the existing event loop or create a new one + if self.event_loop.is_closed(): + if sys.version_info < (3, 10): + self.event_loop = asyncio.new_event_loop() + else: + try: + uvloop = importlib.import_module("uvloop") + self.event_loop = uvloop.new_event_loop() + except ModuleNotFoundError: + self.event_loop = asyncio.new_event_loop() + + try: + return self.event_loop.run_until_complete(_run()) + finally: + # Clean up: close the event loop if we created it + # Note: We don't close it if it was already running + if not self.event_loop.is_running(): + # Schedule remaining tasks cleanup + pending = asyncio.all_tasks(self.event_loop) + for task in pending: + task.cancel() + # Give tasks a chance to clean up + if pending: + self.event_loop.run_until_complete( + asyncio.gather(*pending, return_exceptions=True) + ) def main_callback( self, @@ -6631,7 +6669,17 @@ def sudo_set( exit_early=False, ) if not hyperparams: - # TODO this will cause a hanging connection, subtensor needs to be gracefully exited + # Ensure we don't leave the websocket connection hanging. + if self.subtensor: + try: + self.event_loop.run_until_complete( + self.subtensor.substrate.close() + ) + except Exception: + # Best-effort shutdown. We'll still exit cleanly. + pass + finally: + self.subtensor = None raise typer.Exit() if not param_name: diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 2310074b9..c15b8f287 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -1124,7 +1124,7 @@ def delete_entry( call_hash: str, ): conn.execute( - f"DELETE FROM {cls.name} WHERE call_hash = ?, address = ?, epoch_time = ?, block = ?", + f"DELETE FROM {cls.name} WHERE call_hash = ? AND address = ? AND epoch_time = ? AND block = ?", (call_hash, address, epoch_time, block), ) conn.commit() From 2f6520abb02936808b4de2e1767f22ff0db979ff Mon Sep 17 00:00:00 2001 From: leonace924 Date: Thu, 18 Dec 2025 10:44:35 -0500 Subject: [PATCH 2/2] fix: remove the event loop --- bittensor_cli/cli.py | 43 ++----------------------------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ebdeea8fa..a4e2075f5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1461,7 +1461,7 @@ def initialize_chain( def _run_command(self, cmd: Coroutine, exit_early: bool = True): """ - Runs the supplied coroutine with proper event loop management. + Runs the supplied coroutine with `asyncio.run` """ async def _run(): @@ -1508,45 +1508,7 @@ async def _run(): if exception_occurred: raise typer.Exit() - # Check if there's already a running event loop - try: - asyncio.get_running_loop() - # If we're in an async context, we need to use a different approach - # This shouldn't happen in CLI context, but handle it gracefully - raise RuntimeError( - "Cannot run command: there is already a running event loop. " - "This CLI should be run from a synchronous context." - ) - except RuntimeError: - # No running loop, we can proceed - pass - - # Use the existing event loop or create a new one - if self.event_loop.is_closed(): - if sys.version_info < (3, 10): - self.event_loop = asyncio.new_event_loop() - else: - try: - uvloop = importlib.import_module("uvloop") - self.event_loop = uvloop.new_event_loop() - except ModuleNotFoundError: - self.event_loop = asyncio.new_event_loop() - - try: - return self.event_loop.run_until_complete(_run()) - finally: - # Clean up: close the event loop if we created it - # Note: We don't close it if it was already running - if not self.event_loop.is_running(): - # Schedule remaining tasks cleanup - pending = asyncio.all_tasks(self.event_loop) - for task in pending: - task.cancel() - # Give tasks a chance to clean up - if pending: - self.event_loop.run_until_complete( - asyncio.gather(*pending, return_exceptions=True) - ) + return self.event_loop.run_until_complete(_run()) def main_callback( self, @@ -6676,7 +6638,6 @@ def sudo_set( self.subtensor.substrate.close() ) except Exception: - # Best-effort shutdown. We'll still exit cleanly. pass finally: self.subtensor = None