commit 6ce9f7f35e0b3e7604fda1e15d232a461bae2368 Author: Andriy Gapon Date: Mon Dec 23 12:27:12 2013 +0200 zfs: getnewvnode_reserve must be called outside of a zfs transaction Otherwise we could run into the following deadlock. A thread has a transaction open and assigned to a transaction group. That would prevent the transaction group from be quiesced and synced. The thread is blocked in getnewvnode_reserve waiting for a vnode to a be reclaimed. vnlru thread is blocked trying to enter ZFS VOP because a filesystem is suspended by an ongoing rollback or receive operation. In its turn the operation is waiting for the current transaction group to be synced. zfs_zget is always used outside of active transactions, but zfs_mknode is always used in a transaction context. Thus, we hoist getnewvnode_reserve from zfs_mknode to its callers. While there, assert that ZFS always calls getnewvnode while having a vnode reserved. Reported by: adrian diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_dir.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_dir.c index 1f7fee2..be96da9 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_dir.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_dir.c @@ -951,6 +951,8 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, vnode_t **xvpp, cred_t *cr) return (SET_ERROR(EDQUOT)); } + getnewvnode_reserve(1); + tx = dmu_tx_create(zfsvfs->z_os); dmu_tx_hold_sa_create(tx, acl_ids.z_aclp->z_acl_bytes + ZFS_SA_BASE_ATTR_SIZE); @@ -985,6 +987,8 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, vnode_t **xvpp, cred_t *cr) zfs_acl_ids_free(&acl_ids); dmu_tx_commit(tx); + getnewvnode_drop_reserve(); + *xvpp = ZTOV(xzp); return (0); diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c index 57444ec..0d1dc1b 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_vnops.c @@ -1676,6 +1676,9 @@ zfs_create(vnode_t *dvp, char *name, vattr_t *vap, int excl, int mode, return (error); } } + + getnewvnode_reserve(1); + top: *vpp = NULL; @@ -1704,6 +1707,7 @@ top: zfs_acl_ids_free(&acl_ids); if (strcmp(name, "..") == 0) error = SET_ERROR(EISDIR); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -1772,6 +1776,7 @@ top: } zfs_acl_ids_free(&acl_ids); dmu_tx_abort(tx); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -1838,6 +1843,7 @@ top: } } out: + getnewvnode_drop_reserve(); if (dl) zfs_dirent_unlock(dl); @@ -2181,6 +2187,9 @@ zfs_mkdir(vnode_t *dvp, char *dirname, vattr_t *vap, vnode_t **vpp, cred_t *cr, ZFS_EXIT(zfsvfs); return (error); } + + getnewvnode_reserve(1); + /* * First make sure the new directory doesn't exist. * @@ -2194,6 +2203,7 @@ top: if (error = zfs_dirent_lock(&dl, dzp, dirname, &zp, zf, NULL, NULL)) { zfs_acl_ids_free(&acl_ids); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -2201,6 +2211,7 @@ top: if (error = zfs_zaccess(dzp, ACE_ADD_SUBDIRECTORY, 0, B_FALSE, cr)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -2208,6 +2219,7 @@ top: if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (SET_ERROR(EDQUOT)); } @@ -2240,6 +2252,7 @@ top: } zfs_acl_ids_free(&acl_ids); dmu_tx_abort(tx); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -2269,6 +2282,8 @@ top: dmu_tx_commit(tx); + getnewvnode_drop_reserve(); + zfs_dirent_unlock(dl); if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) @@ -4160,6 +4175,9 @@ zfs_symlink(vnode_t *dvp, vnode_t **vpp, char *name, vattr_t *vap, char *link, ZFS_EXIT(zfsvfs); return (error); } + + getnewvnode_reserve(1); + top: /* * Attempt to lock directory; fail if entry already exists. @@ -4167,6 +4185,7 @@ top: error = zfs_dirent_lock(&dl, dzp, name, &zp, zflg, NULL, NULL); if (error) { zfs_acl_ids_free(&acl_ids); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -4174,6 +4193,7 @@ top: if (error = zfs_zaccess(dzp, ACE_ADD_FILE, 0, B_FALSE, cr)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -4181,6 +4201,7 @@ top: if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (SET_ERROR(EDQUOT)); } @@ -4208,6 +4229,7 @@ top: } zfs_acl_ids_free(&acl_ids); dmu_tx_abort(tx); + getnewvnode_drop_reserve(); ZFS_EXIT(zfsvfs); return (error); } @@ -4246,6 +4268,8 @@ top: dmu_tx_commit(tx); + getnewvnode_drop_reserve(); + zfs_dirent_unlock(dl); if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) diff --git a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_znode.c b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_znode.c index 8168a67..738d53c 100644 --- a/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_znode.c +++ b/sys/cddl/contrib/opensolaris/uts/common/fs/zfs/zfs_znode.c @@ -624,6 +624,8 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, zp = kmem_cache_alloc(znode_cache, KM_SLEEP); + KASSERT(td->td_vp_reserv > 0, + ("zfs_znode_alloc: getnewvnode without any vnodes reserved")); error = getnewvnode("zfs", zfsvfs->z_parent->z_vfs, &zfs_vnodeops, &vp); if (error != 0) { kmem_cache_free(znode_cache, zp); @@ -832,7 +834,6 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, } } - getnewvnode_reserve(1); ZFS_OBJ_HOLD_ENTER(zfsvfs, obj); VERIFY(0 == sa_buf_hold(zfsvfs->z_os, obj, NULL, &db)); @@ -1019,7 +1020,6 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, KASSERT(err == 0, ("insmntque() failed: error %d", err)); } ZFS_OBJ_HOLD_EXIT(zfsvfs, obj); - getnewvnode_drop_reserve(); } /*