diff options
author | Yan, Zheng <zheng.z.yan@intel.com> | 2013-07-24 06:22:11 +0200 |
---|---|---|
committer | Sage Weil <sage@inktank.com> | 2013-08-10 02:55:32 +0200 |
commit | 6f60f889470aecf747610279545c054a99aadca3 (patch) | |
tree | 60f5781c7673d1d0e06ef1b658e652b2738daf24 /fs/ceph | |
parent | ceph: WQ_NON_REENTRANT is meaningless and going away (diff) | |
download | linux-6f60f889470aecf747610279545c054a99aadca3.tar.xz linux-6f60f889470aecf747610279545c054a99aadca3.zip |
ceph: fix freeing inode vs removing session caps race
remove_session_caps() uses iterate_session_caps() to remove caps,
but iterate_session_caps() skips inodes that are being deleted.
So session->s_nr_caps can be non-zero after iterate_session_caps()
return.
We can fix the issue by waiting until deletions are complete.
__wait_on_freeing_inode() is designed for the job, but it is not
exported, so we use lookup inode function to access it.
Signed-off-by: Yan, Zheng <zheng.z.yan@intel.com>
Diffstat (limited to 'fs/ceph')
-rw-r--r-- | fs/ceph/inode.c | 8 | ||||
-rw-r--r-- | fs/ceph/mds_client.c | 31 | ||||
-rw-r--r-- | fs/ceph/super.h | 2 |
3 files changed, 41 insertions, 0 deletions
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 55aaddb4047e..3b0abed667c2 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -61,6 +61,14 @@ struct inode *ceph_get_inode(struct super_block *sb, struct ceph_vino vino) return inode; } +struct inode *ceph_lookup_inode(struct super_block *sb, struct ceph_vino vino) +{ + struct inode *inode; + ino_t t = ceph_vino_to_ino(vino); + inode = ilookup5_nowait(sb, t, ceph_ino_compare, &vino); + return inode; +} + /* * get/constuct snapdir inode for a given directory */ diff --git a/fs/ceph/mds_client.c b/fs/ceph/mds_client.c index 6b40d8112c64..cbf08203e00d 100644 --- a/fs/ceph/mds_client.c +++ b/fs/ceph/mds_client.c @@ -1031,6 +1031,37 @@ static void remove_session_caps(struct ceph_mds_session *session) { dout("remove_session_caps on %p\n", session); iterate_session_caps(session, remove_session_caps_cb, NULL); + + spin_lock(&session->s_cap_lock); + if (session->s_nr_caps > 0) { + struct super_block *sb = session->s_mdsc->fsc->sb; + struct inode *inode; + struct ceph_cap *cap, *prev = NULL; + struct ceph_vino vino; + /* + * iterate_session_caps() skips inodes that are being + * deleted, we need to wait until deletions are complete. + * __wait_on_freeing_inode() is designed for the job, + * but it is not exported, so use lookup inode function + * to access it. + */ + while (!list_empty(&session->s_caps)) { + cap = list_entry(session->s_caps.next, + struct ceph_cap, session_caps); + if (cap == prev) + break; + prev = cap; + vino = cap->ci->i_vino; + spin_unlock(&session->s_cap_lock); + + inode = ceph_lookup_inode(sb, vino); + iput(inode); + + spin_lock(&session->s_cap_lock); + } + } + spin_unlock(&session->s_cap_lock); + BUG_ON(session->s_nr_caps > 0); BUG_ON(!list_empty(&session->s_cap_flushing)); cleanup_cap_releases(session); diff --git a/fs/ceph/super.h b/fs/ceph/super.h index cbded572345e..afcd62a68916 100644 --- a/fs/ceph/super.h +++ b/fs/ceph/super.h @@ -677,6 +677,8 @@ extern void ceph_destroy_inode(struct inode *inode); extern struct inode *ceph_get_inode(struct super_block *sb, struct ceph_vino vino); +extern struct inode *ceph_lookup_inode(struct super_block *sb, + struct ceph_vino vino); extern struct inode *ceph_get_snapdir(struct inode *parent); extern int ceph_fill_file_size(struct inode *inode, int issued, u32 truncate_seq, u64 truncate_size, u64 size); |