diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index b1282cd26526..4ed622a4cf0e 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -801,6 +801,65 @@ void ceph_evict_inode(struct inode *inode) ceph_put_string(rcu_dereference_raw(ci->i_cached_layout.pool_ns)); } +/* + * Decide whether to keep an inode once its last reference is gone. + * + * Dropping it here runs ceph_evict_inode(), which throws away the page + * cache along with the caps that still vouch for it. Keep a regular + * file's inode while it has cached pages and still holds real caps, so + * that a close and reopen can serve reads from the page cache instead + * of re-reading everything from the OSDs. + * + * Such an inode is not pinned: it holds no reference of its own, and + * page reclaim frees its pages like any other file's. It is not on the + * inode LRU either, since mapping_shrinkable() is false while real + * folios are present; emptying the mapping is what puts it there for + * the inode shrinker. Unmount evicts it regardless, since + * evict_inodes() walks sb->s_inodes rather than the LRU. + * + * The retention is bypassed whenever fscrypt requires the inode to be + * evicted, e.g. after its encryption key has been removed: the page + * cache holds plaintext, so it has to go with the key. + * FS_IOC_REMOVE_ENCRYPTION_KEY relies on that, as it evicts the + * dentries and then expects the inodes to follow. fscrypt is + * therefore asked before any of the retention below, and its answer is + * never overridden by it. + * + * Handing caps back to the MDS used to fall out of this eviction, so + * trim_caps_cb() sets CEPH_I_EVICT_ON_FINAL_IPUT_BIT to override the + * retention for one put when the MDS asks for its caps back. + * + * ->drop_inode() is called with inode->i_lock held, so i_ceph_lock + * cannot be taken here and i_caps is tested racily. That is good + * enough: either answer only keeps or drops an inode that could have + * gone the other way. + */ +int ceph_drop_inode(struct inode *inode) +{ + struct ceph_inode_info *ci = ceph_inode(inode); + + /* the MDS asked for this one back */ + if (test_and_clear_bit(CEPH_I_EVICT_ON_FINAL_IPUT_BIT, + &ci->i_ceph_flags)) + return 1; + + if (inode_generic_drop(inode)) + return 1; + + if (fscrypt_drop_inode(inode)) + return 1; + + if (ceph_inode_is_shutdown(inode)) + return 1; + + /* nothing here worth keeping the inode for */ + if (!S_ISREG(inode->i_mode) || !inode->i_data.nrpages) + return 1; + + /* keep the pages only while the inode still holds real caps */ + return !__ceph_is_any_real_caps(ci); +} + static inline blkcnt_t calc_inode_blocks(u64 size) { return (size + (1<<9) - 1) >> 9; diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index e23bb16c43cb..a3756293ed71 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -2277,8 +2277,9 @@ out: * Trim old(er) caps. * * Because we can't cache an inode without one or more caps, we do - * this indirectly: if a cap is unused, we prune its aliases, at which - * point the inode will hopefully get dropped to. + * this indirectly: if a cap is unused, we prune its aliases and mark + * the inode for eviction, at which point the inode will hopefully get + * dropped too, releasing the cap with it. * * Yes, this is a bit sloppy. Our only real goal here is to respond to * memory pressure from the MDS, though, so it needn't be perfect. @@ -2339,20 +2340,44 @@ static int trim_caps_cb(struct inode *inode, int mds, void *arg) (*remaining)--; } else { struct dentry *dentry; + bool pruned = false; + /* try dropping referring dentries */ spin_unlock(&ci->i_ceph_lock); dentry = d_find_any_alias(inode); - if (dentry && drop_negative_children(dentry)) { - int count; + if (!dentry) { + /* nothing refers to it in the first place */ + pruned = true; + } else if (drop_negative_children(dentry)) { dput(dentry); d_prune_aliases(inode); + pruned = true; + } else { + dput(dentry); + } + + if (pruned) { + int count; + + /* + * ceph_drop_inode() keeps a regular file's inode + * alive while it still has cached pages, so pruning + * the aliases is no longer enough on its own to get + * this cap back. Mirror that condition here and ask + * for a one-shot eviction: the iput() that + * ceph_iterate_session_caps() still owes this inode + * then runs ceph_evict_inode(), which is what hands + * the cap back. + */ + if (S_ISREG(inode->i_mode) && inode->i_data.nrpages) + set_bit(CEPH_I_EVICT_ON_FINAL_IPUT_BIT, + &ci->i_ceph_flags); + count = icount_read_once(inode); if (count == 1) (*remaining)--; doutc(cl, "%p %llx.%llx cap %p pruned, count now %d\n", inode, ceph_vinop(inode), cap, count); - } else { - dput(dentry); } return 0; } diff --git a/fs/ceph/super.c b/fs/ceph/super.c index 4306d6b3b995..fc2c1903722b 100644 --- a/fs/ceph/super.c +++ b/fs/ceph/super.c @@ -1049,7 +1049,7 @@ static const struct super_operations ceph_super_ops = { .alloc_inode = ceph_alloc_inode, .free_inode = ceph_free_inode, .write_inode = ceph_write_inode, - .drop_inode = inode_just_drop, + .drop_inode = ceph_drop_inode, .evict_inode = ceph_evict_inode, .sync_fs = ceph_sync_fs, .put_super = ceph_put_super, diff --git a/fs/ceph/super.h b/fs/ceph/super.h index a1c0f28ed4b2..f37b438e7730 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -744,6 +744,7 @@ static inline struct inode *ceph_find_inode(struct super_block *sb, * force a cap message to the MDS once * the deferred work completes */ +#define CEPH_I_EVICT_ON_FINAL_IPUT_BIT (16) /* evict at the final iput() */ #define CEPH_I_DIR_ORDERED (1 << CEPH_I_DIR_ORDERED_BIT) #define CEPH_I_FLUSH (1 << CEPH_I_FLUSH_BIT) @@ -1131,6 +1132,7 @@ struct ceph_acl_sec_ctx; extern const struct inode_operations ceph_file_iops; extern struct inode *ceph_alloc_inode(struct super_block *sb); +extern int ceph_drop_inode(struct inode *inode); extern void ceph_evict_inode(struct inode *inode); extern void ceph_free_inode(struct inode *inode);