drbd: fix thread stop deadlock
authorLars Ellenberg <lars.ellenberg@linbit.com>
Mon, 2 May 2011 09:47:18 +0000 (11:47 +0200)
committerPhilipp Reisner <philipp.reisner@linbit.com>
Thu, 8 Nov 2012 15:53:00 +0000 (16:53 +0100)
There are races where the receiver may be exiting,
but still need the worker to process some stuff.

Do not wait for the receiver to die from an exiting worker.
The receiver must already be dead in case the worker decides to exit.
If the receiver was still alive, it may still want to queue work, and do
drbd_flush_workqueue() from it's disconnect cleanup code,
which would no longer be processed by an exiting worker.

This also would deadlock,
if the worker was to synchornously wait for the receiver to die.

Do not implicitly stop the worker.
The worker will only be stopped from configuration context, from
conn_reconfig_done(), drbd_adm_down() or drbd_adm_delete_connection(),
after making sure the receiver is already stopped.

Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
drivers/block/drbd/drbd_main.c
drivers/block/drbd/drbd_nl.c
drivers/block/drbd/drbd_state.c
drivers/block/drbd/drbd_worker.c

index a5c9b385223af994d3ef90125d020f3a12589ebf..427e959e4869484e88a1a61cb5fe799aa4ff1ef9 100644 (file)
@@ -503,7 +503,7 @@ restart:
        thi->task = NULL;
        thi->t_state = NONE;
        smp_mb();
-       complete(&thi->stop);
+       complete_all(&thi->stop);
        spin_unlock_irqrestore(&thi->t_lock, flags);
 
        conn_info(tconn, "Terminating %s\n", current->comm);
index 9d9b93f0885012fcea0ae1f476776d5a01b8431d..25468e2be8d03081734b9e9fdac8f17784b3aa01 100644 (file)
@@ -1050,10 +1050,16 @@ static void conn_reconfig_start(struct drbd_tconn *tconn)
 /* if still unconfigured, stops worker again. */
 static void conn_reconfig_done(struct drbd_tconn *tconn)
 {
+       bool stop_threads;
        spin_lock_irq(&tconn->req_lock);
-       if (conn_all_vols_unconf(tconn))
-               drbd_thread_stop_nowait(&tconn->worker);
+       stop_threads = conn_all_vols_unconf(tconn);
        spin_unlock_irq(&tconn->req_lock);
+       if (stop_threads) {
+               /* asender is implicitly stopped by receiver
+                * in drbd_disconnect() */
+               drbd_thread_stop(&tconn->receiver);
+               drbd_thread_stop(&tconn->worker);
+       }
 }
 
 /* Make sure IO is suspended before calling this function(). */
@@ -3123,7 +3129,6 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
 
        /* delete connection */
        if (conn_lowest_minor(adm_ctx.tconn) < 0) {
-               drbd_thread_stop(&adm_ctx.tconn->worker);
                list_del(&adm_ctx.tconn->all_tconn);
                kref_put(&adm_ctx.tconn->kref, &conn_destroy);
 
@@ -3133,7 +3138,6 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
                retcode = ERR_CONN_IN_USE;
                drbd_msg_put_info("failed to delete connection");
        }
-
        up_write(&drbd_cfg_rwsem);
        goto out;
 out_unlock:
@@ -3164,6 +3168,8 @@ int drbd_adm_delete_connection(struct sk_buff *skb, struct genl_info *info)
        }
        up_write(&drbd_cfg_rwsem);
 
+       if (retcode == NO_ERROR)
+               drbd_thread_stop(&adm_ctx.tconn->worker);
 out:
        drbd_adm_finish(info, retcode);
        return 0;
index 0512bbb952e8229250345c608099fe8b55a4b345..523ec0940673a4477447548e9ee136fa239ed7ea 100644 (file)
@@ -40,7 +40,6 @@ struct after_state_chg_work {
 static int w_after_state_ch(struct drbd_work *w, int unused);
 static void after_state_ch(struct drbd_conf *mdev, union drbd_state os,
                           union drbd_state ns, enum chg_state_flags flags);
-static void after_all_state_ch(struct drbd_tconn *tconn);
 static enum drbd_state_rv is_valid_state(struct drbd_conf *, union drbd_state);
 static enum drbd_state_rv is_valid_soft_transition(union drbd_state, union drbd_state);
 static enum drbd_state_rv is_valid_transition(union drbd_state os, union drbd_state ns);
@@ -1380,8 +1379,6 @@ static void after_state_ch(struct drbd_conf *mdev, union drbd_state os,
                        resume_next_sg(mdev);
        }
 
-       after_all_state_ch(mdev->tconn);
-
        drbd_md_sync(mdev);
 }
 
@@ -1393,12 +1390,6 @@ struct after_conn_state_chg_work {
        enum chg_state_flags flags;
 };
 
-static void after_all_state_ch(struct drbd_tconn *tconn)
-{
-       if (conn_all_vols_unconf(tconn))
-               drbd_thread_stop_nowait(&tconn->worker);
-}
-
 static int w_after_conn_state_ch(struct drbd_work *w, int unused)
 {
        struct after_conn_state_chg_work *acscw =
@@ -1461,12 +1452,7 @@ static int w_after_conn_state_ch(struct drbd_work *w, int unused)
                        spin_unlock_irq(&tconn->req_lock);
                }
        }
-
-
-       //conn_err(tconn, STATE_FMT, STATE_ARGS("nms", nms));
-       after_all_state_ch(tconn);
        kref_put(&tconn->kref, &conn_destroy);
-
        return 0;
 }
 
index 7a73bd4287c411bed3abe7e2a81bd9a9391d57c4..0da1547bb2d25ba2a51a279f340edf6f6e90cc37 100644 (file)
@@ -1744,10 +1744,6 @@ int drbd_worker(struct drbd_thread *thi)
         */
        spin_unlock_irq(&tconn->data.work.q_lock);
 
-       /* _drbd_set_state only uses stop_nowait.
-        * wait here for the exiting receiver. */
-       drbd_thread_stop(&tconn->receiver);
-
        down_read(&drbd_cfg_rwsem);
        idr_for_each_entry(&tconn->volumes, mdev, vnr) {
                D_ASSERT(mdev->state.disk == D_DISKLESS && mdev->state.conn == C_STANDALONE);
This page took 0.029636 seconds and 5 git commands to generate.