diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index b3a267e16f3e..6a6fc3e3cc4a 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -278,6 +278,7 @@ extern int zfs_znode_hold_compare(const void *, const void *); extern znode_hold_t *zfs_znode_hold_enter(zfsvfs_t *, uint64_t); extern void zfs_znode_hold_exit(zfsvfs_t *, znode_hold_t *); extern int zfs_zget(zfsvfs_t *, uint64_t, znode_t **); +extern int zfs_zget_impl(zfsvfs_t *, uint64_t, znode_t **, boolean_t); extern int zfs_rezget(znode_t *); extern void zfs_zinactive(znode_t *); extern void zfs_znode_delete(znode_t *, dmu_tx_t *); diff --git a/module/os/freebsd/zfs/zfs_znode_os.c b/module/os/freebsd/zfs/zfs_znode_os.c index ce7b93d20a47..8a242815ef19 100644 --- a/module/os/freebsd/zfs/zfs_znode_os.c +++ b/module/os/freebsd/zfs/zfs_znode_os.c @@ -1051,6 +1051,13 @@ zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) return (err); } +int +zfs_zget_impl(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp, + boolean_t check_sync) +{ + return (zfs_zget(zfsvfs, obj_num, zpp)); +} + int zfs_rezget(znode_t *zp) { diff --git a/module/os/linux/zfs/zfs_znode_os.c b/module/os/linux/zfs/zfs_znode_os.c index 607b3995cb60..231eefe0b3ba 100644 --- a/module/os/linux/zfs/zfs_znode_os.c +++ b/module/os/linux/zfs/zfs_znode_os.c @@ -1044,6 +1044,13 @@ zfs_xvattr_set(znode_t *zp, xvattr_t *xvap, dmu_tx_t *tx) int zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) +{ + return (zfs_zget_impl(zfsvfs, obj_num, zpp, B_FALSE)); +} + +int +zfs_zget_impl(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp, + boolean_t check_sync) { dmu_object_info_t doi; dmu_buf_t *db; @@ -1051,6 +1058,7 @@ zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) znode_hold_t *zh; int err; sa_handle_t *hdl; + boolean_t noloop = B_FALSE; *zpp = NULL; @@ -1109,8 +1117,18 @@ zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) if (igrab(ZTOI(zp)) == NULL) { if (zp->z_unlinked) err = SET_ERROR(ENOENT); - else + else { err = SET_ERROR(EAGAIN); + /* + * In writeback path, I_SYNC flag will be set + * and block inode eviction. So we must not + * loop doing igrab in possible writeback + * path, i.e. zfs_get_data, if inode is being + * evicted and I_SYNC is also set. + */ + if (check_sync && (ZTOI(zp)->i_state & I_SYNC)) + noloop = B_TRUE; + } } else { *zpp = zp; err = 0; @@ -1120,7 +1138,7 @@ zfs_zget(zfsvfs_t *zfsvfs, uint64_t obj_num, znode_t **zpp) sa_buf_rele(db, NULL); zfs_znode_hold_exit(zfsvfs, zh); - if (err == EAGAIN) { + if (err == EAGAIN && !noloop) { /* inode might need this to finish evict */ cond_resched(); goto again; diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index afd9e61313a9..8d0e0b4859b7 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -1149,10 +1149,21 @@ zfs_get_data(void *arg, uint64_t gen, lr_write_t *lr, char *buf, ASSERT3P(lwb, !=, NULL); ASSERT3U(size, !=, 0); + error = zfs_zget_impl(zfsvfs, object, &zp, B_TRUE); +#if defined(__linux__) + /* + * Under Linux, EAGAIN indicates the inode is being evicted and I_SYNC + * is also set possibly blocking eviction, so we can't loop in + * zfs_zget to avoid deadlock. Return EIO to force txg sync under such + * scenario. + */ + if (error == EAGAIN) + return (SET_ERROR(EIO)); +#endif /* * Nothing to do if the file has been removed */ - if (zfs_zget(zfsvfs, object, &zp) != 0) + if (error) return (SET_ERROR(ENOENT)); if (zp->z_unlinked) { /*