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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Max Reitz
						Max Reitz