From: Andrey Drobyshev <andrey.drobys...@virtuozzo.com> This case is catching potential deadlock which takes place when job-dismiss is issued when I/O requests are processed in a separate iothread.
See https://mail.gnu.org/archive/html/qemu-devel/2025-04/msg04421.html Signed-off-by: Andrey Drobyshev <andrey.drobys...@virtuozzo.com> --- .../qemu-iotests/tests/graph-changes-while-io | 101 ++++++++++++++++-- .../tests/graph-changes-while-io.out | 4 +- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/tests/qemu-iotests/tests/graph-changes-while-io b/tests/qemu-iotests/tests/graph-changes-while-io index 194fda500e..e30f823da4 100755 --- a/tests/qemu-iotests/tests/graph-changes-while-io +++ b/tests/qemu-iotests/tests/graph-changes-while-io @@ -27,6 +27,8 @@ from iotests import imgfmt, qemu_img, qemu_img_create, qemu_io, \ top = os.path.join(iotests.test_dir, 'top.img') +snap1 = os.path.join(iotests.test_dir, 'snap1.img') +snap2 = os.path.join(iotests.test_dir, 'snap2.img') nbd_sock = os.path.join(iotests.sock_dir, 'nbd.sock') @@ -58,6 +60,15 @@ class TestGraphChangesWhileIO(QMPTestCase): def tearDown(self) -> None: self.qsd.stop() + def _wait_for_blockjob(self, status) -> None: + done = False + while not done: + for event in self.qsd.get_qmp().get_events(wait=10.0): + if event['event'] != 'JOB_STATUS_CHANGE': + continue + if event['data']['status'] == status: + done = True + def test_blockdev_add_while_io(self) -> None: # Run qemu-img bench in the background bench_thr = Thread(target=do_qemu_img_bench) @@ -116,13 +127,89 @@ class TestGraphChangesWhileIO(QMPTestCase): 'device': 'job0', }) - cancelled = False - while not cancelled: - for event in self.qsd.get_qmp().get_events(wait=10.0): - if event['event'] != 'JOB_STATUS_CHANGE': - continue - if event['data']['status'] == 'null': - cancelled = True + self._wait_for_blockjob('null') + + bench_thr.join() + + def test_remove_lower_snapshot_while_io(self) -> None: + # Run qemu-img bench in the background + bench_thr = Thread(target=do_qemu_img_bench, args=(100000, )) + bench_thr.start() + + # While I/O is performed on 'node0' node, consequently add 2 snapshots + # on top of it, then remove (commit) them starting from lower one. + while bench_thr.is_alive(): + # Recreate snapshot images on every iteration + qemu_img_create('-f', imgfmt, snap1, '1G') + qemu_img_create('-f', imgfmt, snap2, '1G') + + self.qsd.cmd('blockdev-add', { + 'driver': imgfmt, + 'node-name': 'snap1', + 'file': { + 'driver': 'file', + 'filename': snap1 + } + }) + + self.qsd.cmd('blockdev-snapshot', { + 'node': 'node0', + 'overlay': 'snap1', + }) + + self.qsd.cmd('blockdev-add', { + 'driver': imgfmt, + 'node-name': 'snap2', + 'file': { + 'driver': 'file', + 'filename': snap2 + } + }) + + self.qsd.cmd('blockdev-snapshot', { + 'node': 'snap1', + 'overlay': 'snap2', + }) + + self.qsd.cmd('block-commit', { + 'job-id': 'commit-snap1', + 'device': 'snap2', + 'top-node': 'snap1', + 'base-node': 'node0', + 'auto-finalize': True, + 'auto-dismiss': False, + }) + + self._wait_for_blockjob('concluded') + self.qsd.cmd('job-dismiss', { + 'id': 'commit-snap1', + }) + + self.qsd.cmd('block-commit', { + 'job-id': 'commit-snap2', + 'device': 'snap2', + 'top-node': 'snap2', + 'base-node': 'node0', + 'auto-finalize': True, + 'auto-dismiss': False, + }) + + self._wait_for_blockjob('ready') + self.qsd.cmd('job-complete', { + 'id': 'commit-snap2', + }) + + self._wait_for_blockjob('concluded') + self.qsd.cmd('job-dismiss', { + 'id': 'commit-snap2', + }) + + self.qsd.cmd('blockdev-del', { + 'node-name': 'snap1' + }) + self.qsd.cmd('blockdev-del', { + 'node-name': 'snap2' + }) bench_thr.join() diff --git a/tests/qemu-iotests/tests/graph-changes-while-io.out b/tests/qemu-iotests/tests/graph-changes-while-io.out index fbc63e62f8..8d7e996700 100644 --- a/tests/qemu-iotests/tests/graph-changes-while-io.out +++ b/tests/qemu-iotests/tests/graph-changes-while-io.out @@ -1,5 +1,5 @@ -.. +... ---------------------------------------------------------------------- -Ran 2 tests +Ran 3 tests OK -- 2.43.5