From 11dc8196b0461485eadbbf68db08169313c0cb70 Mon Sep 17 00:00:00 2001 From: Markus Hilger Date: Mon, 10 Aug 2026 04:39:28 +0200 Subject: [PATCH] Keep hold of a virsh console thread that will not stop Deactivation dropped its reference once the wait expired, whether or not the thread had stopped, so the next activation started a second one and revived the first by setting run_console again. The reference is cleared only when the thread is really gone, and activation refuses with 0x80 while one is alive. That makes the wait a courtesy rather than a correctness measure, so it drops to a second. --- confluent_server/aiohmi/cmd/virshbmc.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/confluent_server/aiohmi/cmd/virshbmc.py b/confluent_server/aiohmi/cmd/virshbmc.py index 03456d6b..63bce006 100755 --- a/confluent_server/aiohmi/cmd/virshbmc.py +++ b/confluent_server/aiohmi/cmd/virshbmc.py @@ -114,6 +114,13 @@ 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() + if self.sol_thread is not None and self.sol_thread.is_alive(): + # The thread from the previous console has not come back out of + # virEventRunDefaultImpl. A second one would run a second event + # loop against the same stream, and setting run_console below + # would revive the first one when it finally wakes. + return await session.send_ipmi_response(code=0x80) + self.sol_thread = None wasactive = self.activated await super(LibvirtBmc, self).activate_payload(request, session) if wasactive or not self.activated: @@ -132,14 +139,16 @@ class LibvirtBmc(bmc.Bmc): async def deactivate_payload(self, request, session): 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. + # The thread only notices that after virEventRunDefaultImpl + # returns, which is documented as possibly never. Waiting on the + # event loop would stall every other session, so wait off it, and + # briefly: this is only to clear the state promptly in the normal + # case. A thread that outlives the wait stays owned here, and + # activate_payload refuses to start another until it is gone. await asyncio.get_running_loop().run_in_executor( - None, self.sol_thread.join, 5) - self.sol_thread = None + None, self.sol_thread.join, 1) + if not self.sol_thread.is_alive(): + self.sol_thread = None await super(LibvirtBmc, self).deactivate_payload(request, session) async def iohandler(self, data):