mirror of https://github.com/xemu-project/xemu.git
tests: Test polling in bdrv_drop_intermediate()
Signed-off-by: Max Reitz <mreitz@redhat.com> Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
parent
debc292767
commit
9746b35cf3
|
@ -100,6 +100,13 @@ static void bdrv_test_child_perm(BlockDriverState *bs, BdrvChild *c,
|
||||||
nperm, nshared);
|
nperm, nshared);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int bdrv_test_change_backing_file(BlockDriverState *bs,
|
||||||
|
const char *backing_file,
|
||||||
|
const char *backing_fmt)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static BlockDriver bdrv_test = {
|
static BlockDriver bdrv_test = {
|
||||||
.format_name = "test",
|
.format_name = "test",
|
||||||
.instance_size = sizeof(BDRVTestState),
|
.instance_size = sizeof(BDRVTestState),
|
||||||
|
@ -111,6 +118,8 @@ static BlockDriver bdrv_test = {
|
||||||
.bdrv_co_drain_end = bdrv_test_co_drain_end,
|
.bdrv_co_drain_end = bdrv_test_co_drain_end,
|
||||||
|
|
||||||
.bdrv_child_perm = bdrv_test_child_perm,
|
.bdrv_child_perm = bdrv_test_child_perm,
|
||||||
|
|
||||||
|
.bdrv_change_backing_file = bdrv_test_change_backing_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void aio_ret_cb(void *opaque, int ret)
|
static void aio_ret_cb(void *opaque, int ret)
|
||||||
|
@ -1671,6 +1680,161 @@ static void test_blockjob_commit_by_drained_end(void)
|
||||||
bdrv_unref(bs_child);
|
bdrv_unref(bs_child);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct TestSimpleBlockJob {
|
||||||
|
BlockJob common;
|
||||||
|
bool should_complete;
|
||||||
|
bool *did_complete;
|
||||||
|
} TestSimpleBlockJob;
|
||||||
|
|
||||||
|
static int coroutine_fn test_simple_job_run(Job *job, Error **errp)
|
||||||
|
{
|
||||||
|
TestSimpleBlockJob *s = container_of(job, TestSimpleBlockJob, common.job);
|
||||||
|
|
||||||
|
while (!s->should_complete) {
|
||||||
|
job_sleep_ns(job, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_simple_job_clean(Job *job)
|
||||||
|
{
|
||||||
|
TestSimpleBlockJob *s = container_of(job, TestSimpleBlockJob, common.job);
|
||||||
|
*s->did_complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const BlockJobDriver test_simple_job_driver = {
|
||||||
|
.job_driver = {
|
||||||
|
.instance_size = sizeof(TestSimpleBlockJob),
|
||||||
|
.free = block_job_free,
|
||||||
|
.user_resume = block_job_user_resume,
|
||||||
|
.drain = block_job_drain,
|
||||||
|
.run = test_simple_job_run,
|
||||||
|
.clean = test_simple_job_clean,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int drop_intermediate_poll_update_filename(BdrvChild *child,
|
||||||
|
BlockDriverState *new_base,
|
||||||
|
const char *filename,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We are free to poll here, which may change the block graph, if
|
||||||
|
* it is not drained.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* If the job is not drained: Complete it, schedule job_exit() */
|
||||||
|
aio_poll(qemu_get_current_aio_context(), false);
|
||||||
|
/* If the job is not drained: Run job_exit(), finish the job */
|
||||||
|
aio_poll(qemu_get_current_aio_context(), false);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test a poll in the midst of bdrv_drop_intermediate().
|
||||||
|
*
|
||||||
|
* bdrv_drop_intermediate() calls BdrvChildRole.update_filename(),
|
||||||
|
* which can yield or poll. This may lead to graph changes, unless
|
||||||
|
* the whole subtree in question is drained.
|
||||||
|
*
|
||||||
|
* We test this on the following graph:
|
||||||
|
*
|
||||||
|
* Job
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* job-node
|
||||||
|
* |
|
||||||
|
* v
|
||||||
|
*
|
||||||
|
* job-node
|
||||||
|
*
|
||||||
|
* |
|
||||||
|
* backing
|
||||||
|
* |
|
||||||
|
* v
|
||||||
|
*
|
||||||
|
* node-2 --chain--> node-1 --chain--> node-0
|
||||||
|
*
|
||||||
|
* We drop node-1 with bdrv_drop_intermediate(top=node-1, base=node-0).
|
||||||
|
*
|
||||||
|
* This first updates node-2's backing filename by invoking
|
||||||
|
* drop_intermediate_poll_update_filename(), which polls twice. This
|
||||||
|
* causes the job to finish, which in turns causes the job-node to be
|
||||||
|
* deleted.
|
||||||
|
*
|
||||||
|
* bdrv_drop_intermediate() uses a QLIST_FOREACH_SAFE() loop, so it
|
||||||
|
* already has a pointer to the BdrvChild edge between job-node and
|
||||||
|
* node-1. When it tries to handle that edge, we probably get a
|
||||||
|
* segmentation fault because the object no longer exists.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The solution is for bdrv_drop_intermediate() to drain top's
|
||||||
|
* subtree. This prevents graph changes from happening just because
|
||||||
|
* BdrvChildRole.update_filename() yields or polls. Thus, the block
|
||||||
|
* job is paused during that drained section and must finish before or
|
||||||
|
* after.
|
||||||
|
*
|
||||||
|
* (In addition, bdrv_replace_child() must keep the job paused.)
|
||||||
|
*/
|
||||||
|
static void test_drop_intermediate_poll(void)
|
||||||
|
{
|
||||||
|
static BdrvChildRole chain_child_role;
|
||||||
|
BlockDriverState *chain[3];
|
||||||
|
TestSimpleBlockJob *job;
|
||||||
|
BlockDriverState *job_node;
|
||||||
|
bool job_has_completed = false;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
chain_child_role = child_backing;
|
||||||
|
chain_child_role.update_filename = drop_intermediate_poll_update_filename;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
char name[32];
|
||||||
|
snprintf(name, 32, "node-%i", i);
|
||||||
|
|
||||||
|
chain[i] = bdrv_new_open_driver(&bdrv_test, name, 0, &error_abort);
|
||||||
|
}
|
||||||
|
|
||||||
|
job_node = bdrv_new_open_driver(&bdrv_test, "job-node", BDRV_O_RDWR,
|
||||||
|
&error_abort);
|
||||||
|
bdrv_set_backing_hd(job_node, chain[1], &error_abort);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Establish the chain last, so the chain links are the first
|
||||||
|
* elements in the BDS.parents lists
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if (i) {
|
||||||
|
/* Takes the reference to chain[i - 1] */
|
||||||
|
chain[i]->backing = bdrv_attach_child(chain[i], chain[i - 1],
|
||||||
|
"chain", &chain_child_role,
|
||||||
|
&error_abort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
job = block_job_create("job", &test_simple_job_driver, NULL, job_node,
|
||||||
|
0, BLK_PERM_ALL, 0, 0, NULL, NULL, &error_abort);
|
||||||
|
|
||||||
|
/* The job has a reference now */
|
||||||
|
bdrv_unref(job_node);
|
||||||
|
|
||||||
|
job->did_complete = &job_has_completed;
|
||||||
|
|
||||||
|
job_start(&job->common.job);
|
||||||
|
job->should_complete = true;
|
||||||
|
|
||||||
|
g_assert(!job_has_completed);
|
||||||
|
ret = bdrv_drop_intermediate(chain[1], chain[0], NULL);
|
||||||
|
g_assert(ret == 0);
|
||||||
|
g_assert(job_has_completed);
|
||||||
|
|
||||||
|
bdrv_unref(chain[2]);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
@ -1757,6 +1921,9 @@ int main(int argc, char **argv)
|
||||||
g_test_add_func("/bdrv-drain/blockjob/commit_by_drained_end",
|
g_test_add_func("/bdrv-drain/blockjob/commit_by_drained_end",
|
||||||
test_blockjob_commit_by_drained_end);
|
test_blockjob_commit_by_drained_end);
|
||||||
|
|
||||||
|
g_test_add_func("/bdrv-drain/bdrv_drop_intermediate/poll",
|
||||||
|
test_drop_intermediate_poll);
|
||||||
|
|
||||||
ret = g_test_run();
|
ret = g_test_run();
|
||||||
qemu_event_destroy(&done_event);
|
qemu_event_destroy(&done_event);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
Loading…
Reference in New Issue