]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
dm cache: fix write hang in passthrough mode
authorMing-Hung Tsai <mtsai@redhat.com>
Mon, 9 Feb 2026 07:54:07 +0000 (15:54 +0800)
committerMikulas Patocka <mpatocka@redhat.com>
Mon, 2 Mar 2026 15:49:35 +0000 (16:49 +0100)
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 <mtsai@redhat.com>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
drivers/md/dm-cache-target.c

index b608e88acd5118ebc62dcc702b8cf91e324207d0..d3ef88b859ab36dbff24cdd341c9bf9cb5c6151e 100644 (file)
@@ -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);
 }