All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] dm-mirror: fix crash with mirror recovery and discard
@ 2012-07-06 18:56 Mikulas Patocka
  2012-07-06 19:33 ` Brassow Jonathan
  2012-07-06 19:49 ` Mike Snitzer
  0 siblings, 2 replies; 7+ messages in thread
From: Mikulas Patocka @ 2012-07-06 18:56 UTC (permalink / raw)
  To: Alasdair G. Kergon, Zdenek Kabelac; +Cc: dm-devel

dm-mirror: fix crash with mirror recovery and discard

This patch fixes bug 837607 - crash when mirror recovery happens and a
discard request is sent.

Generally, the following sequence happens during mirror synchronization:
- function do_recovery is called
- do_recovery calls dm_rh_recovery_prepare
- dm_rh_recovery_prepare uses a semaphore to limit the number
  simultaneously recovered regions (by default the semaphore value is 1,
  so only one region at a time is recovered)
- dm_rh_recovery_prepare calls __rh_recovery_prepare,
  __rh_recovery_prepare asks the log driver for the next region to
  recover. Then, it sets the region state to DM_RH_RECOVERING. If there
  are no pending I/Os on this region, the region is added to
  quiesced_regions list. If there are pending I/Os, the region is not
  added to any list. It is added to the quiesced_regions list later (by
  dm_rh_dec function) when all I/Os finish.
- when the region is on quiesced_regions list, there are no I/Os in
  flight on this region. The region popped from the list in
  dm_rh_recovery_start function. Then, kcopyd job is started in the
  recover function.
- when the kcopyd job finishes, recovery_complete is called. It calls
  dm_rh_recovery_end. dm_rh_recovery_end adds the region to
  recovered_regions or failed_recovered_regions list (depending on
  whether the copy operation was successful or not).

The above mechanism assumes that if the region is in DM_RH_RECOVERING
state, no new I/Os are started on this region. When I/O is started,
dm_rh_inc_pending is called, which increases reg->pending count. When
I/O is finished, dm_rh_dec is called. It decreases reg->pending count.
If the count is zero and the region was in DM_RH_RECOVERING state,
dm_rh_dec adds it to the quiesced_regions list.s

Consequently, if we call dm_rh_inc_pending/dm_rh_dec while the region is
in DM_RH_RECOVERING state, it could be added to quiesced_regions list
multiple times or it could be added to this list when kcopyd is copying
data (it is assumed that the region is not on any list while kcopyd does
its jobs). This results in memory corruption and crash.

There already exist bypasses for REQ_FLUSH requests: REQ_FLUSH requests
do not belong to any region, so they are always added to the sync list
in do_writes. dm_rh_inc_pending does not increase count for REQ_FLUSH
requests. In mirror_end_io, dm_rh_dec is never called for REQ_FLUSH
requests. These bypasses avoid the crash possibility described above.

These bypasses were improperly implemented for REQ_DISCARD. In
do_writes, REQ_DISCARD requests is always added on sync queue and
immediatelly dispatched (even if the region is in DM_RH_RECOVERING).
However, dm_rh_inc and dm_rh_dec is called for REQ_DISCARD resusts.
So it violates the rule that no I/Os are started on DM_RH_RECOVERING
regions, and causes the list corruption described above.

This patch changes it so that REQ_DISCARD requests follow the same path
as REQ_FLUSH. This avoids the crash.

Note that we can't guarantee that REQ_DISCARD zeroes the data even if
the underlying disks support zero on discard, so this patch also sets
ti->discard_zeroes_data_unsupported.

Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@kernel.org

---
 drivers/md/dm-raid1.c       |    3 ++-
 drivers/md/dm-region-hash.c |    5 ++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

Index: linux-3.4.4-fast/drivers/md/dm-raid1.c
===================================================================
--- linux-3.4.4-fast.orig/drivers/md/dm-raid1.c	2012-07-06 19:01:27.000000000 +0200
+++ linux-3.4.4-fast/drivers/md/dm-raid1.c	2012-07-06 19:03:53.000000000 +0200
@@ -1091,6 +1091,7 @@ static int mirror_ctr(struct dm_target *
 
 	ti->num_flush_requests = 1;
 	ti->num_discard_requests = 1;
+	ti->discard_zeroes_data_unsupported = 1;
 
 	ms->kmirrord_wq = alloc_workqueue("kmirrord",
 					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
@@ -1221,7 +1222,7 @@ static int mirror_end_io(struct dm_targe
 	 * We need to dec pending if this was a write.
 	 */
 	if (rw == WRITE) {
-		if (!(bio->bi_rw & REQ_FLUSH))
+		if (!(bio->bi_rw & (REQ_FLUSH | REQ_DISCARD)))
 			dm_rh_dec(ms->rh, map_context->ll);
 		return error;
 	}
Index: linux-3.4.4-fast/drivers/md/dm-region-hash.c
===================================================================
--- linux-3.4.4-fast.orig/drivers/md/dm-region-hash.c	2012-07-06 19:02:28.000000000 +0200
+++ linux-3.4.4-fast/drivers/md/dm-region-hash.c	2012-07-06 19:02:57.000000000 +0200
@@ -404,6 +404,9 @@ void dm_rh_mark_nosync(struct dm_region_
 		return;
 	}
 
+	if (bio->bi_rw & REQ_DISCARD)
+		return;
+
 	/* We must inform the log that the sync count has changed. */
 	log->type->set_region_sync(log, region, 0);
 
@@ -524,7 +527,7 @@ void dm_rh_inc_pending(struct dm_region_
 	struct bio *bio;
 
 	for (bio = bios->head; bio; bio = bio->bi_next) {
-		if (bio->bi_rw & REQ_FLUSH)
+		if (bio->bi_rw & (REQ_FLUSH | REQ_FLUSH))
 			continue;
 		rh_inc(rh, dm_rh_bio_to_region(rh, bio));
 	}

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] dm-mirror: fix crash with mirror recovery and discard
  2012-07-06 18:56 [PATCH] dm-mirror: fix crash with mirror recovery and discard Mikulas Patocka
@ 2012-07-06 19:33 ` Brassow Jonathan
  2012-07-06 19:49 ` Mike Snitzer
  1 sibling, 0 replies; 7+ messages in thread
From: Brassow Jonathan @ 2012-07-06 19:33 UTC (permalink / raw)
  To: device-mapper development; +Cc: Alasdair G. Kergon, Zdenek Kabelac


On Jul 6, 2012, at 1:56 PM, Mikulas Patocka wrote:

> 	for (bio = bios->head; bio; bio = bio->bi_next) {
> -		if (bio->bi_rw & REQ_FLUSH)
> +		if (bio->bi_rw & (REQ_FLUSH | REQ_FLUSH))
> 			continue;

Woops?

 brassow

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: dm-mirror: fix crash with mirror recovery and discard
  2012-07-06 18:56 [PATCH] dm-mirror: fix crash with mirror recovery and discard Mikulas Patocka
  2012-07-06 19:33 ` Brassow Jonathan
@ 2012-07-06 19:49 ` Mike Snitzer
  2012-07-06 20:38   ` Mikulas Patocka
  1 sibling, 1 reply; 7+ messages in thread
From: Mike Snitzer @ 2012-07-06 19:49 UTC (permalink / raw)
  To: Mikulas Patocka; +Cc: dm-devel, Alasdair G. Kergon, Zdenek Kabelac

On Fri, Jul 06 2012 at  2:56pm -0400,
Mikulas Patocka <mpatocka@redhat.com> wrote:

> dm-mirror: fix crash with mirror recovery and discard
...
> These bypasses were improperly implemented for REQ_DISCARD. In
> do_writes, REQ_DISCARD requests is always added on sync queue and
> immediatelly dispatched (even if the region is in DM_RH_RECOVERING).
> However, dm_rh_inc and dm_rh_dec is called for REQ_DISCARD resusts.
> So it violates the rule that no I/Os are started on DM_RH_RECOVERING
> regions, and causes the list corruption described above.

Yeap, my fault.  Thanks for sorting this out.  So this bug has existed
since: 5fc2ffe v2.6.38-rc1 dm raid1: support discard

Best to reference that in the patch header so stable knows how far back
this issue goes.
 
> This patch changes it so that REQ_DISCARD requests follow the same path
> as REQ_FLUSH. This avoids the crash.
> 
> Note that we can't guarantee that REQ_DISCARD zeroes the data even if
> the underlying disks support zero on discard, so this patch also sets
> ti->discard_zeroes_data_unsupported.

...

> Index: linux-3.4.4-fast/drivers/md/dm-raid1.c
> ===================================================================
> --- linux-3.4.4-fast.orig/drivers/md/dm-raid1.c	2012-07-06 19:01:27.000000000 +0200
> +++ linux-3.4.4-fast/drivers/md/dm-raid1.c	2012-07-06 19:03:53.000000000 +0200
> @@ -1091,6 +1091,7 @@ static int mirror_ctr(struct dm_target *
>  
>  	ti->num_flush_requests = 1;
>  	ti->num_discard_requests = 1;
> +	ti->discard_zeroes_data_unsupported = 1;
>  
>  	ms->kmirrord_wq = alloc_workqueue("kmirrord",
>  					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);

This should be split out to a separate patch and properly justified in
the patch header.  Is there something unique to dm-mirror that renders
the underlying device's zeroing unreliable?

> Index: linux-3.4.4-fast/drivers/md/dm-region-hash.c
> ===================================================================
> --- linux-3.4.4-fast.orig/drivers/md/dm-region-hash.c	2012-07-06 19:02:28.000000000 +0200
> +++ linux-3.4.4-fast/drivers/md/dm-region-hash.c	2012-07-06 19:02:57.000000000 +0200
> @@ -524,7 +527,7 @@ void dm_rh_inc_pending(struct dm_region_
>  	struct bio *bio;
>  
>  	for (bio = bios->head; bio; bio = bio->bi_next) {
> -		if (bio->bi_rw & REQ_FLUSH)
> +		if (bio->bi_rw & (REQ_FLUSH | REQ_FLUSH))
>  			continue;
>  		rh_inc(rh, dm_rh_bio_to_region(rh, bio));
>  	}

Typo, you meant: if (bio->bi_rw & (REQ_FLUSH | REQ_DISCARD))

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: dm-mirror: fix crash with mirror recovery and discard
  2012-07-06 19:49 ` Mike Snitzer
@ 2012-07-06 20:38   ` Mikulas Patocka
  2012-07-06 20:44     ` Mikulas Patocka
  2012-07-09 12:16     ` Mike Snitzer
  0 siblings, 2 replies; 7+ messages in thread
From: Mikulas Patocka @ 2012-07-06 20:38 UTC (permalink / raw)
  To: Mike Snitzer; +Cc: dm-devel, Alasdair G. Kergon, Zdenek Kabelac

> > +	ti->discard_zeroes_data_unsupported = 1;
> >  
> >  	ms->kmirrord_wq = alloc_workqueue("kmirrord",
> >  					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
> 
> This should be split out to a separate patch and properly justified in
> the patch header.  Is there something unique to dm-mirror that renders
> the underlying device's zeroing unreliable?

There are two possible approaches to handling REQ_DISCARD

1. treat REQ_DISCARD as REQ_FLUSH (this is what the patch does) --- i.e. 
do not synchronize it with region states, do not set mirror error on 
failure. In this mode we must assume that there are uninitialized data 
after a flush.

For example, if there is a region that is being resynchronized and we send 
REQ_DISCARD that overlaps this region, there is no guarantee that data in 
this region were zeroed. 

- kcopyd reads a few blocks for resynchronization
- REQ_DISCARD is sent to both mirror legs, both disks overwrites the area 
with zeroes
- kcopyd writes those blocks to the other leg => the blocks are no longer 
zero despite REQ_DISCARD being sent


2. treat REQ_DISCARD as writes (i.e. synchronize it with region states, 
wait until resynchronization finishes, etc.) --- it is possible to do it 
this way to, but if we do it this way, we have to split REQ_DISCARD on 
region boundaries (it is currently split only on target boundaries, 
which is insufficient).


Mikulas

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: dm-mirror: fix crash with mirror recovery and discard
  2012-07-06 20:38   ` Mikulas Patocka
@ 2012-07-06 20:44     ` Mikulas Patocka
  2012-07-09 12:16     ` Mike Snitzer
  1 sibling, 0 replies; 7+ messages in thread
From: Mikulas Patocka @ 2012-07-06 20:44 UTC (permalink / raw)
  To: Mike Snitzer; +Cc: dm-devel, Alasdair G. Kergon, Zdenek Kabelac



On Fri, 6 Jul 2012, Mikulas Patocka wrote:

> > > +	ti->discard_zeroes_data_unsupported = 1;
> > >  
> > >  	ms->kmirrord_wq = alloc_workqueue("kmirrord",
> > >  					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
> > 
> > This should be split out to a separate patch and properly justified in
> > the patch header.  Is there something unique to dm-mirror that renders
> > the underlying device's zeroing unreliable?
> 
> There are two possible approaches to handling REQ_DISCARD
> 
> 1. treat REQ_DISCARD as REQ_FLUSH (this is what the patch does) --- i.e. 
> do not synchronize it with region states, do not set mirror error on 
> failure. In this mode we must assume that there are uninitialized data 
> after a flush.

s/flush/discard/

> For example, if there is a region that is being resynchronized and we send 
> REQ_DISCARD that overlaps this region, there is no guarantee that data in 
> this region were zeroed. 
> 
> - kcopyd reads a few blocks for resynchronization
> - REQ_DISCARD is sent to both mirror legs, both disks overwrites the area 
> with zeroes
> - kcopyd writes those blocks to the other leg => the blocks are no longer 
> zero despite REQ_DISCARD being sent
> 
> 
> 2. treat REQ_DISCARD as writes (i.e. synchronize it with region states, 
> wait until resynchronization finishes, etc.) --- it is possible to do it 
> this way to, but if we do it this way, we have to split REQ_DISCARD on 
> region boundaries (it is currently split only on target boundaries, 
> which is insufficient).
> 
> 
> Mikulas
> 

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: dm-mirror: fix crash with mirror recovery and discard
  2012-07-06 20:38   ` Mikulas Patocka
  2012-07-06 20:44     ` Mikulas Patocka
@ 2012-07-09 12:16     ` Mike Snitzer
  2012-07-09 23:12       ` Mikulas Patocka
  1 sibling, 1 reply; 7+ messages in thread
From: Mike Snitzer @ 2012-07-09 12:16 UTC (permalink / raw)
  To: Mikulas Patocka; +Cc: dm-devel, Alasdair G. Kergon, Zdenek Kabelac

On Fri, Jul 06 2012 at  4:38pm -0400,
Mikulas Patocka <mpatocka@redhat.com> wrote:

> > > +	ti->discard_zeroes_data_unsupported = 1;
> > >  
> > >  	ms->kmirrord_wq = alloc_workqueue("kmirrord",
> > >  					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
> > 
> > This should be split out to a separate patch and properly justified in
> > the patch header.  Is there something unique to dm-mirror that renders
> > the underlying device's zeroing unreliable?
> 
> There are two possible approaches to handling REQ_DISCARD
> 
> 1. treat REQ_DISCARD as REQ_FLUSH (this is what the patch does) --- i.e. 
> do not synchronize it with region states, do not set mirror error on 
> failure. In this mode we must assume that there are uninitialized data 
> after a flush.
> 
> For example, if there is a region that is being resynchronized and we send 
> REQ_DISCARD that overlaps this region, there is no guarantee that data in 
> this region were zeroed. 
> 
> - kcopyd reads a few blocks for resynchronization
> - REQ_DISCARD is sent to both mirror legs, both disks overwrites the area 
> with zeroes
> - kcopyd writes those blocks to the other leg => the blocks are no longer 
> zero despite REQ_DISCARD being sent

OK, thanks, but my point still stands: this is worthy of a separate
patch (with the same type of backround you provided above).

> 2. treat REQ_DISCARD as writes (i.e. synchronize it with region states, 
> wait until resynchronization finishes, etc.) --- it is possible to do it 
> this way to, but if we do it this way, we have to split REQ_DISCARD on 
> region boundaries (it is currently split only on target boundaries, 
> which is insufficient).

I think discards should be treated as writes.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: dm-mirror: fix crash with mirror recovery and discard
  2012-07-09 12:16     ` Mike Snitzer
@ 2012-07-09 23:12       ` Mikulas Patocka
  0 siblings, 0 replies; 7+ messages in thread
From: Mikulas Patocka @ 2012-07-09 23:12 UTC (permalink / raw)
  To: Mike Snitzer; +Cc: dm-devel, Alasdair G. Kergon, Zdenek Kabelac



On Mon, 9 Jul 2012, Mike Snitzer wrote:

> On Fri, Jul 06 2012 at  4:38pm -0400,
> Mikulas Patocka <mpatocka@redhat.com> wrote:
> 
> > > > +	ti->discard_zeroes_data_unsupported = 1;
> > > >  
> > > >  	ms->kmirrord_wq = alloc_workqueue("kmirrord",
> > > >  					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);
> > > 
> > > This should be split out to a separate patch and properly justified in
> > > the patch header.  Is there something unique to dm-mirror that renders
> > > the underlying device's zeroing unreliable?
> > 
> > There are two possible approaches to handling REQ_DISCARD
> > 
> > 1. treat REQ_DISCARD as REQ_FLUSH (this is what the patch does) --- i.e. 
> > do not synchronize it with region states, do not set mirror error on 
> > failure. In this mode we must assume that there are uninitialized data 
> > after a flush.
> > 
> > For example, if there is a region that is being resynchronized and we send 
> > REQ_DISCARD that overlaps this region, there is no guarantee that data in 
> > this region were zeroed. 
> > 
> > - kcopyd reads a few blocks for resynchronization
> > - REQ_DISCARD is sent to both mirror legs, both disks overwrites the area 
> > with zeroes
> > - kcopyd writes those blocks to the other leg => the blocks are no longer 
> > zero despite REQ_DISCARD being sent
> 
> OK, thanks, but my point still stands: this is worthy of a separate
> patch (with the same type of backround you provided above).
> 
> > 2. treat REQ_DISCARD as writes (i.e. synchronize it with region states, 
> > wait until resynchronization finishes, etc.) --- it is possible to do it 
> > this way to, but if we do it this way, we have to split REQ_DISCARD on 
> > region boundaries (it is currently split only on target boundaries, 
> > which is insufficient).
> 
> I think discards should be treated as writes.

You can use this to fix the bug and treat discards as writes. But unlike 
the previous patch, it causes discard splitting on region boundary.

Mikulas

---
 drivers/md/dm.c               |    5 ++++-
 include/linux/device-mapper.h |    6 ++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

Index: linux-3.4.4-fast/drivers/md/dm.c
===================================================================
--- linux-3.4.4-fast.orig/drivers/md/dm.c	2012-07-05 23:55:38.000000000 +0200
+++ linux-3.4.4-fast/drivers/md/dm.c	2012-07-05 23:56:41.000000000 +0200
@@ -1244,7 +1244,10 @@ static int __clone_and_map_discard(struc
 		if (!ti->num_discard_requests)
 			return -EOPNOTSUPP;
 
-		len = min(ci->sector_count, max_io_len_target_boundary(ci->sector, ti));
+		if (!ti->split_discard_requests)
+			len = min(ci->sector_count, max_io_len_target_boundary(ci->sector, ti));
+		else
+			len = min(ci->sector_count, max_io_len(ci->sector, ti));
 
 		__issue_target_requests(ci, ti, ti->num_discard_requests, len);
 
Index: linux-3.4.4-fast/include/linux/device-mapper.h
===================================================================
--- linux-3.4.4-fast.orig/include/linux/device-mapper.h	2012-07-05 23:53:47.000000000 +0200
+++ linux-3.4.4-fast/include/linux/device-mapper.h	2012-07-05 23:59:37.000000000 +0200
@@ -220,6 +220,12 @@ struct dm_target {
 	unsigned discards_supported:1;
 
 	/*
+	 * Set if the target required discard request to be split
+	 * on max_io_len boundary.
+	 */
+	unsigned split_discard_requests:1;
+
+	/*
 	 * Set if this target does not return zeroes on discarded blocks.
 	 */
 	unsigned discard_zeroes_data_unsupported:1;
---
 drivers/md/dm-raid1.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Index: linux-3.4.4-fast/drivers/md/dm-raid1.c
===================================================================
--- linux-3.4.4-fast.orig/drivers/md/dm-raid1.c	2012-07-06 00:00:12.000000000 +0200
+++ linux-3.4.4-fast/drivers/md/dm-raid1.c	2012-07-06 00:01:54.000000000 +0200
@@ -677,8 +677,7 @@ static void do_writes(struct mirror_set 
 	bio_list_init(&requeue);
 
 	while ((bio = bio_list_pop(writes))) {
-		if ((bio->bi_rw & REQ_FLUSH) ||
-		    (bio->bi_rw & REQ_DISCARD)) {
+		if (bio->bi_rw & REQ_FLUSH) {
 			bio_list_add(&sync, bio);
 			continue;
 		}
@@ -1091,6 +1090,7 @@ static int mirror_ctr(struct dm_target *
 
 	ti->num_flush_requests = 1;
 	ti->num_discard_requests = 1;
+	ti->split_discard_requests = 1;
 
 	ms->kmirrord_wq = alloc_workqueue("kmirrord",
 					  WQ_NON_REENTRANT | WQ_MEM_RECLAIM, 0);

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2012-07-09 23:12 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2012-07-06 18:56 [PATCH] dm-mirror: fix crash with mirror recovery and discard Mikulas Patocka
2012-07-06 19:33 ` Brassow Jonathan
2012-07-06 19:49 ` Mike Snitzer
2012-07-06 20:38   ` Mikulas Patocka
2012-07-06 20:44     ` Mikulas Patocka
2012-07-09 12:16     ` Mike Snitzer
2012-07-09 23:12       ` Mikulas Patocka

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.