ceph: fix snap writeback when racing with writes
Sage Weil [Tue, 26 Jul 2011 18:26:31 +0000 (11:26 -0700)]
There are two problems that come up when we try to queue a capsnap while a
write is in progress:

 - The FILE_WR cap is held, but not yet dirty, so we may queue a capsnap
   with dirty == 0.  That will crash later in __ceph_flush_snaps().  Or
   on the FILE_WR cap if a write is in progress.
 - We may not have i_head_snapc set, which causes problems pretty quickly.
   Look to the snaprealm in this case.

Reviewed-by: Yehuda Sadeh <yehuda@hq.newdream.net>
Signed-off-by: Sage Weil <sage@newdream.net>

fs/ceph/snap.c

index 54b14de..ac030c9 100644 (file)
@@ -449,6 +449,15 @@ void ceph_queue_cap_snap(struct ceph_inode_info *ci)
        spin_lock(&inode->i_lock);
        used = __ceph_caps_used(ci);
        dirty = __ceph_caps_dirty(ci);
+
+       /*
+        * If there is a write in progress, treat that as a dirty Fw,
+        * even though it hasn't completed yet; by the time we finish
+        * up this capsnap it will be.
+        */
+       if (used & CEPH_CAP_FILE_WR)
+               dirty |= CEPH_CAP_FILE_WR;
+
        if (__ceph_have_pending_cap_snap(ci)) {
                /* there is no point in queuing multiple "pending" cap_snaps,
                   as no new writes are allowed to start when pending, so any
@@ -456,14 +465,22 @@ void ceph_queue_cap_snap(struct ceph_inode_info *ci)
                   cap_snap.  lucky us. */
                dout("queue_cap_snap %p already pending\n", inode);
                kfree(capsnap);
-       } else if (ci->i_wrbuffer_ref_head || (used & CEPH_CAP_FILE_WR) ||
+       } else if (ci->i_wrbuffer_ref_head ||
                   (dirty & (CEPH_CAP_AUTH_EXCL|CEPH_CAP_XATTR_EXCL|
                             CEPH_CAP_FILE_EXCL|CEPH_CAP_FILE_WR))) {
                struct ceph_snap_context *snapc = ci->i_head_snapc;
 
-               dout("queue_cap_snap %p cap_snap %p queuing under %p\n", inode,
-                    capsnap, snapc);
+               /*
+                * if we are a sync write, we may need to go to the snaprealm
+                * to get the current snapc.
+                */
+               if (!snapc)
+                       snapc = ci->i_snap_realm->cached_context;
+
+               dout("queue_cap_snap %p cap_snap %p queuing under %p %s\n",
+                    inode, capsnap, snapc, ceph_cap_string(dirty));
                ihold(inode);
+               BUG_ON(dirty == 0);
 
                atomic_set(&capsnap->nref, 1);
                capsnap->ci = ci;