From nobody Fri Oct 2 08:25:16 2026 Received: from mail-wm1-f50.google.com (mail-wm1-f50.google.com [209.85.128.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE90E3E9F8E for ; Mon, 3 Aug 2026 11:15:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785755736; cv=none; b=nq+zy4/SV+22FNqy0O+9Ws8QbkmO32dLLv05VBFFkXzWEJYY9lTOw7oVFmvCp9gFEXmt+DIPq2AwMNwuOolu07WYQvADQDurWwljwieNGr7iSq70MGPH0krRWbB9IEc1TnTXeO8d6scmxR3oJtlN6Y3sHc4HO6HKYq5ytn6wfSE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785755736; c=relaxed/simple; bh=Iqj6oPbgV3K4z4iMJ6xkTWP4KmRB34Sc09PDX1epHZw=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Ahy3LDhbVjdfWZJFLvQTiHsHP3x1fluzvFJX/lnZJ1ecqNK+5iNrDBnrsXC/ayCN9RKEcI02kSLegGwvzgihsApCCYoIt0UmEmWMIfbYoj9rg+NjatA/cEJ6goRFkSztz/jQ5D4DHjUmTk6NtFoyIHB8UlW9V1/HNgW+wDbFvtU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=tOhKVMGI; arc=none smtp.client-ip=209.85.128.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="tOhKVMGI" Received: by mail-wm1-f50.google.com with SMTP id 5b1f17b1804b1-493f6de72faso13778565e9.0 for ; Mon, 03 Aug 2026 04:15:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785755733; x=1786360533; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=pgP5KTUbnDtL+9NzoxDA4NbkaUK1pK4XxPjHL/tVDEc=; b=tOhKVMGIvGfeFMwR7OzZMFyu85HHbAET9a+Y+feI+11o/FpgiSB5kFIkQjzNn6nPee 0O1+gJWY7ZxbGHKApjan3ATqCwyUWQOAK7/O4GQnOVFD0yz63ogUaLOHJbjRvuGBwwBB 7EPSxtUaxoIOLV5ZYNZfa4F0eNV0+ARQBn/L+LIvxzsYpl6CC8WO/1No4DT+fid7r1YF A5t0ycMxmy7huU2B+91BRc6OAtOK1w9IonAwAPGrBTm97KsIvOG0Hfw8m7XKP+IPJuYQ ZMDpRAgOCbpAi19M9Sw0kwSB9ODwkpea/EWCWfwDGh0weMIWJzT1t7/d61OtDz2aMe1z XJXA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785755733; x=1786360533; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=pgP5KTUbnDtL+9NzoxDA4NbkaUK1pK4XxPjHL/tVDEc=; b=F0uInIiDfX5l1oHTo5v43IetbaWpzWPQ/vHwPyAYM+GxC582T4xEbnzPxOobW5EB3w 8KwIOhQyZOmE5S7OYrnRy3mH5Ii6TSHa2jVI5NoPWtdyGUmTjEfxLZlxU8ux0E92fOc5 Jv8+cOnUSwmq/4E8YXgCMFmzY8P96uiXYKq7juy1eoAG7Rwn4iVaBxzYirwlPmsOcnkg Urzy1BmwxxDBw7d/oOH00Aa4+m8b6JlmZT6KDoST9I2TxNUb4vAauSDCPe8xLaxvoCe1 /YW+0pDM7a2FS/D0OXLw7RyjEpUZWuSrUDvQfB+OoFj1Qzo8lNDUYqZr/QbB1NqncQ5g uE0Q== X-Forwarded-Encrypted: i=1; AHgh+RoqE/lXAv5jaGBssGGaJfzoM7/qdj8CtTyxVWeHCwrGsElNVy/EMEHGx+4kc/dsNv+YgD+/u2cEWRliCqM=@vger.kernel.org X-Gm-Message-State: AOJu0YzzTntdoWB0QOtvoH8Dijc2K8oe3EsCAoHCoXwMQrWi1L2c4IYx 64JyOBk/0F+yN0+tluSZdEQxGapILeGUteu+18uDz8NjfNKbLuuxtx8Q X-Gm-Gg: AR+sD10+3GXQn093WCYy8VrvCj0y4vfkjIb09QDZWf1f+ISMb9HkFzbFZxibEmAbT9b EyJtmwXcuTQwDa8dCu8w16ViiknvbDPlWQKClx8oc+XR+rlVljzFDoiX3VT37M8bXcErlW7jixT vK4gyK+GYJGdnstoCAVk0XSwJuVrjJpNHATG08N0bV4szxE+CQc/Qh972OTWTzAdyU1A90waaib MaeR60b/ibOnmBD2Zz27u9WrpCIGefIK7m61YFUDmawshW++VrcPB+yhh4fXIrPMuoDgrBgKZ4v jA5vIUj/XTttwiVadgS4Bi9TK+1WcW9j4tJGaGmIeUve3BNdR4wLqK/QhXBxINo6kqd8goF+Ooe 9fbcz8uRkwSk4wbFZ7yxnzE9DOHmb85EmpsEjYlQ4ScjuVl55L2Nw2SBbV6rcj2BfVDRQJVHuH0 Kgr7FvJqOTOCPJBaMNvjwAhMImuZ5WuOCIRCNywdeIDzGQKsiKXwiZqaS0d2qQfZfChYk8gU8Om Fc8v/rtzshVV4CFf8jaaOgj0GOYimTjJx+1htM= X-Received: by 2002:a05:600c:4708:b0:495:5d6d:9cc1 with SMTP id 5b1f17b1804b1-4980eb4e95dmr157839335e9.0.1785755732832; Mon, 03 Aug 2026 04:15:32 -0700 (PDT) Received: from f.. (cst-prg-94-167.cust.vodafone.cz. [46.135.94.167]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49808199a5csm317489835e9.4.2026.08.03.04.15.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 03 Aug 2026 04:15:31 -0700 (PDT) From: Mateusz Guzik To: brauner@kernel.org Cc: viro@zeniv.linux.org.uk, jack@suse.cz, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, Mateusz Guzik Subject: [PATCH v4] fs: avoid spurious dentry ref/unref cycle on open Date: Mon, 3 Aug 2026 13:15:26 +0200 Message-ID: <20260803111527.1922776-1-mjguzik@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Opening a file grabs a reference on the terminal dentry in __legitimize_path(), then another one in do_dentry_open() and finally drops the initial reference in terminate_walk(). That's 2 modifications which don't need to be there -- do_dentry_open() can consume the already held reference instead. When benchmarking on a 20-core vm using will-it-scale to open the same file read-only, the results are (ops/s): before: 4043375 after: 5629378 (+39%) Signed-off-by: Mateusz Guzik --- The spurious ref cycle remains an issue and it is trivially avoidable, for the most common case anyway. Al Viro had a more involved patchset which got stalled, see: https://lore.kernel.org/linux-fsdevel/20240822003359.GO504335@ZenIV/ I already pointed this out over a year ago when sending v3. Given lack of traffic on the more involved variant, the nice win from my simple patch and its overall triviality, I think it should go in. Worst case, if the more involved work ever gets off the ground it can be trivially reverted later. bench is: $ cat tests/openro3.c #include #include #include #include #include #include static char tmpfile[] =3D "/tmp/willitscale.XXXXXX"; char *testcase_description =3D "Same file open/close read-only"; void testcase_prepare(unsigned long nr_tasks) { int fd =3D mkstemp(tmpfile); assert(fd >=3D 0); close(fd); } void testcase(unsigned long long *iterations, unsigned long nr) { while (1) { int fd =3D open(tmpfile, O_RDONLY); assert(fd >=3D 0); close(fd); (*iterations)++; } } void testcase_cleanup(void) { unlink(tmpfile); } v4: - rebase - don't grab the extra ref on mnt for truncate - bench opening things r/o. note perf improved from last year thanks to other changes fs/internal.h | 1 + fs/namei.c | 13 ++++++++++--- fs/open.c | 27 ++++++++++++++++++++++++++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/fs/internal.h b/fs/internal.h index c658c8a5ebd5..9632239036ac 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -205,6 +205,7 @@ int do_fchownat(int dfd, const char __user *filename, u= id_t user, gid_t group, int flag); int chown_common(const struct path *path, uid_t user, gid_t group); extern int vfs_open(const struct path *, struct file *); +int vfs_open_consume(struct path *, struct file *); =20 /* * inode.c diff --git a/fs/namei.c b/fs/namei.c index 3f9bf103ba12..cf79ecedf288 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4789,6 +4789,7 @@ static const char *open_last_lookups(struct nameidata= *nd, static int do_open(struct nameidata *nd, struct file *file, const struct open_flags *op) { + struct vfsmount *mnt; struct mnt_idmap *idmap; int open_flag =3D op->open_flag; bool do_truncate; @@ -4827,14 +4828,20 @@ static int do_open(struct nameidata *nd, open_flag &=3D ~O_TRUNC; acc_mode =3D 0; } else if (d_is_reg(nd->path.dentry) && open_flag & O_TRUNC) { - error =3D mnt_want_write(nd->path.mnt); + /* + * Stash the mount point before vfs_open_consume() whacks nd->path. + * It is safely accessible because the file obj using it is guaranteed + * to not disappear while we execute. + */ + mnt =3D nd->path.mnt; + error =3D mnt_want_write(mnt); if (error) return error; do_truncate =3D true; } error =3D may_open(idmap, &nd->path, acc_mode, open_flag); if (!error && !(file->f_mode & FMODE_OPENED)) - error =3D vfs_open(&nd->path, file); + error =3D vfs_open_consume(&nd->path, file); if (!error) error =3D security_file_post_open(file, op->acc_mode); if (!error && do_truncate) @@ -4844,7 +4851,7 @@ static int do_open(struct nameidata *nd, error =3D -EINVAL; } if (do_truncate) - mnt_drop_write(nd->path.mnt); + mnt_drop_write(mnt); return error; } =20 diff --git a/fs/open.c b/fs/open.c index 6b1c14e684a9..2a7697cee00b 100644 --- a/fs/open.c +++ b/fs/open.c @@ -931,6 +931,11 @@ static inline int file_get_write_access(struct file *f) return error; } =20 +/* + * Populate struct file + * + * NOTE: it assumes f_path is populated and consumes the caller's referenc= e. + */ static int do_dentry_open(struct file *f, int (*open)(struct inode *, struct file *)) { @@ -938,7 +943,6 @@ static int do_dentry_open(struct file *f, struct inode *inode =3D f->f_path.dentry->d_inode; int error; =20 - path_get(&f->f_path); f->f_inode =3D inode; f->f_mapping =3D inode->i_mapping; f->f_wb_err =3D filemap_sample_wb_err(f->f_mapping); @@ -1055,6 +1059,7 @@ int finish_open(struct file *file, struct dentry *den= try, BUG_ON(file->f_mode & FMODE_OPENED); /* once it's opened, it's opened */ =20 file->__f_path.dentry =3D dentry; + path_get(&file->f_path); return do_dentry_open(file, open); } EXPORT_SYMBOL(finish_open); @@ -1098,6 +1103,7 @@ int vfs_open(const struct path *path, struct file *fi= le) int ret; =20 file->__f_path =3D *path; + path_get(&file->f_path); ret =3D do_dentry_open(file, NULL); if (!ret) { /* @@ -1110,6 +1116,25 @@ int vfs_open(const struct path *path, struct file *f= ile) return ret; } =20 +/** + * vfs_open_consume - open the file at the given path and consume the refe= rence + * @path: path to open + * @file: newly allocated file with f_flag initialized + */ +int vfs_open_consume(struct path *path, struct file *file) +{ + int ret; + + file->__f_path =3D *path; + path->mnt =3D NULL; + path->dentry =3D NULL; + ret =3D do_dentry_open(file, NULL); + if (!ret) { + fsnotify_open(file); + } + return ret; +} + struct file *dentry_open(const struct path *path, int flags, const struct cred *cred) { --=20 2.53.0