fusefs: cache negative lookups
The FUSE protocol includes a way for a server to tell the client that a negative lookup response is cacheable for a certain amount of time. PR: 236226 Sponsored by: The FreeBSD Foundation
This commit is contained in:
parent
ccb75e4939
commit
44f10c6e40
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/projects/fuse2/; revision=346067
@ -189,22 +189,12 @@ fuse_internal_cache_attrs(struct vnode *vp, struct fuse_attr *attr,
|
|||||||
struct mount *mp;
|
struct mount *mp;
|
||||||
struct fuse_vnode_data *fvdat;
|
struct fuse_vnode_data *fvdat;
|
||||||
struct vattr *vp_cache_at;
|
struct vattr *vp_cache_at;
|
||||||
struct timespec now, duration, timeout;
|
|
||||||
|
|
||||||
mp = vnode_mount(vp);
|
mp = vnode_mount(vp);
|
||||||
fvdat = VTOFUD(vp);
|
fvdat = VTOFUD(vp);
|
||||||
|
|
||||||
getnanouptime(&now);
|
fuse_validity_2_bintime(attr_valid, attr_valid_nsec,
|
||||||
/* "+ 2" is the bound of attr_valid_nsec + now.tv_nsec */
|
&fvdat->attr_cache_timeout);
|
||||||
/* Why oh why isn't there a TIME_MAX defined? */
|
|
||||||
if (attr_valid >= INT_MAX || attr_valid + now.tv_sec + 2 >= INT_MAX) {
|
|
||||||
fvdat->attr_cache_timeout.sec = INT_MAX;
|
|
||||||
} else {
|
|
||||||
duration.tv_sec = attr_valid;
|
|
||||||
duration.tv_nsec = attr_valid_nsec;
|
|
||||||
timespecadd(&duration, &now, &timeout);
|
|
||||||
timespec2bintime(&timeout, &fvdat->attr_cache_timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
vp_cache_at = VTOVA(vp);
|
vp_cache_at = VTOVA(vp);
|
||||||
|
|
||||||
|
@ -150,6 +150,52 @@ fuse_iosize(struct vnode *vp)
|
|||||||
return (vp->v_mount->mnt_stat.f_iosize);
|
return (vp->v_mount->mnt_stat.f_iosize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a cacheable timeout in bintime format value based on a fuse_attr_out
|
||||||
|
* response
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
fuse_validity_2_bintime(uint64_t attr_valid, uint32_t attr_valid_nsec,
|
||||||
|
struct bintime *timeout)
|
||||||
|
{
|
||||||
|
struct timespec now, duration, timeout_ts;
|
||||||
|
|
||||||
|
getnanouptime(&now);
|
||||||
|
/* "+ 2" is the bound of attr_valid_nsec + now.tv_nsec */
|
||||||
|
/* Why oh why isn't there a TIME_MAX defined? */
|
||||||
|
if (attr_valid >= INT_MAX || attr_valid + now.tv_sec + 2 >= INT_MAX) {
|
||||||
|
timeout->sec = INT_MAX;
|
||||||
|
} else {
|
||||||
|
duration.tv_sec = attr_valid;
|
||||||
|
duration.tv_nsec = attr_valid_nsec;
|
||||||
|
timespecadd(&duration, &now, &timeout_ts);
|
||||||
|
timespec2bintime(&timeout_ts, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make a cacheable timeout value in timespec format based on the fuse_entry_out
|
||||||
|
* response
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
fuse_validity_2_timespec(const struct fuse_entry_out *feo,
|
||||||
|
struct timespec *timeout)
|
||||||
|
{
|
||||||
|
struct timespec duration, now;
|
||||||
|
|
||||||
|
getnanouptime(&now);
|
||||||
|
/* "+ 2" is the bound of entry_valid_nsec + now.tv_nsec */
|
||||||
|
if (feo->entry_valid >= INT_MAX ||
|
||||||
|
feo->entry_valid + now.tv_sec + 2 >= INT_MAX) {
|
||||||
|
timeout->tv_sec = INT_MAX;
|
||||||
|
} else {
|
||||||
|
duration.tv_sec = feo->entry_valid;
|
||||||
|
duration.tv_nsec = feo->entry_valid_nsec;
|
||||||
|
timespecadd(&duration, &now, timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* access */
|
/* access */
|
||||||
|
|
||||||
#define FVP_ACCESS_NOOP 0x01
|
#define FVP_ACCESS_NOOP 0x01
|
||||||
|
@ -291,20 +291,12 @@ fuse_vnode_get(struct mount *mp,
|
|||||||
if (dvp != NULL && cnp != NULL && (cnp->cn_flags & MAKEENTRY) != 0 &&
|
if (dvp != NULL && cnp != NULL && (cnp->cn_flags & MAKEENTRY) != 0 &&
|
||||||
feo != NULL &&
|
feo != NULL &&
|
||||||
(feo->entry_valid != 0 || feo->entry_valid_nsec != 0)) {
|
(feo->entry_valid != 0 || feo->entry_valid_nsec != 0)) {
|
||||||
struct timespec duration, now, timeout;
|
struct timespec timeout;
|
||||||
|
|
||||||
ASSERT_VOP_LOCKED(*vpp, "fuse_vnode_get");
|
ASSERT_VOP_LOCKED(*vpp, "fuse_vnode_get");
|
||||||
ASSERT_VOP_LOCKED(dvp, "fuse_vnode_get");
|
ASSERT_VOP_LOCKED(dvp, "fuse_vnode_get");
|
||||||
|
|
||||||
getnanouptime(&now);
|
fuse_validity_2_timespec(feo, &timeout);
|
||||||
if (feo->entry_valid >= INT_MAX ||
|
|
||||||
feo->entry_valid + now.tv_sec + 2 >= INT_MAX) {
|
|
||||||
timeout.tv_sec = INT_MAX;
|
|
||||||
} else {
|
|
||||||
duration.tv_sec = feo->entry_valid;
|
|
||||||
duration.tv_nsec = feo->entry_valid_nsec;
|
|
||||||
timespecadd(&duration, &now, &timeout);
|
|
||||||
}
|
|
||||||
cache_enter_time(dvp, *vpp, cnp, &timeout, NULL);
|
cache_enter_time(dvp, *vpp, cnp, &timeout, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,6 +706,9 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
|
|
||||||
struct fuse_dispatcher fdi;
|
struct fuse_dispatcher fdi;
|
||||||
enum fuse_opcode op;
|
enum fuse_opcode op;
|
||||||
|
struct fuse_entry_out *feo = NULL;
|
||||||
|
struct fuse_attr_out *fao = NULL;
|
||||||
|
struct fuse_attr *fattr = NULL;
|
||||||
|
|
||||||
uint64_t nid;
|
uint64_t nid;
|
||||||
struct fuse_access_param facp;
|
struct fuse_access_param facp;
|
||||||
@ -802,15 +805,18 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
if ((op == FUSE_LOOKUP) && !lookup_err) {
|
if ((op == FUSE_LOOKUP) && !lookup_err) {
|
||||||
/* lookup call succeeded */
|
/* lookup call succeeded */
|
||||||
nid = ((struct fuse_entry_out *)fdi.answ)->nodeid;
|
nid = ((struct fuse_entry_out *)fdi.answ)->nodeid;
|
||||||
if (!nid) {
|
if (nid == 0) {
|
||||||
/*
|
/* zero nodeid means ENOENT and cache it */
|
||||||
* zero nodeid is the same as "not found",
|
struct timespec timeout;
|
||||||
* but it's also cacheable (which we keep
|
|
||||||
* keep on doing not as of writing this)
|
|
||||||
* See PR 236226
|
|
||||||
*/
|
|
||||||
fdi.answ_stat = ENOENT;
|
fdi.answ_stat = ENOENT;
|
||||||
lookup_err = ENOENT;
|
lookup_err = ENOENT;
|
||||||
|
if (cnp->cn_flags & MAKEENTRY) {
|
||||||
|
feo = (struct fuse_entry_out *)fdi.answ;
|
||||||
|
fuse_validity_2_timespec(feo, &timeout);
|
||||||
|
cache_enter_time(dvp, *vpp, cnp, &timeout,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
} else if (nid == FUSE_ROOT_ID) {
|
} else if (nid == FUSE_ROOT_ID) {
|
||||||
lookup_err = EINVAL;
|
lookup_err = EINVAL;
|
||||||
}
|
}
|
||||||
@ -849,24 +855,7 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
err = EJUSTRETURN;
|
err = EJUSTRETURN;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
/* Consider inserting name into cache. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* No we can't use negative caching, as the fs
|
|
||||||
* changes are out of our control.
|
|
||||||
* False positives' falseness turns out just as things
|
|
||||||
* go by, but false negatives' falseness doesn't.
|
|
||||||
* (and aiding the caching mechanism with extra control
|
|
||||||
* mechanisms comes quite close to beating the whole purpose
|
|
||||||
* caching...)
|
|
||||||
*/
|
|
||||||
#if 0
|
|
||||||
if ((cnp->cn_flags & MAKEENTRY) != 0) {
|
|
||||||
SDT_PROBE2(fuse, , vnops, trace, 1,
|
|
||||||
"inserting NULL into cache");
|
|
||||||
cache_enter(dvp, NULL, cnp);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
err = ENOENT;
|
err = ENOENT;
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
@ -874,9 +863,6 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
|
|
||||||
/* !lookup_err */
|
/* !lookup_err */
|
||||||
|
|
||||||
struct fuse_entry_out *feo = NULL;
|
|
||||||
struct fuse_attr *fattr = NULL;
|
|
||||||
|
|
||||||
if (op == FUSE_GETATTR) {
|
if (op == FUSE_GETATTR) {
|
||||||
fattr = &((struct fuse_attr_out *)fdi.answ)->attr;
|
fattr = &((struct fuse_attr_out *)fdi.answ)->attr;
|
||||||
} else {
|
} else {
|
||||||
@ -1040,47 +1026,16 @@ fuse_vnop_lookup(struct vop_lookup_args *ap)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (op == FUSE_GETATTR) {
|
if (op == FUSE_GETATTR) {
|
||||||
struct fuse_attr_out *fao =
|
fao = (struct fuse_attr_out*)fdi.answ;
|
||||||
(struct fuse_attr_out*)fdi.answ;
|
|
||||||
fuse_internal_cache_attrs(*vpp,
|
fuse_internal_cache_attrs(*vpp,
|
||||||
&fao->attr, fao->attr_valid,
|
&fao->attr, fao->attr_valid,
|
||||||
fao->attr_valid_nsec, NULL);
|
fao->attr_valid_nsec, NULL);
|
||||||
} else {
|
} else {
|
||||||
struct fuse_entry_out *feo =
|
feo = (struct fuse_entry_out*)fdi.answ;
|
||||||
(struct fuse_entry_out*)fdi.answ;
|
|
||||||
fuse_internal_cache_attrs(*vpp,
|
fuse_internal_cache_attrs(*vpp,
|
||||||
&feo->attr, feo->attr_valid,
|
&feo->attr, feo->attr_valid,
|
||||||
feo->attr_valid_nsec, NULL);
|
feo->attr_valid_nsec, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Insert name into cache if appropriate. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Nooo, caching is evil. With caching, we can't avoid stale
|
|
||||||
* information taking over the playground (cached info is not
|
|
||||||
* just positive/negative, it does have qualitative aspects,
|
|
||||||
* too). And a (VOP/FUSE)_GETATTR is always thrown anyway, when
|
|
||||||
* walking down along cached path components, and that's not
|
|
||||||
* any cheaper than FUSE_LOOKUP. This might change with
|
|
||||||
* implementing kernel side attr caching, but... In Linux,
|
|
||||||
* lookup results are not cached, and the daemon is bombarded
|
|
||||||
* with FUSE_LOOKUPS on and on. This shows that by design, the
|
|
||||||
* daemon is expected to handle frequent lookup queries
|
|
||||||
* efficiently, do its caching in userspace, and so on.
|
|
||||||
*
|
|
||||||
* So just leave the name cache alone.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Well, now I know, Linux caches lookups, but with a
|
|
||||||
* timeout... So it's the same thing as attribute caching:
|
|
||||||
* we can deal with it when implement timeouts.
|
|
||||||
*/
|
|
||||||
#if 0
|
|
||||||
if (cnp->cn_flags & MAKEENTRY) {
|
|
||||||
cache_enter(dvp, *vpp, cnp);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
out:
|
out:
|
||||||
if (!lookup_err) {
|
if (!lookup_err) {
|
||||||
|
@ -169,8 +169,7 @@ TEST_F(Lookup, entry_cache)
|
|||||||
* If the daemon returns an error of 0 and an inode of 0, that's a flag for
|
* If the daemon returns an error of 0 and an inode of 0, that's a flag for
|
||||||
* "ENOENT and cache it" with the given entry_timeout
|
* "ENOENT and cache it" with the given entry_timeout
|
||||||
*/
|
*/
|
||||||
/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236226 */
|
TEST_F(Lookup, entry_cache_negative)
|
||||||
TEST_F(Lookup, DISABLED_entry_cache_negative)
|
|
||||||
{
|
{
|
||||||
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
|
struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user