[PULL 2/6] tests/unit/test-blockjob: cover keeping a job paused while a pause is pending

Vladimir Sementsov-Ogievskiy <[email protected]>
Newsgroups gmane.comp.emulators.qemu.block,gmane.comp.emulators.qemu
Message-ID <[email protected]>
From: "Denis V. Lunev" <[email protected]>

Add a regression test for the previous commit. A job that has reached
its pause point is spuriously re-entered (job_enter()) while a pause is
still pending (pause_count > 0), reproducing what an overlapping drain
does: one drain's job_resume() wakes the job while the next drain's
job_pause() is already counted. The job must stay parked - it must not
run job code or clear job->paused, or job_set_aio_context() could
observe paused == false and abort.

The test counts the job's run-loop iterations: without the fix the
re-entered job clears job->paused, runs one iteration and re-pauses, so
the counter advances; with the fix it stays parked and the counter is
unchanged. It runs in the main AioContext, so job_enter() is synchronous
and the check is deterministic.

Signed-off-by: Denis V. Lunev <[email protected]>
Message-ID: <[email protected]>
Reviewed-by: Vladimir Sementsov-Ogievskiy <[email protected]>
Tested-by: Vladimir Sementsov-Ogievskiy <[email protected]>
Signed-off-by: Vladimir Sementsov-Ogievskiy <[email protected]>
---
 tests/unit/test-blockjob.c | 100 +++++++++++++++++++++++++++++++++++++
 1 file changed, 100 insertions(+)

diff --git a/tests/unit/test-blockjob.c b/tests/unit/test-blockjob.c
index abdbe4b8350..3b77d19b911 100644
--- a/tests/unit/test-blockjob.c
+++ b/tests/unit/test-blockjob.c
@@ -388,6 +388,105 @@ static void test_cancel_concluded(void)
     cancel_common(s);
 }
 
+typedef struct PauseCountJob {
+    BlockJob common;
+    int n;
+    bool should_complete;
+} PauseCountJob;
+
+static void pause_count_job_complete(Job *job, Error **errp)
+{
+    PauseCountJob *s = container_of(job, PauseCountJob, common.job);
+    s->should_complete = true;
+}
+
+static int coroutine_fn pause_count_job_run(Job *job, Error **errp)
+{
+    PauseCountJob *s = container_of(job, PauseCountJob, common.job);
+
+    while (!s->should_complete) {
+        if (job_is_cancelled(&s->common.job)) {
+            return 0;
+        }
+        s->n++;
+        /*
+         * Yields; while a pause is pending the yield is skipped and the job
+         * parks in job_pause_point() instead.
+         */
+        job_sleep_ns(&s->common.job, 10 * 1000 * 1000);
+    }
+
+    return 0;
+}
+
+static const BlockJobDriver pause_count_job_driver = {
+    .job_driver = {
+        .instance_size = sizeof(PauseCountJob),
+        .free          = block_job_free,
+        .user_resume   = block_job_user_resume,
+        .run           = pause_count_job_run,
+        .complete      = pause_count_job_complete,
+    },
+};
+
+/*
+ * A job that has reached its pause point must stay paused while a pause is
+ * still pending (pause_count > 0). An overlapping drain re-enters the job (one
+ * drain's job_resume() wakes it while the next drain's job_pause() is already
+ * counted); the job must not run or clear job->paused, otherwise
+ * job_set_aio_context() can observe paused == false and abort.
+ */
+static void test_pause_keeps_paused(void)
+{
+    BlockBackend *blk;
+    BlockJob *bjob;
+    PauseCountJob *s;
+    Job *job;
+    int n0;
+
+    blk = create_blk(NULL);
+    bjob = mk_job(blk, "job0", &pause_count_job_driver, true,
+                  JOB_MANUAL_FINALIZE | JOB_MANUAL_DISMISS);
+    s = container_of(bjob, PauseCountJob, common);
+    job = &bjob->job;
+    WITH_JOB_LOCK_GUARD() {
+        job_ref_locked(job);
+    }
+
+    job_start(job);
+
+    /* Pause the running job; it parks in job_pause_point() with paused set. */
+    WITH_JOB_LOCK_GUARD() {
+        job_pause_locked(job);
+        g_assert_true(job->paused);
+        g_assert_cmpint(job->status, ==, JOB_STATUS_PAUSED);
+    }
+    n0 = s->n;
+
+    /*
+     * Spurious wake while the pause is still pending. The job must stay parked:
+     * the bug clears job->paused, runs an iteration (s->n advances) and
+     * re-pauses, exposing a paused == false window.
+     */
+    job_enter(job);
+    WITH_JOB_LOCK_GUARD() {
+        g_assert_true(job->paused);
+    }
+    g_assert_cmpint(s->n, ==, n0);
+
+    /* Resume and tear down. */
+    WITH_JOB_LOCK_GUARD() {
+        job_resume_locked(job);
+    }
+    job_cancel_sync(job, true);
+    WITH_JOB_LOCK_GUARD() {
+        Job *dummy = job;
+        job_dismiss_locked(&dummy, &error_abort);
+        job_unref_locked(job);
+    }
+    destroy_blk(blk);
+}
+
 int main(int argc, char **argv)
 {
     qemu_init_main_loop(&error_abort);
@@ -402,5 +501,6 @@ int main(int argc, char **argv)
     g_test_add_func("/blockjob/cancel/standby", test_cancel_standby);
     g_test_add_func("/blockjob/cancel/pending", test_cancel_pending);
     g_test_add_func("/blockjob/cancel/concluded", test_cancel_concluded);
+    g_test_add_func("/blockjob/pause/keep_paused", test_pause_keeps_paused);
     return g_test_run();
 }
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.