diff options
author | Shaohua Li <shli@fb.com> | 2017-08-25 13:40:02 -0400 |
---|---|---|
committer | Shaohua Li <shli@fb.com> | 2017-09-05 13:57:49 -0400 |
commit | 3664847d95e60a9a943858b7800f8484669740fc (patch) | |
tree | 6a2cf324ca3376ab355e7aaa93f5aaf75466f813 /drivers/md/raid5.c | |
parent | e8a27f836f165c26f867ece7f31eb5c811692319 (diff) |
md/raid5: fix a race condition in stripe batch
We have a race condition in below scenario, say have 3 continuous stripes, sh1,
sh2 and sh3, sh1 is the stripe_head of sh2 and sh3:
CPU1 CPU2 CPU3
handle_stripe(sh3)
stripe_add_to_batch_list(sh3)
-> lock(sh2, sh3)
-> lock batch_lock(sh1)
-> add sh3 to batch_list of sh1
-> unlock batch_lock(sh1)
clear_batch_ready(sh1)
-> lock(sh1) and batch_lock(sh1)
-> clear STRIPE_BATCH_READY for all stripes in batch_list
-> unlock(sh1) and batch_lock(sh1)
->clear_batch_ready(sh3)
-->test_and_clear_bit(STRIPE_BATCH_READY, sh3)
--->return 0 as sh->batch == NULL
-> sh3->batch_head = sh1
-> unlock (sh2, sh3)
In CPU1, handle_stripe will continue handle sh3 even it's in batch stripe list
of sh1. By moving sh3->batch_head assignment in to batch_lock, we make it
impossible to clear STRIPE_BATCH_READY before batch_head is set.
Thanks Stephane for helping debug this tricky issue.
Reported-and-tested-by: Stephane Thiell <sthiell@stanford.edu>
Cc: stable@vger.kernel.org (v4.1+)
Signed-off-by: Shaohua Li <shli@fb.com>
Diffstat (limited to 'drivers/md/raid5.c')
-rw-r--r-- | drivers/md/raid5.c | 10 |
1 files changed, 8 insertions, 2 deletions
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 049a958d3c1e..90414a4e0d49 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c | |||
@@ -811,6 +811,14 @@ static void stripe_add_to_batch_list(struct r5conf *conf, struct stripe_head *sh | |||
811 | spin_unlock(&head->batch_head->batch_lock); | 811 | spin_unlock(&head->batch_head->batch_lock); |
812 | goto unlock_out; | 812 | goto unlock_out; |
813 | } | 813 | } |
814 | /* | ||
815 | * We must assign batch_head of this stripe within the | ||
816 | * batch_lock, otherwise clear_batch_ready of batch head | ||
817 | * stripe could clear BATCH_READY bit of this stripe and | ||
818 | * this stripe->batch_head doesn't get assigned, which | ||
819 | * could confuse clear_batch_ready for this stripe | ||
820 | */ | ||
821 | sh->batch_head = head->batch_head; | ||
814 | 822 | ||
815 | /* | 823 | /* |
816 | * at this point, head's BATCH_READY could be cleared, but we | 824 | * at this point, head's BATCH_READY could be cleared, but we |
@@ -818,8 +826,6 @@ static void stripe_add_to_batch_list(struct r5conf *conf, struct stripe_head *sh | |||
818 | */ | 826 | */ |
819 | list_add(&sh->batch_list, &head->batch_list); | 827 | list_add(&sh->batch_list, &head->batch_list); |
820 | spin_unlock(&head->batch_head->batch_lock); | 828 | spin_unlock(&head->batch_head->batch_lock); |
821 | |||
822 | sh->batch_head = head->batch_head; | ||
823 | } else { | 829 | } else { |
824 | head->batch_head = head; | 830 | head->batch_head = head; |
825 | sh->batch_head = head->batch_head; | 831 | sh->batch_head = head->batch_head; |