Skip to content

Conversation

@hbirth
Copy link
Collaborator

@hbirth hbirth commented Jan 15, 2026

Note that for properly testing this we need multiple clients and mount points

There is a race condition between fuse_iget() and fuse_reverse_inval_inode()
where an invalidation request can arrive while an inode is still being
initialized. This can lead to the invalidation being lost, causing stale
inode attributes to persist.

The race occurs when:
1. fuse_iget() is called to create/initialize an inode
2. Before fuse_change_attributes_i() completes, fuse_reverse_inval_inode()
   is called for the same inode
3. The invalidation updates attr_version, but the subsequent
   fuse_change_attributes_i() overwrites it, losing the invalidation

Fix this by introducing a delayed invalidation mechanism using attr_version
as a state indicator:
- attr_version == 0: inode initialization in progress (fuse_iget not done)
- attr_version == 1: delayed invalidation marker
- attr_version > 1: normal operation

When fuse_reverse_inval_inode() encounters an inode with attr_version == 0,
it marks it with attr_version = 1 instead of performing immediate invalidation.
After fuse_iget() completes fuse_change_attributes_i(), it checks for the
delayed invalidation marker (attr_version == 1) and performs the invalidation
at that point.

This ensures invalidation requests are not lost during inode initialization,
maintaining cache coherency.

Signed-off-by: Guang Yuan Wu <gwu@ddn.com>
Applied-by: Horst Birthelmer <hbirthelmer@ddn.com>
fi = get_fuse_inode(inode);
spin_lock(&fi->lock);

if (fi->attr_version == 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just return -EAGAIN and let userspace handle it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative would be too add a waitq

evict_ctr);

spin_lock(&fi->lock);
if (fi->attr_version == 1) /* delayed inode invalidation */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I must be missing something, somehow I think this is always true? fuse_change_attributes_common() increases to 1?

* evict_ctr == fuse_get_evict_ctr() -> no evicts while during request
*/
if (!evict_ctr || fi->attr_version || evict_ctr == fuse_get_evict_ctr(fc))
if (!evict_ctr || fi->attr_version > 1 || evict_ctr == fuse_get_evict_ctr(fc))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these changes >1, same as >0?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah not >0


fi->attr_version = atomic64_inc_return(&fc->attr_version);
if (fi->attr_version != 1) /* fuse_iget() handle if attr_version == 1 */
fi->attr_version = atomic64_inc_return(&fc->attr_version);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is if this is later on called?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's assume there is no invalidate, and just two times fuse_change_attributes_common() from two different calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants