]> granicus.if.org Git - zfs/commitdiff
Fix zfs_putpage() lock inversion
authorBrian Behlendorf <behlendorf1@llnl.gov>
Fri, 19 Dec 2014 20:57:54 +0000 (12:57 -0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Mon, 22 Dec 2014 17:31:56 +0000 (09:31 -0800)
There exists a lock inversions involving the zfs range lock and the
individual page writeback bits which can result in a deadlock.  To
prevent this we must always manipulate the writeback bit while
holding the range lock.  The exact deadlock is as follows:

------ Process A ------        ------ Process B ------
zpl_writepages                 zpl_fallocate
write_cache_pages              zpl_fallocate_common
zpl_putpage                    zfs_space
zfs_putpage (set bit)          zfs_freesp
zfs_range_lock (wait on lock)  zfs_free_range (take lock)
[has not yet initiated I/O,    truncate_inode_pages_range
the bit will not be cleared]   wait_on_page_writeback (wait on bit)

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Tim Chase <tim@chase2k.com>
Signed-off-by: Richard Yao <richard.yao@clusterhq.com>
Issue #2976

module/zfs/zfs_vnops.c

index d05ccef39506e0bdd9d8f792c102e51b5c4d7a41..a048aeb36c792822a251db29ebead4478e9d37d0 100644 (file)
@@ -3899,14 +3899,13 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
        }
 #endif
 
+       rl = zfs_range_lock(zp, pgoff, pglen, RL_WRITER);
+
        set_page_writeback(pp);
        unlock_page(pp);
 
-       rl = zfs_range_lock(zp, pgoff, pglen, RL_WRITER);
        tx = dmu_tx_create(zsb->z_os);
-
        dmu_tx_hold_write(tx, zp->z_id, pgoff, pglen);
-
        dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE);
        zfs_sa_upgrade_txholds(tx, zp);
        err = dmu_tx_assign(tx, TXG_NOWAIT);