summaryrefslogtreecommitdiff
path: root/fs/iomap/bio.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/iomap/bio.c')
-rw-r--r--fs/iomap/bio.c51
1 files changed, 50 insertions, 1 deletions
diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c
index fc045f2e4c45..edd908183058 100644
--- a/fs/iomap/bio.c
+++ b/fs/iomap/bio.c
@@ -8,7 +8,10 @@
#include "internal.h"
#include "trace.h"
-static void iomap_read_end_io(struct bio *bio)
+static DEFINE_SPINLOCK(failed_read_lock);
+static struct bio_list failed_read_list = BIO_EMPTY_LIST;
+
+static void __iomap_read_end_io(struct bio *bio)
{
int error = blk_status_to_errno(bio->bi_status);
struct folio_iter fi;
@@ -18,6 +21,52 @@ static void iomap_read_end_io(struct bio *bio)
bio_put(bio);
}
+static void
+iomap_fail_reads(
+ struct work_struct *work)
+{
+ struct bio *bio;
+ struct bio_list tmp = BIO_EMPTY_LIST;
+ unsigned long flags;
+
+ spin_lock_irqsave(&failed_read_lock, flags);
+ bio_list_merge_init(&tmp, &failed_read_list);
+ spin_unlock_irqrestore(&failed_read_lock, flags);
+
+ while ((bio = bio_list_pop(&tmp)) != NULL) {
+ __iomap_read_end_io(bio);
+ cond_resched();
+ }
+}
+
+static DECLARE_WORK(failed_read_work, iomap_fail_reads);
+
+static void iomap_fail_buffered_read(struct bio *bio)
+{
+ unsigned long flags;
+
+ /*
+ * Bounce I/O errors to a workqueue to avoid nested i_lock acquisitions
+ * in the fserror code. The caller no longer owns the bio reference
+ * after the spinlock drops.
+ */
+ spin_lock_irqsave(&failed_read_lock, flags);
+ if (bio_list_empty(&failed_read_list))
+ WARN_ON_ONCE(!schedule_work(&failed_read_work));
+ bio_list_add(&failed_read_list, bio);
+ spin_unlock_irqrestore(&failed_read_lock, flags);
+}
+
+static void iomap_read_end_io(struct bio *bio)
+{
+ if (bio->bi_status) {
+ iomap_fail_buffered_read(bio);
+ return;
+ }
+
+ __iomap_read_end_io(bio);
+}
+
static void iomap_bio_submit_read(struct iomap_read_folio_ctx *ctx)
{
struct bio *bio = ctx->read_ctx;