md/r5cache: shift complex rmw from read path to write path

Write back cache requires a complex RMW mechanism, where old data is
read into dev->orig_page for prexor, and then xor is done with
dev->page. This logic is already implemented in the write path.

However, current read path is not awared of this requirement. When
the array is optimal, the RMW is not required, as the data are
read from raid disks. However, when the target stripe is degraded,
complex RMW is required to generate right data.

To keep read path as clean as possible, we handle read path by
flushing degraded, in-journal stripes before processing reads to
missing dev.

Specifically, when there is read requests to a degraded stripe
with data in journal, handle_stripe_fill() calls
r5c_make_stripe_write_out() and exits. Then handle_stripe_dirtying()
will do the complex RMW and flush the stripe to RAID disks. After
that, read requests are handled.

There is one more corner case when there is non-overwrite bio for
the missing (or out of sync) dev. handle_stripe_dirtying() will not
be able to process the non-overwrite bios without constructing the
data in handle_stripe_fill(). This is fixed by delaying non-overwrite
bios in handle_stripe_dirtying(). So handle_stripe_fill() works on
these bios after the stripe is flushed to raid disks.

Signed-off-by: Song Liu <songliubraving@fb.com>
Signed-off-by: Shaohua Li <shli@fb.com>
This commit is contained in:
Song Liu 2017-01-23 17:12:58 -08:00 committed by Shaohua Li
parent a85dd7b8df
commit 07e8336484
1 changed files with 45 additions and 4 deletions

View File

@ -2897,6 +2897,30 @@ sector_t raid5_compute_blocknr(struct stripe_head *sh, int i, int previous)
return r_sector; return r_sector;
} }
/*
* There are cases where we want handle_stripe_dirtying() and
* schedule_reconstruction() to delay towrite to some dev of a stripe.
*
* This function checks whether we want to delay the towrite. Specifically,
* we delay the towrite when:
*
* 1. degraded stripe has a non-overwrite to the missing dev, AND this
* stripe has data in journal (for other devices).
*
* In this case, when reading data for the non-overwrite dev, it is
* necessary to handle complex rmw of write back cache (prexor with
* orig_page, and xor with page). To keep read path simple, we would
* like to flush data in journal to RAID disks first, so complex rmw
* is handled in the write patch (handle_stripe_dirtying).
*
*/
static inline bool delay_towrite(struct r5dev *dev,
struct stripe_head_state *s)
{
return !test_bit(R5_OVERWRITE, &dev->flags) &&
!test_bit(R5_Insync, &dev->flags) && s->injournal;
}
static void static void
schedule_reconstruction(struct stripe_head *sh, struct stripe_head_state *s, schedule_reconstruction(struct stripe_head *sh, struct stripe_head_state *s,
int rcw, int expand) int rcw, int expand)
@ -2917,7 +2941,7 @@ schedule_reconstruction(struct stripe_head *sh, struct stripe_head_state *s,
for (i = disks; i--; ) { for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i]; struct r5dev *dev = &sh->dev[i];
if (dev->towrite) { if (dev->towrite && !delay_towrite(dev, s)) {
set_bit(R5_LOCKED, &dev->flags); set_bit(R5_LOCKED, &dev->flags);
set_bit(R5_Wantdrain, &dev->flags); set_bit(R5_Wantdrain, &dev->flags);
if (!expand) if (!expand)
@ -3494,10 +3518,26 @@ static void handle_stripe_fill(struct stripe_head *sh,
* midst of changing due to a write * midst of changing due to a write
*/ */
if (!test_bit(STRIPE_COMPUTE_RUN, &sh->state) && !sh->check_state && if (!test_bit(STRIPE_COMPUTE_RUN, &sh->state) && !sh->check_state &&
!sh->reconstruct_state) !sh->reconstruct_state) {
/*
* For degraded stripe with data in journal, do not handle
* read requests yet, instead, flush the stripe to raid
* disks first, this avoids handling complex rmw of write
* back cache (prexor with orig_page, and then xor with
* page) in the read path
*/
if (s->injournal && s->failed) {
if (test_bit(STRIPE_R5C_CACHING, &sh->state))
r5c_make_stripe_write_out(sh);
goto out;
}
for (i = disks; i--; ) for (i = disks; i--; )
if (fetch_block(sh, s, i, disks)) if (fetch_block(sh, s, i, disks))
break; break;
}
out:
set_bit(STRIPE_HANDLE, &sh->state); set_bit(STRIPE_HANDLE, &sh->state);
} }
@ -3653,7 +3693,8 @@ static int handle_stripe_dirtying(struct r5conf *conf,
} else for (i = disks; i--; ) { } else for (i = disks; i--; ) {
/* would I have to read this buffer for read_modify_write */ /* would I have to read this buffer for read_modify_write */
struct r5dev *dev = &sh->dev[i]; struct r5dev *dev = &sh->dev[i];
if ((dev->towrite || i == sh->pd_idx || i == sh->qd_idx || if (((dev->towrite && !delay_towrite(dev, s)) ||
i == sh->pd_idx || i == sh->qd_idx ||
test_bit(R5_InJournal, &dev->flags)) && test_bit(R5_InJournal, &dev->flags)) &&
!test_bit(R5_LOCKED, &dev->flags) && !test_bit(R5_LOCKED, &dev->flags) &&
!(uptodate_for_rmw(dev) || !(uptodate_for_rmw(dev) ||
@ -3717,7 +3758,7 @@ static int handle_stripe_dirtying(struct r5conf *conf,
for (i = disks; i--; ) { for (i = disks; i--; ) {
struct r5dev *dev = &sh->dev[i]; struct r5dev *dev = &sh->dev[i];
if ((dev->towrite || if (((dev->towrite && !delay_towrite(dev, s)) ||
i == sh->pd_idx || i == sh->qd_idx || i == sh->pd_idx || i == sh->qd_idx ||
test_bit(R5_InJournal, &dev->flags)) && test_bit(R5_InJournal, &dev->flags)) &&
!test_bit(R5_LOCKED, &dev->flags) && !test_bit(R5_LOCKED, &dev->flags) &&