Normally if a page is locked by page_lock() other than
btrfs_page_start_writer_lock(), it should be passed as @locked_page for
various extent_clear_unlock_delalloc() call sites.
But there are quite some call sites in compression path, where we
intentionally call extent_clear_unlock_delalloc() with @locked_page ==
NULL.
This will abuse extent_clear_unlock_delalloc() to unlock @locked_page.
This works fine for regular page size, but not really for subpage, as if
a page is locked by btrfs_page_start_writer_lock() it will have proper
@writers value increased.
If a page not locked by btrfs_page_start_writer_lock() we will underflow
the value.
But thankfully, for such pages its @writers value should be zero, and
we can use that to distinguish page locked by
btrfs_page_start_writer_lock() and @locked_page by delalloc.
Signed-off-by: Qu Wenruo <redacted>
---
fs/btrfs/subpage.c | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c
index a61aa33aeeee..72d5d4712933 100644
--- a/fs/btrfs/subpage.c
+++ b/fs/btrfs/subpage.c
@@ -245,11 +245,33 @@ bool btrfs_subpage_end_and_test_writer(const struct btrfs_fs_info *fs_info,
{
struct btrfs_subpage *subpage = (struct btrfs_subpage *)page->private;
const int nbits = (len >> fs_info->sectorsize_bits);
+ unsigned long flags;
+ bool ret;
btrfs_subpage_assert(fs_info, page, start, len);
+ /* Will do two atomic checks, no longer atomic and need spinlock */
+ spin_lock_irqsave(&subpage->lock, flags);
+
+ /*
+ * In compression path, we have extent_clear_unlock_delalloc() call
+ * sites which intentionally pass @locked_page == NULL to unlock
+ * the locked page.
+ *
+ * In that case, @locked_page should has no writer counts, as it's
+ * not locked by btrfs_page_start_writer_lock().
+ * For such case, we just return true so that
+ * btrfs_page_end_writer_lock() will unlock the page.
+ */
+ if (atomic_read(&subpage->writers) == 0) {
+ ret = true;
+ spin_unlock_irqrestore(&subpage->lock, flags);
+ return ret;
+ }
ASSERT(atomic_read(&subpage->writers) >= nbits);
- return atomic_sub_and_test(nbits, &subpage->writers);
+ ret = atomic_sub_and_test(nbits, &subpage->writers);
+ spin_unlock_irqrestore(&subpage->lock, flags);
+ return ret;
}
/*--
2.32.0