From e9e24ce2c8046c2573d49468e1133770b649018f Mon Sep 17 00:00:00 2001 From: Jaromir Hamala Date: Thu, 8 Oct 2026 18:25:07 +0200 Subject: [PATCH] ZIL: avoid deadlock in xattr owner check zfs_xattr_owner_unlinked() runs with an assigned transaction. A final zrele() can run inode or vnode cleanup inline. During Linux writeback, cleanup may wait for the caller's own I_SYNC state. Cleanup may also open another transaction while the current transaction remains assigned. The caller cannot return to commit its transaction, so the txg remains open and cannot sync. Treat the input znode as borrowed on all platforms. Release only parents acquired by zfs_zget() with zfs_zrele_async(). This removes the Linux zhold()/zrele() pair and the platform split. Check the current walk node in the assertion. Reviewed-by: Brian Behlendorf Signed-off-by: Jaromir Hamala Closes #19253 --- module/zfs/zfs_log.c | 39 ++++++++++----------------------------- 1 file changed, 10 insertions(+), 29 deletions(-) diff --git a/module/zfs/zfs_log.c b/module/zfs/zfs_log.c index 1bec2850b..11652a63c 100644 --- a/module/zfs/zfs_log.c +++ b/module/zfs/zfs_log.c @@ -37,6 +37,7 @@ #include #include #include +#include /* * These zfs_log_* functions must be called within a dmu tx, in one @@ -219,50 +220,30 @@ zfs_xattr_owner_unlinked(znode_t *zp) { int unlinked = 0; znode_t *dzp; -#ifdef __FreeBSD__ znode_t *tzp = zp; /* - * zrele drops the vnode lock which violates the VOP locking contract - * on FreeBSD. See comment at the top of zfs_replay.c for more detail. - */ - /* - * if zp is XATTR node, keep walking up via z_xattr_parent until we - * get the owner + * zp is borrowed and must not be released here. Only release + * references acquired by zfs_zget(), and release them asynchronously + * because zfs_log_* functions are called with an assigned transaction. + * + * If zp is an XATTR node, keep walking up via z_xattr_parent until we + * get the owner. */ while (tzp->z_pflags & ZFS_XATTR) { - ASSERT3U(zp->z_xattr_parent, !=, 0); + ASSERT3U(tzp->z_xattr_parent, !=, 0); if (zfs_zget(ZTOZSB(tzp), tzp->z_xattr_parent, &dzp) != 0) { unlinked = 1; break; } if (tzp != zp) - zrele(tzp); + zfs_zrele_async(tzp); tzp = dzp; unlinked = tzp->z_unlinked; } if (tzp != zp) - zrele(tzp); -#else - zhold(zp); - /* - * if zp is XATTR node, keep walking up via z_xattr_parent until we - * get the owner - */ - while (zp->z_pflags & ZFS_XATTR) { - ASSERT3U(zp->z_xattr_parent, !=, 0); - if (zfs_zget(ZTOZSB(zp), zp->z_xattr_parent, &dzp) != 0) { - unlinked = 1; - break; - } - - zrele(zp); - zp = dzp; - unlinked = zp->z_unlinked; - } - zrele(zp); -#endif + zfs_zrele_async(tzp); return (unlinked); } -- 2.53.0