@samitouri / QOSamiQemu / commits / 8b142ecb34

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>

Daniel P. Berrangé committed Jul 6, 2026 at 14:58 UTC 8b142ecb34f0a75bac9bed475c92ee9037a96f46
1 file changed +62
tests/functional/generic/test_monitor_hotplug.py
+62
@@ -6,11 +6,16 @@
6 #
7 # Copyright (c) 2026 Christian Brauner
8
9 +import asyncio
10 import os
11 +import random
12 +import threading
13 +import time
14
15 from qemu_test import QemuSystemTest
16
17 from qemu.qmp.legacy import QEMUMonitorProtocol
18 +from qemu.qmp import QMPClient
19
20
21 class MonitorHotplug(QemuSystemTest):
@@ -163,6 +168,63 @@ class MonitorHotplug(QemuSystemTest):
168 qmp.close()
169 self._remove_monitor()
170
171 + def stress_mon(self, sock):
172 + async def main():
173 + qmp = QMPClient('testvm')
174 + await qmp.connect(sock)
175 + # Run query-version in a tight loop so that the
176 + # monitor thread/dispatcher is very busy at the
177 + # time we try to delete the monitor
178 + while True:
179 + try:
180 + # A command which returns a lot of data to make
181 + # it more likely we're in the I/O reply path
182 + # when deleting the monitor
183 + res = await qmp.execute('query-qmp-schema')
184 + # Some commands which generate async events
185 + # as those can trigger different code paths
186 + res = await qmp.execute('stop')
187 + res = await qmp.execute('cont')
188 + except:
189 + # we'll get here if the monitor is terminated
190 + # by QEMU in which case we must disconnect
191 + # out side, but....
192 + try:
193 + await qmp.disconnect()
194 + except (ConnectionResetError, EOFError, BrokenPipeError):
195 + # ... disconnect() will probably see
196 + # errors too, but we must try to call it
197 + # regardless to cleanup asyncio state
198 + # and prevent python warnings at GC time
199 + pass
200 + return
201 + asyncio.run(main())
202 +
203 + def test_hotplug_stress(self):
204 + """
205 + Repeatedly hotplug and unplug a monitor, while another thread
206 + concurrently issues commands on that monitor. This stresses
207 + the synchronization with the monitor thread during cleanup
208 + """
209 + self.set_machine('none')
210 + self.vm.add_args('-nodefaults')
211 + self.vm.launch()
212 +
213 + # Each loop sleeps at most 0.5 seconds, so this should
214 + # give an upper bound of approx 5 seconds execution
215 + # time which is reasonable to run by default
216 + repeat = 10
217 + for i in range(repeat):
218 + # First cycle
219 + sock = self._add_monitor()
220 + print ("# stress cycle %02d/%02d" % (i, repeat))
221 + stress = threading.Thread(target=self.stress_mon, args=[sock])
222 + stress.start()
223 + # Sleep upto 1/2 second to vary the races
224 + time.sleep(random.random() / 2)
225 + self._remove_monitor()
226 + stress.join()
227 +
228
229 if __name__ == '__main__':
230 QemuSystemTest.main()