]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - fs/nfs/pnfs.c
pnfs: fix pnfs lock inversion of i_lock and cl_lock
[mirror_ubuntu-artful-kernel.git] / fs / nfs / pnfs.c
index bc4089769735384073579926047d0347124f5b6b..0f5b66f90d1740c8b44e570d4f6ec98bddebc7d3 100644 (file)
@@ -247,14 +247,9 @@ put_lseg_locked(struct pnfs_layout_segment *lseg,
                BUG_ON(test_bit(NFS_LSEG_VALID, &lseg->pls_flags));
                list_del(&lseg->pls_list);
                if (list_empty(&lseg->pls_layout->plh_segs)) {
-                       struct nfs_client *clp;
-
-                       clp = NFS_SERVER(ino)->nfs_client;
-                       spin_lock(&clp->cl_lock);
-                       /* List does not take a reference, so no need for put here */
-                       list_del_init(&lseg->pls_layout->plh_layouts);
-                       spin_unlock(&clp->cl_lock);
-                       clear_bit(NFS_LAYOUT_BULK_RECALL, &lseg->pls_layout->plh_flags);
+                       set_bit(NFS_LAYOUT_DESTROYED, &lseg->pls_layout->plh_flags);
+                       /* Matched by initial refcount set in alloc_init_layout_hdr */
+                       put_layout_hdr_locked(lseg->pls_layout);
                }
                rpc_wake_up(&NFS_SERVER(ino)->roc_rpcwaitq);
                list_add(&lseg->pls_list, tmp_list);
@@ -299,6 +294,11 @@ mark_matching_lsegs_invalid(struct pnfs_layout_hdr *lo,
 
        dprintk("%s:Begin lo %p\n", __func__, lo);
 
+       if (list_empty(&lo->plh_segs)) {
+               if (!test_and_set_bit(NFS_LAYOUT_DESTROYED, &lo->plh_flags))
+                       put_layout_hdr_locked(lo);
+               return 0;
+       }
        list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list)
                if (should_free_lseg(lseg->pls_range.iomode, iomode)) {
                        dprintk("%s: freeing lseg %p iomode %d "
@@ -312,11 +312,27 @@ mark_matching_lsegs_invalid(struct pnfs_layout_hdr *lo,
        return invalid - removed;
 }
 
+/* note free_me must contain lsegs from a single layout_hdr */
 void
 pnfs_free_lseg_list(struct list_head *free_me)
 {
        struct pnfs_layout_segment *lseg, *tmp;
+       struct pnfs_layout_hdr *lo;
+
+       if (list_empty(free_me))
+               return;
 
+       lo = list_first_entry(free_me, struct pnfs_layout_segment,
+                             pls_list)->pls_layout;
+
+       if (test_bit(NFS_LAYOUT_DESTROYED, &lo->plh_flags)) {
+               struct nfs_client *clp;
+
+               clp = NFS_SERVER(lo->plh_inode)->nfs_client;
+               spin_lock(&clp->cl_lock);
+               list_del_init(&lo->plh_layouts);
+               spin_unlock(&clp->cl_lock);
+       }
        list_for_each_entry_safe(lseg, tmp, free_me, pls_list) {
                list_del(&lseg->pls_list);
                free_lseg(lseg);
@@ -332,10 +348,8 @@ pnfs_destroy_layout(struct nfs_inode *nfsi)
        spin_lock(&nfsi->vfs_inode.i_lock);
        lo = nfsi->layout;
        if (lo) {
-               set_bit(NFS_LAYOUT_DESTROYED, &nfsi->layout->plh_flags);
+               lo->plh_block_lgets++; /* permanently block new LAYOUTGETs */
                mark_matching_lsegs_invalid(lo, &tmp_list, IOMODE_ANY);
-               /* Matched by refcount set to 1 in alloc_init_layout_hdr */
-               put_layout_hdr_locked(lo);
        }
        spin_unlock(&nfsi->vfs_inode.i_lock);
        pnfs_free_lseg_list(&tmp_list);
@@ -403,6 +417,7 @@ pnfs_layoutgets_blocked(struct pnfs_layout_hdr *lo, nfs4_stateid *stateid,
            (int)(lo->plh_barrier - be32_to_cpu(stateid->stateid.seqid)) >= 0)
                return true;
        return lo->plh_block_lgets ||
+               test_bit(NFS_LAYOUT_DESTROYED, &lo->plh_flags) ||
                test_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags) ||
                (list_empty(&lo->plh_segs) &&
                 (atomic_read(&lo->plh_outstanding) > lget));
@@ -699,6 +714,7 @@ pnfs_update_layout(struct inode *ino,
        struct nfs_client *clp = NFS_SERVER(ino)->nfs_client;
        struct pnfs_layout_hdr *lo;
        struct pnfs_layout_segment *lseg = NULL;
+       bool first = false;
 
        if (!pnfs_enabled_sb(NFS_SERVER(ino)))
                return NULL;
@@ -729,7 +745,10 @@ pnfs_update_layout(struct inode *ino,
        atomic_inc(&lo->plh_outstanding);
 
        get_layout_hdr(lo);
-       if (list_empty(&lo->plh_segs)) {
+       if (list_empty(&lo->plh_segs))
+               first = true;
+       spin_unlock(&ino->i_lock);
+       if (first) {
                /* The lo must be on the clp list if there is any
                 * chance of a CB_LAYOUTRECALL(FILE) coming in.
                 */
@@ -738,18 +757,12 @@ pnfs_update_layout(struct inode *ino,
                list_add_tail(&lo->plh_layouts, &clp->cl_layouts);
                spin_unlock(&clp->cl_lock);
        }
-       spin_unlock(&ino->i_lock);
 
        lseg = send_layoutget(lo, ctx, iomode);
-       if (!lseg) {
-               spin_lock(&ino->i_lock);
-               if (list_empty(&lo->plh_segs)) {
-                       spin_lock(&clp->cl_lock);
-                       list_del_init(&lo->plh_layouts);
-                       spin_unlock(&clp->cl_lock);
-                       clear_bit(NFS_LAYOUT_BULK_RECALL, &lo->plh_flags);
-               }
-               spin_unlock(&ino->i_lock);
+       if (!lseg && first) {
+               spin_lock(&clp->cl_lock);
+               list_del_init(&lo->plh_layouts);
+               spin_unlock(&clp->cl_lock);
        }
        atomic_dec(&lo->plh_outstanding);
        put_layout_hdr(lo);
@@ -951,7 +964,7 @@ pnfs_put_deviceid_cache(struct nfs_client *clp)
 {
        struct pnfs_deviceid_cache *local = clp->cl_devid_cache;
 
-       dprintk("--> %s cl_devid_cache %p\n", __func__, clp->cl_devid_cache);
+       dprintk("--> %s ({%d})\n", __func__, atomic_read(&local->dc_ref));
        if (atomic_dec_and_lock(&local->dc_ref, &clp->cl_lock)) {
                int i;
                /* Verify cache is empty */