client: drop null child dentries before try pruning inode's alias

Null child dentries holds reference on inode's alias, they prevents
Client::trim_caps() from trimming the inode. Null dentries are trimmed
by Client::trim_cache() according to 'client_cache_size' config option.
So Client::trim_caps() may fail to trim as many caps as MDS asked when
client cache size is smaller than the config limit.

Fixes: http://tracker.ceph.com/issues/22293
Signed-off-by: "Yan, Zheng" <zyan@redhat.com>
This commit is contained in:
Yan, Zheng 2017-12-01 10:17:18 +08:00
parent f3b2eb9d2d
commit 99b1b959e1
3 changed files with 38 additions and 0 deletions

View File

@ -2964,10 +2964,15 @@ Dentry* Client::link(Dir *dir, const string& name, Inode *in, Dentry *dn)
dn->dir = dir;
dir->dentries[dn->name] = dn;
lru.lru_insert_mid(dn); // mid or top?
if (!in)
dir->num_null_dentries++;
ldout(cct, 15) << "link dir " << dir->parent_inode << " '" << name << "' to inode " << in
<< " dn " << dn << " (new dn)" << dendl;
} else {
assert(!dn->inode);
if (in)
dir->num_null_dentries--;
ldout(cct, 15) << "link dir " << dir->parent_inode << " '" << name << "' to inode " << in
<< " dn " << dn << " (old dn)" << dendl;
}
@ -3004,11 +3009,15 @@ void Client::unlink(Dentry *dn, bool keepdir, bool keepdentry)
if (keepdentry) {
dn->lease_mds = -1;
if (in)
dn->dir->num_null_dentries++;
} else {
ldout(cct, 15) << "unlink removing '" << dn->name << "' dn " << dn << dendl;
// unlink from dir
dn->dir->dentries.erase(dn->name);
if (!in)
dn->dir->num_null_dentries--;
if (dn->dir->is_empty() && !keepdir)
close_dir(dn->dir);
dn->dir = 0;
@ -4042,6 +4051,31 @@ void Client::_invalidate_kernel_dcache()
}
}
void Client::_trim_negative_child_dentries(InodeRef& in)
{
if (!in->is_dir())
return;
Dir* dir = in->dir;
if (dir && dir->dentries.size() == dir->num_null_dentries) {
for (auto p = dir->dentries.begin(); p != dir->dentries.end(); ) {
Dentry *dn = p->second;
++p;
assert(!dn->inode);
if (dn->lru_is_expireable())
unlink(dn, true, false); // keep dir, drop dentry
}
if (dir->dentries.empty()) {
close_dir(dir);
}
}
if (in->flags & I_SNAPDIR_OPEN) {
InodeRef snapdir = open_snapdir(in.get());
_trim_negative_child_dentries(snapdir);
}
}
void Client::trim_caps(MetaSession *s, uint64_t max)
{
mds_rank_t mds = s->mds_num;
@ -4073,6 +4107,7 @@ void Client::trim_caps(MetaSession *s, uint64_t max)
}
} else {
ldout(cct, 20) << " trying to trim dentries for " << *in << dendl;
_trim_negative_child_dentries(in);
bool all = true;
auto q = in->dentries.begin();
while (q != in->dentries.end()) {

View File

@ -537,6 +537,7 @@ protected:
void trim_dentry(Dentry *dn);
void trim_caps(MetaSession *s, uint64_t max);
void _invalidate_kernel_dcache();
void _trim_negative_child_dentries(InodeRef& in);
void dump_inode(Formatter *f, Inode *in, set<Inode*>& did, bool disconnected);
void dump_cache(Formatter *f); // debug

View File

@ -7,6 +7,8 @@ class Dir {
public:
Inode *parent_inode; // my inode
ceph::unordered_map<string, Dentry*> dentries;
unsigned num_null_dentries = 0;
vector<Dentry*> readdir_cache;
explicit Dir(Inode* in) { parent_inode = in; }