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 20 typedef struct { 21 BlockJob common; 22 unsigned int iterations; 23 bool use_timer; 24 int rc; 25 int *result; 26 } TestBlockJob; 27 28 static 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 47 typedef struct { 48 TestBlockJob *job; 49 int *result; 50 } TestBlockJobCBData; 51 52 static 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 62 static 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 */ 78 static 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 109 static 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 131 static void test_single_job_success(void) 132 { 133 test_single_job(0); 134 } 135 136 static void test_single_job_failure(void) 137 { 138 test_single_job(-EIO); 139 } 140 141 static void test_single_job_cancel(void) 142 { 143 test_single_job(-ECANCELED); 144 } 145 146 static 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 187 static void test_pair_jobs_success(void) 188 { 189 test_pair_jobs(0, 0); 190 } 191 192 static 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 202 static void test_pair_jobs_cancel(void) 203 { 204 test_pair_jobs(-ECANCELED, 0); 205 test_pair_jobs(0, -ECANCELED); 206 } 207 208 static 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 240 int 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