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