From bda403766e613b451268560945cdb47aaf372827 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 10 Aug 2026 04:39:28 +0200 Subject: [PATCH] Start and stop the virsh console thread with the payload Activation started an event thread whichever way the base handler had just answered, so a refusal started one anyway and an already active console got a second. activated alone cannot tell the two refusals apart, being true already on the already active path, so the value from before the call decides. Deactivation joined that thread on the event loop, where it could stall every other session and its own response. The wait moves off the loop and is bounded, and the thread is a daemon. --- confluent_server/aiohmi/cmd/virshbmc.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/confluent_server/aiohmi/cmd/virshbmc.py b/confluent_server/aiohmi/cmd/virshbmc.py index 359ff97a..03456d6b 100755 --- a/confluent_server/aiohmi/cmd/virshbmc.py +++ b/confluent_server/aiohmi/cmd/virshbmc.py @@ -114,14 +114,32 @@ class LibvirtBmc(bmc.Bmc): async def activate_payload(self, request, session): # captured for stream_callback, which runs on a libvirt thread self.asyncloop = asyncio.get_running_loop() + wasactive = self.activated await super(LibvirtBmc, self).activate_payload(request, session) + if wasactive or not self.activated: + # the base handler refused: no io handler, or the domain is not + # running, so activated stayed false; or a console was already up, + # in which case activated was true before we asked and the thread + # for it is already running. Either way there is nothing to start. + return self.run_console = True self.sol_thread = threading.Thread(target=self.loop) + # virEventRunDefaultImpl can wait for an event that never comes, so + # this thread has no reliable end of its own + self.sol_thread.daemon = True self.sol_thread.start() async def deactivate_payload(self, request, session): - self.run_console = False - self.sol_thread.join() + if self.activated and self.sol_thread: + self.run_console = False + # Joining here would run on the event loop, and the thread sits in + # virEventRunDefaultImpl, which is documented as able to wait + # indefinitely: clearing run_console does not wake it. So wait off + # the loop, and not forever, rather than stall every other session + # and never send the response below. + await asyncio.get_running_loop().run_in_executor( + None, self.sol_thread.join, 5) + self.sol_thread = None await super(LibvirtBmc, self).deactivate_payload(request, session) async def iohandler(self, data):