fuse: don't hold lock over request_wait_answer()
authorMiklos Szeredi <mszeredi@suse.cz>
Wed, 1 Jul 2015 14:26:00 +0000 (16:26 +0200)
committerMiklos Szeredi <mszeredi@suse.cz>
Wed, 1 Jul 2015 14:26:00 +0000 (16:26 +0200)
Only hold fc->lock over sections of request_wait_answer() that actually
need it.  If wait_event_interruptible() returns zero, it means that the
request finished.  Need to add memory barriers, though, to make sure that
all relevant data in the request is synchronized.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
fs/fuse/dev.c

index 2014cee76036b51a751110ce32c995d150978857..638aafde6e22c5c9b5ec2721168a37f339b69770 100644 (file)
@@ -381,6 +381,7 @@ __releases(fc->lock)
        req->end = NULL;
        list_del_init(&req->list);
        list_del_init(&req->intr_entry);
+       smp_wmb();
        req->state = FUSE_REQ_FINISHED;
        if (test_bit(FR_BACKGROUND, &req->flags)) {
                clear_bit(FR_BACKGROUND, &req->flags);
@@ -407,19 +408,6 @@ __releases(fc->lock)
        fuse_put_request(fc, req);
 }
 
-static void wait_answer_interruptible(struct fuse_conn *fc,
-                                     struct fuse_req *req)
-__releases(fc->lock)
-__acquires(fc->lock)
-{
-       if (signal_pending(current))
-               return;
-
-       spin_unlock(&fc->lock);
-       wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED);
-       spin_lock(&fc->lock);
-}
-
 static void queue_interrupt(struct fuse_conn *fc, struct fuse_req *req)
 {
        list_add_tail(&req->intr_entry, &fc->interrupts);
@@ -428,19 +416,21 @@ static void queue_interrupt(struct fuse_conn *fc, struct fuse_req *req)
 }
 
 static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
-__releases(fc->lock)
-__acquires(fc->lock)
 {
+       int err;
+
        if (!fc->no_interrupt) {
                /* Any signal may interrupt this */
-               wait_answer_interruptible(fc, req);
-
-               if (req->state == FUSE_REQ_FINISHED)
+               err = wait_event_interruptible(req->waitq,
+                                       req->state == FUSE_REQ_FINISHED);
+               if (!err)
                        return;
 
+               spin_lock(&fc->lock);
                set_bit(FR_INTERRUPTED, &req->flags);
                if (req->state == FUSE_REQ_SENT)
                        queue_interrupt(fc, req);
+               spin_unlock(&fc->lock);
        }
 
        if (!test_bit(FR_FORCE, &req->flags)) {
@@ -448,46 +438,51 @@ __acquires(fc->lock)
 
                /* Only fatal signals may interrupt this */
                block_sigs(&oldset);
-               wait_answer_interruptible(fc, req);
+               err = wait_event_interruptible(req->waitq,
+                                       req->state == FUSE_REQ_FINISHED);
                restore_sigs(&oldset);
 
-               if (req->state == FUSE_REQ_FINISHED)
+               if (!err)
                        return;
 
+               spin_lock(&fc->lock);
                /* Request is not yet in userspace, bail out */
                if (req->state == FUSE_REQ_PENDING) {
                        list_del(&req->list);
+                       spin_unlock(&fc->lock);
                        __fuse_put_request(req);
                        req->out.h.error = -EINTR;
                        return;
                }
+               spin_unlock(&fc->lock);
        }
 
        /*
         * Either request is already in userspace, or it was forced.
         * Wait it out.
         */
-       spin_unlock(&fc->lock);
        wait_event(req->waitq, req->state == FUSE_REQ_FINISHED);
-       spin_lock(&fc->lock);
 }
 
 static void __fuse_request_send(struct fuse_conn *fc, struct fuse_req *req)
 {
        BUG_ON(test_bit(FR_BACKGROUND, &req->flags));
        spin_lock(&fc->lock);
-       if (!fc->connected)
+       if (!fc->connected) {
+               spin_unlock(&fc->lock);
                req->out.h.error = -ENOTCONN;
-       else {
+       else {
                req->in.h.unique = fuse_get_unique(fc);
                queue_request(fc, req);
                /* acquire extra reference, since request is still needed
                   after request_end() */
                __fuse_get_request(req);
+               spin_unlock(&fc->lock);
 
                request_wait_answer(fc, req);
+               /* Pairs with smp_wmb() in request_end() */
+               smp_rmb();
        }
-       spin_unlock(&fc->lock);
 }
 
 void fuse_request_send(struct fuse_conn *fc, struct fuse_req *req)