Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[dm-thin] Fix a race condition between one thin device discarding a b…
…lock and another provisioning it.

The discard passdown was being issued after the unmapping, which meant
the block could be reprovisioned whilst the passdown discards are
still in flight.

Now the unmapping does not occur until after the passdown is complete.
  • Loading branch information
jthornber committed Jul 1, 2016
1 parent ebdd99f commit c05989b
Showing 1 changed file with 67 additions and 14 deletions.
81 changes: 67 additions & 14 deletions drivers/md/dm-thin.c
Expand Up @@ -253,6 +253,7 @@ struct pool {
struct bio_list deferred_flush_bios;
struct list_head prepared_mappings;
struct list_head prepared_discards;
struct list_head prepared_discards_pt2;
struct list_head active_thins;

struct dm_deferred_set *shared_read_ds;
Expand All @@ -269,6 +270,7 @@ struct pool {

process_mapping_fn process_prepared_mapping;
process_mapping_fn process_prepared_discard;
process_mapping_fn process_prepared_discard_pt2;

struct dm_bio_prison_cell **cell_sort_array;
};
Expand Down Expand Up @@ -1014,7 +1016,8 @@ static void process_prepared_discard_no_passdown(struct dm_thin_new_mapping *m)

/*----------------------------------------------------------------*/

static void passdown_double_checking_shared_status(struct dm_thin_new_mapping *m)
static void passdown_double_checking_shared_status(struct dm_thin_new_mapping *m,
struct bio *discard_parent)
{
/*
* We've already unmapped this range of blocks, but before we
Expand All @@ -1027,7 +1030,7 @@ static void passdown_double_checking_shared_status(struct dm_thin_new_mapping *m
dm_block_t b = m->data_block, e, end = m->data_block + m->virt_end - m->virt_begin;
struct discard_op op;

begin_discard(&op, tc, m->bio);
begin_discard(&op, tc, discard_parent);
while (b != end) {
/* find start of unmapped run */
for (; b < end; b++) {
Expand Down Expand Up @@ -1062,7 +1065,61 @@ static void passdown_double_checking_shared_status(struct dm_thin_new_mapping *m
end_discard(&op, r);
}

static void process_prepared_discard_passdown(struct dm_thin_new_mapping *m)
static void queue_passdown_pt2(struct dm_thin_new_mapping *m)
{
unsigned long flags;
struct pool *pool = m->tc->pool;

spin_lock_irqsave(&pool->lock, flags);
list_add_tail(&m->list, &pool->prepared_discards_pt2);
spin_unlock_irqrestore(&pool->lock, flags);
wake_worker(pool);
}

static void passdown_endio(struct bio *bio)
{
/*
* It doesn't matter if the passdown discard failed, we still want
* to unmap (we ignore err).
*/
queue_passdown_pt2(bio->bi_private);
}

static void process_prepared_discard_passdown_pt1(struct dm_thin_new_mapping *m)
{
int r;
struct bio *discard_parent;
struct thin_c *tc = m->tc;

/*
* We're about to do passdown, so we should consider the original
* bio to be in flight.
*/
inc_all_io_entry(tc->pool, m->bio);

discard_parent = bio_alloc(GFP_NOIO, 1);
if (!discard_parent) {
DMWARN("unable to allocate top level discard bio for passdown. Skipping passdown.");
queue_passdown_pt2(m);

} else {
discard_parent->bi_end_io = passdown_endio;
discard_parent->bi_private = m;

if (m->maybe_shared)
passdown_double_checking_shared_status(m, discard_parent);

else {
struct discard_op op;
begin_discard(&op, tc, discard_parent);
r = issue_discard(&op, m->data_block,
m->data_block + (m->virt_end - m->virt_begin));
end_discard(&op, r);
}
}
}

static void process_prepared_discard_passdown_pt2(struct dm_thin_new_mapping *m)
{
int r;
struct thin_c *tc = m->tc;
Expand All @@ -1073,16 +1130,8 @@ static void process_prepared_discard_passdown(struct dm_thin_new_mapping *m)
metadata_operation_failed(pool, "dm_thin_remove_range", r);
bio_io_error(m->bio);

} else if (m->maybe_shared) {
passdown_double_checking_shared_status(m);

} else {
struct discard_op op;
begin_discard(&op, tc, m->bio);
r = issue_discard(&op, m->data_block,
m->data_block + (m->virt_end - m->virt_begin));
end_discard(&op, r);
}
} else
bio_complete(m->bio, r);

cell_defer_no_holder(tc, m->cell);
mempool_free(m, pool->mapping_pool);
Expand Down Expand Up @@ -2227,6 +2276,8 @@ static void do_worker(struct work_struct *ws)
throttle_work_update(&pool->throttle);
process_prepared(pool, &pool->prepared_discards, &pool->process_prepared_discard);
throttle_work_update(&pool->throttle);
process_prepared(pool, &pool->prepared_discards_pt2, &pool->process_prepared_discard_pt2);
throttle_work_update(&pool->throttle);
process_deferred_bios(pool);
throttle_work_complete(&pool->throttle);
}
Expand Down Expand Up @@ -2355,7 +2406,8 @@ static void set_discard_callbacks(struct pool *pool)

if (passdown_enabled(pt)) {
pool->process_discard_cell = process_discard_cell_passdown;
pool->process_prepared_discard = process_prepared_discard_passdown;
pool->process_prepared_discard = process_prepared_discard_passdown_pt1;
pool->process_prepared_discard_pt2 = process_prepared_discard_passdown_pt2;
} else {
pool->process_discard_cell = process_discard_cell_no_passdown;
pool->process_prepared_discard = process_prepared_discard_no_passdown;
Expand Down Expand Up @@ -2840,6 +2892,7 @@ static struct pool *pool_create(struct mapped_device *pool_md,
bio_list_init(&pool->deferred_flush_bios);
INIT_LIST_HEAD(&pool->prepared_mappings);
INIT_LIST_HEAD(&pool->prepared_discards);
INIT_LIST_HEAD(&pool->prepared_discards_pt2);
INIT_LIST_HEAD(&pool->active_thins);
pool->low_water_triggered = false;
pool->suspended = true;
Expand Down

0 comments on commit c05989b

Please sign in to comment.