From: NeilBrown <neilb@suse.de>
Subject: md/raid10: expedite the removal of lots of devices.
Git-commit: d787be4092e27728cb4c012bee9762098ef3c662
Patch-mainline: v4.8
References: bnc#768084

Currently each call to raid10_remove_disk() calls synchronize_rcu 
which can cause a delay of a few milliseconds.  For 100 devices this is
a few hundreds of milliseconds.  For 1000 it is a few seconds.

So hoist the 'synchronize_rcu()' up out of the loop.  This requires us
to call both before and after, but 2 isn't much worse than 1, but is
lots better than 1000.

Signed-off-by: Neil Brown <neilb@suse.de>

---
 drivers/md/md.c     |   26 ++++++++++++++++++++------
 drivers/md/md.h     |    5 +++++
 drivers/md/raid10.c |    3 ++-
 3 files changed, 27 insertions(+), 7 deletions(-)

--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -8219,6 +8219,8 @@ static int remove_and_add_spares(struct
 {
 	struct md_rdev *rdev;
 	int spares = 0;
+	int remove_some = 0;
+
 	int removed = 0;
 
 	rdev_for_each(rdev, mddev)
@@ -8229,13 +8231,25 @@ static int remove_and_add_spares(struct
 		     (!test_bit(In_sync, &rdev->flags) &&
 		      !test_bit(Journal, &rdev->flags))) &&
 		    atomic_read(&rdev->nr_pending)==0) {
-			if (mddev->pers->hot_remove_disk(
-				    mddev, rdev) == 0) {
-				sysfs_unlink_rdev(mddev, rdev);
-				rdev->raid_disk = -1;
-				removed++;
-			}
+			remove_some = 1;
+			set_bit(RemoveSynchronised, &rdev->flags);
 		}
+	if (remove_some) {
+		synchronize_rcu();
+		/* Now we know that no-one will take a new reference */
+		list_for_each_entry(rdev, &mddev->disks, same_set)
+			if (test_bit(RemoveSynchronised, &rdev->flags)) {
+				if (mddev->pers->hot_remove_disk(
+					    mddev, rdev) == 0) {
+					sysfs_unlink_rdev(mddev, rdev);
+					rdev->raid_disk = -1;
+					removed++;
+				}
+				clear_bit(RemoveSynchronised, &rdev->flags);
+			}
+		synchronize_rcu();
+		/* Now any temp reference that was taken is released */
+	}
 	if (removed && mddev->kobj.sd)
 		sysfs_notify(&mddev->kobj, NULL, "degraded");
 
--- a/drivers/md/md.h
+++ b/drivers/md/md.h
@@ -196,6 +196,11 @@ enum flag_bits {
 				 * than other devices in the array
 				 */
 	ClusterRemove,
+	RemoveSynchronised,	/* synchronize_rcu was called after
+				 * This device was known to be faulty,
+				 * so it is save to remove without
+				 * another call.
+				 */
 };
 
 static inline int is_badblock(struct md_rdev *rdev, sector_t s, int sectors,
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -1845,7 +1845,8 @@ static int raid10_remove_disk(struct mdd
 		goto abort;
 	}
 	*rdevp = NULL;
-	synchronize_rcu();
+	if (!test_bit(RemoveSynchronised, &rdev->flags))
+			synchronize_rcu();
 	if (atomic_read(&rdev->nr_pending)) {
 		/* lost the race, try later */
 		err = -EBUSY;
