bcachefs: Deadlock prevention for ei_pagecache_lock
authorKent Overstreet <kent.overstreet@gmail.com>
Wed, 11 Nov 2020 17:33:12 +0000 (12:33 -0500)
committerKent Overstreet <kent.overstreet@linux.dev>
Sun, 22 Oct 2023 21:08:46 +0000 (17:08 -0400)
In the dio write path, when get_user_pages() invokes the fault handler
we have a recursive locking situation - we have to handle the lock
ordering ourselves or we have a deadlock: this patch addresses that by
checking for locking ordering violations and doing the unlock/relock
dance if necessary.

Signed-off-by: Kent Overstreet <kent.overstreet@gmail.com>
Signed-off-by: Kent Overstreet <kent.overstreet@linux.dev>
fs/bcachefs/fs-io.c
fs/bcachefs/fs.c
fs/bcachefs/fs.h

index 658d19c04b995d15d0ccc28d34112592329cc136..1afdd775ffb3b2c051e07e8fbc3540d356f84158 100644 (file)
@@ -44,6 +44,22 @@ static inline bool bio_full(struct bio *bio, unsigned len)
        return false;
 }
 
+static inline struct address_space *faults_disabled_mapping(void)
+{
+       return (void *) (((unsigned long) current->faults_disabled_mapping) & ~1UL);
+}
+
+static inline void set_fdm_dropped_locks(void)
+{
+       current->faults_disabled_mapping =
+               (void *) (((unsigned long) current->faults_disabled_mapping)|1);
+}
+
+static inline bool fdm_dropped_locks(void)
+{
+       return ((unsigned long) current->faults_disabled_mapping) & 1;
+}
+
 struct quota_res {
        u64                             sectors;
 };
@@ -501,10 +517,35 @@ static void bch2_set_page_dirty(struct bch_fs *c,
 vm_fault_t bch2_page_fault(struct vm_fault *vmf)
 {
        struct file *file = vmf->vma->vm_file;
+       struct address_space *mapping = file->f_mapping;
+       struct address_space *fdm = faults_disabled_mapping();
        struct bch_inode_info *inode = file_bch_inode(file);
        int ret;
 
+       if (fdm == mapping)
+               return VM_FAULT_SIGBUS;
+
+       /* Lock ordering: */
+       if (fdm > mapping) {
+               struct bch_inode_info *fdm_host = to_bch_ei(fdm->host);
+
+               if (bch2_pagecache_add_tryget(&inode->ei_pagecache_lock))
+                       goto got_lock;
+
+               bch2_pagecache_block_put(&fdm_host->ei_pagecache_lock);
+
+               bch2_pagecache_add_get(&inode->ei_pagecache_lock);
+               bch2_pagecache_add_put(&inode->ei_pagecache_lock);
+
+               bch2_pagecache_block_get(&fdm_host->ei_pagecache_lock);
+
+               /* Signal that lock has been dropped: */
+               set_fdm_dropped_locks();
+               return VM_FAULT_SIGBUS;
+       }
+
        bch2_pagecache_add_get(&inode->ei_pagecache_lock);
+got_lock:
        ret = filemap_fault(vmf);
        bch2_pagecache_add_put(&inode->ei_pagecache_lock);
 
@@ -1765,14 +1806,16 @@ static long bch2_dio_write_loop(struct dio_write *dio)
        struct bio *bio = &dio->op.wbio.bio;
        struct bvec_iter_all iter;
        struct bio_vec *bv;
-       unsigned unaligned;
-       bool sync = dio->sync;
+       unsigned unaligned, iter_count;
+       bool sync = dio->sync, dropped_locks;
        long ret;
 
        if (dio->loop)
                goto loop;
 
        while (1) {
+               iter_count = dio->iter.count;
+
                if (kthread)
                        kthread_use_mm(dio->mm);
                BUG_ON(current->faults_disabled_mapping);
@@ -1780,13 +1823,34 @@ static long bch2_dio_write_loop(struct dio_write *dio)
 
                ret = bio_iov_iter_get_pages(bio, &dio->iter);
 
+               dropped_locks = fdm_dropped_locks();
+
                current->faults_disabled_mapping = NULL;
                if (kthread)
                        kthread_unuse_mm(dio->mm);
 
+               /*
+                * If the fault handler returned an error but also signalled
+                * that it dropped & retook ei_pagecache_lock, we just need to
+                * re-shoot down the page cache and retry:
+                */
+               if (dropped_locks && ret)
+                       ret = 0;
+
                if (unlikely(ret < 0))
                        goto err;
 
+               if (unlikely(dropped_locks)) {
+                       ret = write_invalidate_inode_pages_range(mapping,
+                                       req->ki_pos,
+                                       req->ki_pos + iter_count - 1);
+                       if (unlikely(ret))
+                               goto err;
+
+                       if (!bio->bi_iter.bi_size)
+                               continue;
+               }
+
                unaligned = bio->bi_iter.bi_size & (block_bytes(c) - 1);
                bio->bi_iter.bi_size -= unaligned;
                iov_iter_revert(&dio->iter, unaligned);
index 3e3ab4e53f33f4515d1c404146aa375e51574839..231a5433577f149a155245f93746842bb4b608bd 100644 (file)
@@ -93,6 +93,11 @@ void bch2_pagecache_add_put(struct pagecache_lock *lock)
        __pagecache_lock_put(lock, 1);
 }
 
+bool bch2_pagecache_add_tryget(struct pagecache_lock *lock)
+{
+       return __pagecache_lock_tryget(lock, 1);
+}
+
 void bch2_pagecache_add_get(struct pagecache_lock *lock)
 {
        __pagecache_lock_get(lock, 1);
index b3a2993dd9bc73db36ac2256185cca7a1210f8f3..7c095b856b056673d17ed1655941670ff06f8278 100644 (file)
@@ -26,6 +26,7 @@ static inline void pagecache_lock_init(struct pagecache_lock *lock)
 }
 
 void bch2_pagecache_add_put(struct pagecache_lock *);
+bool bch2_pagecache_add_tryget(struct pagecache_lock *);
 void bch2_pagecache_add_get(struct pagecache_lock *);
 void bch2_pagecache_block_put(struct pagecache_lock *);
 void bch2_pagecache_block_get(struct pagecache_lock *);