Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3aafbc4
fuse: give DLM ranges a lifecycle state
hbirth Aug 25, 2026
971a7ea
fuse: make the DLM lock mode part of the range state
hbirth Aug 25, 2026
c556aba
fuse: keep a revoked DLM range instead of deleting it
hbirth Aug 27, 2026
9d66b81
fuse: bound the re-request of a grant a revoke killed in flight
hbirth Aug 27, 2026
81cd737
fuse: split DLM ranges at the bounds of a recorded grant
Aug 26, 2026
55e2dff
fuse: take a DLM read lock for the readahead window
hbirth Aug 21, 2026
29d1f21
fuse: let a NOTIFY invalidate ask what it has to do
hbirth Aug 27, 2026
a941a49
fuse: hold the DLM grant again before writeback sends a folio
hbirth Aug 27, 2026
60579c9
fuse: free the DLM ranges a NOTIFY invalidate emptied
hbirth Aug 27, 2026
b87c11e
fuse: drop the per-inode coherency gate
hbirth Aug 27, 2026
870f3c2
fuse: flush the page cache before re-routing to direct IO
hbirth Aug 27, 2026
3b816f0
fuse: free the DLM ranges a truncate emptied
hbirth Aug 25, 2026
92bde30
fuse: drop the DLM ranges a truncate's invalidate empties
Aug 26, 2026
75618b7
fuse: forget the DLM record an O_TRUNC open discards
Aug 26, 2026
fb80d43
fuse: drop the DLM ranges a punch hole empties
Aug 26, 2026
2bf46a5
fuse: drop the DLM ranges copy_file_range invalidates
Aug 26, 2026
9a6184a
fuse: keep the local size while the tail is dirty
Aug 26, 2026
380ff95
fuse: drop the DLM ranges an attribute change invalidates
Aug 26, 2026
2764f2b
fuse: wait out the writeback a NOTIFY invalidate starts
hbirth Aug 27, 2026
1560fc8
fuse: do not hand O_APPEND to the server under the writeback cache
Aug 26, 2026
3d797ce
fuse: drop the open time page cache under the invalidate lock
hbirth Aug 27, 2026
ae415cf
fuse: size the readahead window by request
hbirth Aug 27, 2026
0dacbfe
fuse: send the readahead mm built before growing the window
hbirth Aug 27, 2026
6ce78c0
fuse: keep the read lookahead on the inode
hbirth Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 61 additions & 30 deletions fs/fuse/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,10 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
memset(&inarg, 0, sizeof(inarg));
memset(&outentry, 0, sizeof(outentry));
inarg.flags = flags;

/* The kernel owns append positioning; see fuse_send_open() */
if (fm->fc->writeback_cache)
inarg.flags &= ~O_APPEND;
inarg.mode = mode;
inarg.umask = current_umask();

Expand Down Expand Up @@ -785,10 +789,27 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
fi = get_fuse_inode(inode);
fuse_sync_release(fi, ff, flags);
} else {
if (fm->fc->atomic_o_trunc && trunc)
if (fm->fc->atomic_o_trunc && trunc) {
/*
* Every grant goes with the cache, as on the
* fuse_open() O_TRUNC path: a record left behind
* would keep naming bytes the folios no longer hold.
*/
if (fm->fc->dlm && fm->fc->writeback_cache)
fuse_dlm_cache_release_locks(
get_fuse_inode(inode));
truncate_pagecache(inode, 0);
else if (!(ff->open_flags & FOPEN_KEEP_CACHE))
invalidate_inode_pages2(inode->i_mapping);
} else if (!(ff->open_flags & FOPEN_KEEP_CACHE)) {
/*
* Only when the drop really emptied the mapping, as
* in fuse_open(): a folio that survived still needs
* its record.
*/
if (fuse_open_drop_cache(inode) &&
fm->fc->dlm && fm->fc->writeback_cache)
fuse_dlm_ranges_dropped(get_fuse_inode(inode),
0, U64_MAX);
}
}
return err;

Expand Down Expand Up @@ -2082,34 +2103,24 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
WARN_ON(!(attr->ia_valid & ATTR_SIZE));
WARN_ON(attr->ia_size != 0);
if (fc->atomic_o_trunc) {
struct percpu_rw_semaphore *wb_sem = fi->wb_inval_rwsem;

/*
* No need to send request to userspace, since actual
* truncation has already been done by OPEN. But still
* need to truncate page cache.
*
* Revoke and drop under the coherency gate write side,
* like the NOTIFY invalidate path: a gate reader that
* already re-validated its grant must not have the
* lock tree and the cache yanked mid-hold, or it
* would repopulate the truncated range trusting a
* grant that no longer exists. Waiting for gate
* readers here is safe: we hold i_rwsem exclusive, so
* no gate holder can be waiting on it (the write path
* takes i_rwsem before the gate, the read path never
* takes it).
* Dropping every grant here does not need a reader or
* writer fenced out: truncate_pagecache() discards the
* folios rather than writing them, and a write racing
* this is a write racing an O_TRUNC open, which has no
* order to preserve. i_rwsem is held exclusive
* anyway, so no cached write is in progress.
*/
if (wb_sem)
percpu_down_write(wb_sem);
if (fc->dlm && fc->writeback_cache)
fuse_dlm_cache_release_locks(fi);
spin_lock(&fi->lock);
i_size_write(inode, 0);
spin_unlock(&fi->lock);
truncate_pagecache(inode, 0);
if (wb_sem)
percpu_up_write(wb_sem);
goto out;
}
file = NULL;
Expand Down Expand Up @@ -2213,23 +2224,43 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
*/
if ((is_truncate || !is_wb) &&
S_ISREG(inode->i_mode) && oldsize != outarg.attr.size) {
struct percpu_rw_semaphore *wb_sem = fi->wb_inval_rwsem;

/*
* Revoke and drop under the coherency gate write side; see
* the atomic-O_TRUNC branch above. i_rwsem is held
* exclusive here as well (setattr), so waiting out gate
* readers cannot deadlock.
* Revoke past the new size and drop what is beyond it; see
* the atomic-O_TRUNC branch above for why this needs nothing
* fenced out. i_rwsem is held exclusive here as well.
*/
if (wb_sem)
percpu_down_write(wb_sem);
if (fc->dlm && fc->writeback_cache)
fuse_dlm_unlock_range(fi, outarg.attr.size & PAGE_MASK, -1);
fuse_dlm_unlock_range(fi, outarg.attr.size & PAGE_MASK,
U64_MAX);

truncate_pagecache(inode, outarg.attr.size);
invalidate_inode_pages2(mapping);
if (wb_sem)
percpu_up_write(wb_sem);

/*
* The cache above the new size is gone, so the ranges
* describing it have nothing left to say. From the first
* whole page above it: the page holding the new end of the
* file survives the truncate, and so does its record.
*/
if (fc->dlm && fc->writeback_cache)
fuse_dlm_ranges_dropped(fi, PAGE_ALIGN(outarg.attr.size),
U64_MAX);

/*
* invalidate_inode_pages2() emptied the mapping below the new
* size too (laundering anything dirty first, so those bytes
* are on the server). A revoked range is kept only to make
* writeback take the grant again before sending the folios
* under it, so with those folios gone it describes nothing
* and would sit in the tree unfreed, keeping its neighbours
* from merging. Only when the drop really emptied it: a busy
* folio that survived still needs its record, and a fault
* populating after the check keeps its page visible to it.
*/
if (fc->dlm && fc->writeback_cache && outarg.attr.size &&
!filemap_range_has_page(mapping, 0, outarg.attr.size - 1))
fuse_dlm_ranges_dropped(fi, 0,
PAGE_ALIGN(outarg.attr.size) - 1);
}

clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state);
Expand Down
Loading
Loading