Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[PATCH] Fix try_to_free_buffer() locking

Fix commit ecdfc9787fe527491baefc22dce8b2dbd5b2908d

Not to put too fine a point on it, but in a nutshell...

__set_page_dirty_buffers() | try_to_free_buffers()
---------------------------+---------------------------
| spin_lock(private_lock);
| drop_bufers()
| spin_unlock(private_lock);
spin_lock(private_lock) |
!page_has_buffers() |
spin_unlock(private_lock) |
SetPageDirty() |
| cancel_dirty_page()

oops!

Signed-off-by: Nick Piggin <npiggin@suse.de>
Acked-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Nick Piggin and committed by
Linus Torvalds
87df7241 4cbf2aa3

+5 -1
+5 -1
fs/buffer.c
··· 2844 2844 2845 2845 spin_lock(&mapping->private_lock); 2846 2846 ret = drop_buffers(page, &buffers_to_free); 2847 - spin_unlock(&mapping->private_lock); 2848 2847 2849 2848 /* 2850 2849 * If the filesystem writes its buffers by hand (eg ext3) ··· 2854 2855 * Also, during truncate, discard_buffer will have marked all 2855 2856 * the page's buffers clean. We discover that here and clean 2856 2857 * the page also. 2858 + * 2859 + * private_lock must be held over this entire operation in order 2860 + * to synchronise against __set_page_dirty_buffers and prevent the 2861 + * dirty bit from being lost. 2857 2862 */ 2858 2863 if (ret) 2859 2864 cancel_dirty_page(page, PAGE_CACHE_SIZE); 2865 + spin_unlock(&mapping->private_lock); 2860 2866 out: 2861 2867 if (buffers_to_free) { 2862 2868 struct buffer_head *bh = buffers_to_free;