tests/functional: add a stress test for monitor hot unplug

When unplugging a monitor there is a careful synchronization dance
between the monitor handling the "object-del" command and the
command processing for the monitor being deleted.

The stress test runs a busy loop of 'query-qmp-schema' on a second
monitor, while the primary monitor requests its deletion.

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Tested-by: Peter Krempa <pkrempa@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
Message-ID: <20260706135824.2623960-31-berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Daniel P. Berrangé
2026-07-06 14:58:18 +01:00
committed by Markus Armbruster
parent f9827a9b9a
commit 8b142ecb34

View File

@@ -6,11 +6,16 @@
#
# Copyright (c) 2026 Christian Brauner
import asyncio
import os
import random
import threading
import time
from qemu_test import QemuSystemTest
from qemu.qmp.legacy import QEMUMonitorProtocol
from qemu.qmp import QMPClient
class MonitorHotplug(QemuSystemTest):
@@ -163,6 +168,63 @@ class MonitorHotplug(QemuSystemTest):
qmp.close()
self._remove_monitor()
def stress_mon(self, sock):
async def main():
qmp = QMPClient('testvm')
await qmp.connect(sock)
# Run query-version in a tight loop so that the
# monitor thread/dispatcher is very busy at the
# time we try to delete the monitor
while True:
try:
# A command which returns a lot of data to make
# it more likely we're in the I/O reply path
# when deleting the monitor
res = await qmp.execute('query-qmp-schema')
# Some commands which generate async events
# as those can trigger different code paths
res = await qmp.execute('stop')
res = await qmp.execute('cont')
except:
# we'll get here if the monitor is terminated
# by QEMU in which case we must disconnect
# out side, but....
try:
await qmp.disconnect()
except (ConnectionResetError, EOFError, BrokenPipeError):
# ... disconnect() will probably see
# errors too, but we must try to call it
# regardless to cleanup asyncio state
# and prevent python warnings at GC time
pass
return
asyncio.run(main())
def test_hotplug_stress(self):
"""
Repeatedly hotplug and unplug a monitor, while another thread
concurrently issues commands on that monitor. This stresses
the synchronization with the monitor thread during cleanup
"""
self.set_machine('none')
self.vm.add_args('-nodefaults')
self.vm.launch()
# Each loop sleeps at most 0.5 seconds, so this should
# give an upper bound of approx 5 seconds execution
# time which is reasonable to run by default
repeat = 10
for i in range(repeat):
# First cycle
sock = self._add_monitor()
print ("# stress cycle %02d/%02d" % (i, repeat))
stress = threading.Thread(target=self.stress_mon, args=[sock])
stress.start()
# Sleep upto 1/2 second to vary the races
time.sleep(random.random() / 2)
self._remove_monitor()
stress.join()
if __name__ == '__main__':
QemuSystemTest.main()