qemu/tests/unit/test-blockjob-txn.c
<<
>>
Prefs
   1/*
   2 * Blockjob transactions tests
   3 *
   4 * Copyright Red Hat, Inc. 2015
   5 *
   6 * Authors:
   7 *  Stefan Hajnoczi    <stefanha@redhat.com>
   8 *
   9 * This work is licensed under the terms of the GNU LGPL, version 2 or later.
  10 * See the COPYING.LIB file in the top-level directory.
  11 */
  12
  13#include "qemu/osdep.h"
  14#include "qapi/error.h"
  15#include "qemu/main-loop.h"
  16#include "block/blockjob_int.h"
  17#include "sysemu/block-backend.h"
  18#include "qapi/qmp/qdict.h"
  19
  20typedef struct {
  21    BlockJob common;
  22    unsigned int iterations;
  23    bool use_timer;
  24    int rc;
  25    int *result;
  26} TestBlockJob;
  27
  28static void test_block_job_clean(Job *job)
  29{
  30    BlockJob *bjob = container_of(job, BlockJob, job);
  31    BlockDriverState *bs = blk_bs(bjob->blk);
  32
  33    bdrv_unref(bs);
  34}
  35
  36static int coroutine_fn test_block_job_run(Job *job, Error **errp)
  37{
  38    TestBlockJob *s = container_of(job, TestBlockJob, common.job);
  39
  40    while (s->iterations--) {
  41        if (s->use_timer) {
  42            job_sleep_ns(job, 0);
  43        } else {
  44            job_yield(job);
  45        }
  46
  47        if (job_is_cancelled(job)) {
  48            break;
  49        }
  50    }
  51
  52    return s->rc;
  53}
  54
  55typedef struct {
  56    TestBlockJob *job;
  57    int *result;
  58} TestBlockJobCBData;
  59
  60static void test_block_job_cb(void *opaque, int ret)
  61{
  62    TestBlockJobCBData *data = opaque;
  63    if (!ret && job_is_cancelled(&data->job->common.job)) {
  64        ret = -ECANCELED;
  65    }
  66    *data->result = ret;
  67    g_free(data);
  68}
  69
  70static const BlockJobDriver test_block_job_driver = {
  71    .job_driver = {
  72        .instance_size = sizeof(TestBlockJob),
  73        .free          = block_job_free,
  74        .user_resume   = block_job_user_resume,
  75        .run           = test_block_job_run,
  76        .clean         = test_block_job_clean,
  77    },
  78};
  79
  80/* Create a block job that completes with a given return code after a given
  81 * number of event loop iterations.  The return code is stored in the given
  82 * result pointer.
  83 *
  84 * The event loop iterations can either be handled automatically with a 0 delay
  85 * timer, or they can be stepped manually by entering the coroutine.
  86 */
  87static BlockJob *test_block_job_start(unsigned int iterations,
  88                                      bool use_timer,
  89                                      int rc, int *result, JobTxn *txn)
  90{
  91    BlockDriverState *bs;
  92    TestBlockJob *s;
  93    TestBlockJobCBData *data;
  94    static unsigned counter;
  95    char job_id[24];
  96
  97    data = g_new0(TestBlockJobCBData, 1);
  98
  99    QDict *opt = qdict_new();
 100    qdict_put_str(opt, "file.read-zeroes", "on");
 101    bs = bdrv_open("null-co://", NULL, opt, 0, &error_abort);
 102    g_assert_nonnull(bs);
 103
 104    snprintf(job_id, sizeof(job_id), "job%u", counter++);
 105    s = block_job_create(job_id, &test_block_job_driver, txn, bs,
 106                         0, BLK_PERM_ALL, 0, JOB_DEFAULT,
 107                         test_block_job_cb, data, &error_abort);
 108    s->iterations = iterations;
 109    s->use_timer = use_timer;
 110    s->rc = rc;
 111    s->result = result;
 112    data->job = s;
 113    data->result = result;
 114    return &s->common;
 115}
 116
 117static void test_single_job(int expected)
 118{
 119    BlockJob *job;
 120    JobTxn *txn;
 121    int result = -EINPROGRESS;
 122
 123    txn = job_txn_new();
 124    job = test_block_job_start(1, true, expected, &result, txn);
 125    job_start(&job->job);
 126
 127    if (expected == -ECANCELED) {
 128        job_cancel(&job->job, false);
 129    }
 130
 131    while (result == -EINPROGRESS) {
 132        aio_poll(qemu_get_aio_context(), true);
 133    }
 134    g_assert_cmpint(result, ==, expected);
 135
 136    job_txn_unref(txn);
 137}
 138
 139static void test_single_job_success(void)
 140{
 141    test_single_job(0);
 142}
 143
 144static void test_single_job_failure(void)
 145{
 146    test_single_job(-EIO);
 147}
 148
 149static void test_single_job_cancel(void)
 150{
 151    test_single_job(-ECANCELED);
 152}
 153
 154static void test_pair_jobs(int expected1, int expected2)
 155{
 156    BlockJob *job1;
 157    BlockJob *job2;
 158    JobTxn *txn;
 159    int result1 = -EINPROGRESS;
 160    int result2 = -EINPROGRESS;
 161
 162    txn = job_txn_new();
 163    job1 = test_block_job_start(1, true, expected1, &result1, txn);
 164    job2 = test_block_job_start(2, true, expected2, &result2, txn);
 165    job_start(&job1->job);
 166    job_start(&job2->job);
 167
 168    /* Release our reference now to trigger as many nice
 169     * use-after-free bugs as possible.
 170     */
 171    job_txn_unref(txn);
 172
 173    if (expected1 == -ECANCELED) {
 174        job_cancel(&job1->job, false);
 175    }
 176    if (expected2 == -ECANCELED) {
 177        job_cancel(&job2->job, false);
 178    }
 179
 180    while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
 181        aio_poll(qemu_get_aio_context(), true);
 182    }
 183
 184    /* Failure or cancellation of one job cancels the other job */
 185    if (expected1 != 0) {
 186        expected2 = -ECANCELED;
 187    } else if (expected2 != 0) {
 188        expected1 = -ECANCELED;
 189    }
 190
 191    g_assert_cmpint(result1, ==, expected1);
 192    g_assert_cmpint(result2, ==, expected2);
 193}
 194
 195static void test_pair_jobs_success(void)
 196{
 197    test_pair_jobs(0, 0);
 198}
 199
 200static void test_pair_jobs_failure(void)
 201{
 202    /* Test both orderings.  The two jobs run for a different number of
 203     * iterations so the code path is different depending on which job fails
 204     * first.
 205     */
 206    test_pair_jobs(-EIO, 0);
 207    test_pair_jobs(0, -EIO);
 208}
 209
 210static void test_pair_jobs_cancel(void)
 211{
 212    test_pair_jobs(-ECANCELED, 0);
 213    test_pair_jobs(0, -ECANCELED);
 214}
 215
 216static void test_pair_jobs_fail_cancel_race(void)
 217{
 218    BlockJob *job1;
 219    BlockJob *job2;
 220    JobTxn *txn;
 221    int result1 = -EINPROGRESS;
 222    int result2 = -EINPROGRESS;
 223
 224    txn = job_txn_new();
 225    job1 = test_block_job_start(1, true, -ECANCELED, &result1, txn);
 226    job2 = test_block_job_start(2, false, 0, &result2, txn);
 227    job_start(&job1->job);
 228    job_start(&job2->job);
 229
 230    job_cancel(&job1->job, false);
 231
 232    /* Now make job2 finish before the main loop kicks jobs.  This simulates
 233     * the race between a pending kick and another job completing.
 234     */
 235    job_enter(&job2->job);
 236    job_enter(&job2->job);
 237
 238    while (result1 == -EINPROGRESS || result2 == -EINPROGRESS) {
 239        aio_poll(qemu_get_aio_context(), true);
 240    }
 241
 242    g_assert_cmpint(result1, ==, -ECANCELED);
 243    g_assert_cmpint(result2, ==, -ECANCELED);
 244
 245    job_txn_unref(txn);
 246}
 247
 248int main(int argc, char **argv)
 249{
 250    qemu_init_main_loop(&error_abort);
 251    bdrv_init();
 252
 253    g_test_init(&argc, &argv, NULL);
 254    g_test_add_func("/single/success", test_single_job_success);
 255    g_test_add_func("/single/failure", test_single_job_failure);
 256    g_test_add_func("/single/cancel", test_single_job_cancel);
 257    g_test_add_func("/pair/success", test_pair_jobs_success);
 258    g_test_add_func("/pair/failure", test_pair_jobs_failure);
 259    g_test_add_func("/pair/cancel", test_pair_jobs_cancel);
 260    g_test_add_func("/pair/fail-cancel-race", test_pair_jobs_fail_cancel_race);
 261    return g_test_run();
 262}
 263