The invalidate_remove() function has incomplete logic for handling write
hit bios after cache invalidation. It sets up the remapping for the
overwrite_bio but then drops it immediately without submission, causing
write operations to hang.

Fix by adding a new invalidate_committed() continuation that submits
the remapped writes to the cache origin after metadata commit completes,
while using the overwrite_endio hook to ensure proper completion
sequencing. This maintains existing coherency. Also improve error
handling in invalidate_complete() to preserve the original error status
instead of using bio_io_error() unconditionally.

Fixes: b29d4986d0da ("dm cache: significant rework to leverage 
dm-bio-prison-v2")
Signed-off-by: Ming-Hung Tsai <[email protected]>
---
 drivers/md/dm-cache-target.c | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index ac9901033073..8b62fe69722d 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -1467,8 +1467,14 @@ static void invalidate_complete(struct 
dm_cache_migration *mg, bool success)
                        free_prison_cell(cache, mg->cell);
        }
 
-       if (!success && mg->overwrite_bio)
-               bio_io_error(mg->overwrite_bio);
+       if (mg->overwrite_bio) {
+               // Set generic error if the bio hasn't been issued yet,
+               // e.g., invalidation or metadata commit failed before bio
+               // submission. Otherwise preserve the bio's own error status.
+               if (!success && !mg->overwrite_bio->bi_status)
+                       mg->overwrite_bio->bi_status = BLK_STS_IOERR;
+               bio_endio(mg->overwrite_bio);
+       }
 
        free_migration(mg);
        defer_bios(cache, &bios);
@@ -1508,6 +1514,22 @@ static int invalidate_cblock(struct cache *cache, 
dm_cblock_t cblock)
        return r;
 }
 
+static void invalidate_committed(struct work_struct *ws)
+{
+       struct dm_cache_migration *mg = ws_to_mg(ws);
+       struct cache *cache = mg->cache;
+       struct bio *bio = mg->overwrite_bio;
+       struct per_bio_data *pb = get_per_bio_data(bio);
+
+       if (mg->k.input)
+               invalidate_complete(mg, false);
+
+       init_continuation(&mg->k, invalidate_completed);
+       remap_to_origin_clear_discard(cache, bio, mg->invalidate_oblock);
+       dm_hook_bio(&pb->hook_info, bio, overwrite_endio, mg);
+       dm_submit_bio_remap(bio, NULL);
+}
+
 static void invalidate_remove(struct work_struct *ws)
 {
        int r;
@@ -1520,10 +1542,8 @@ static void invalidate_remove(struct work_struct *ws)
                return;
        }
 
-       init_continuation(&mg->k, invalidate_completed);
+       init_continuation(&mg->k, invalidate_committed);
        continue_after_commit(&cache->committer, &mg->k);
-       remap_to_origin_clear_discard(cache, mg->overwrite_bio, 
mg->invalidate_oblock);
-       mg->overwrite_bio = NULL;
        schedule_commit(&cache->committer);
 }
 
-- 
2.49.0


Reply via email to