md/raid5: use seqcount to protect access to shape in make_request.
authorNeilBrown <neilb@suse.de>
Tue, 27 Aug 2013 05:52:13 +0000 (15:52 +1000)
committerNeilBrown <neilb@suse.de>
Wed, 28 Aug 2013 06:58:36 +0000 (16:58 +1000)
make_request() access various shape parameters (raid_disks, chunk_size
etc) which might be changed by raid5_start_reshape().

If the later is called at and awkward time during the form, the wrong
stripe_head might be used.

So introduce a 'seqcount' and after finding a stripe_head make sure
there is no reason to expect that we got the wrong one.

Signed-off-by: NeilBrown <neilb@suse.de>
drivers/md/raid5.c
drivers/md/raid5.h

index d79ecd9032694a85bebcfe7e354c1b1bff146614..4263df11d5976413d2bf7d5ec173d40dd09674d6 100644 (file)
@@ -4408,8 +4408,10 @@ static void make_request(struct mddev *mddev, struct bio * bi)
        for (;logical_sector < last_sector; logical_sector += STRIPE_SECTORS) {
                DEFINE_WAIT(w);
                int previous;
+               int seq;
 
        retry:
+               seq = read_seqcount_begin(&conf->gen_lock);
                previous = 0;
                prepare_to_wait(&conf->wait_for_overlap, &w, TASK_UNINTERRUPTIBLE);
                if (unlikely(conf->reshape_progress != MaxSector)) {
@@ -4442,7 +4444,7 @@ static void make_request(struct mddev *mddev, struct bio * bi)
                                                  previous,
                                                  &dd_idx, NULL);
                pr_debug("raid456: make_request, sector %llu logical %llu\n",
-                       (unsigned long long)new_sector, 
+                       (unsigned long long)new_sector,
                        (unsigned long long)logical_sector);
 
                sh = get_active_stripe(conf, new_sector, previous,
@@ -4471,6 +4473,13 @@ static void make_request(struct mddev *mddev, struct bio * bi)
                                        goto retry;
                                }
                        }
+                       if (read_seqcount_retry(&conf->gen_lock, seq)) {
+                               /* Might have got the wrong stripe_head
+                                * by accident
+                                */
+                               release_stripe(sh);
+                               goto retry;
+                       }
 
                        if (rw == WRITE &&
                            logical_sector >= mddev->suspend_lo &&
@@ -5437,6 +5446,7 @@ static struct r5conf *setup_conf(struct mddev *mddev)
        if (alloc_thread_groups(conf, 0))
                goto abort;
        spin_lock_init(&conf->device_lock);
+       seqcount_init(&conf->gen_lock);
        init_waitqueue_head(&conf->wait_for_stripe);
        init_waitqueue_head(&conf->wait_for_overlap);
        INIT_LIST_HEAD(&conf->handle_list);
@@ -6249,6 +6259,7 @@ static int raid5_start_reshape(struct mddev *mddev)
 
        atomic_set(&conf->reshape_stripes, 0);
        spin_lock_irq(&conf->device_lock);
+       write_seqcount_begin(&conf->gen_lock);
        conf->previous_raid_disks = conf->raid_disks;
        conf->raid_disks += mddev->delta_disks;
        conf->prev_chunk_sectors = conf->chunk_sectors;
@@ -6265,6 +6276,7 @@ static int raid5_start_reshape(struct mddev *mddev)
        else
                conf->reshape_progress = 0;
        conf->reshape_safe = conf->reshape_progress;
+       write_seqcount_end(&conf->gen_lock);
        spin_unlock_irq(&conf->device_lock);
 
        /* Add some new drives, as many as will fit.
index 105366371fbfe9bdb86d73fb3593d39925f4470c..435b12d581657c852a236cde781a3600b2d159fb 100644 (file)
@@ -400,6 +400,7 @@ struct r5conf {
        int                     prev_chunk_sectors;
        int                     prev_algo;
        short                   generation; /* increments with every reshape */
+       seqcount_t              gen_lock;       /* lock against generation changes */
        unsigned long           reshape_checkpoint; /* Time we last updated
                                                     * metadata */
        long long               min_offset_diff; /* minimum difference between