drbd: fix various disconnecting races
authorLars Ellenberg <lars.ellenberg@linbit.com>
Wed, 27 Apr 2011 08:25:28 +0000 (10:25 +0200)
committerPhilipp Reisner <philipp.reisner@linbit.com>
Thu, 8 Nov 2012 15:49:08 +0000 (16:49 +0100)
If an admin requests disconnect at a time when the state handling
already disconnects/reconnects, there have been some races.

Make sure to always really stop the network threads before
returning success for disconnect. Do not pretend successfull
forced disconnect, if the state handling returned an error.

Return success from drbd_adm_down() only after all threads are finished.

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

index 1a8fb7a0db59f5493adb88ccc8010e57f3be3f19..a16089ce0a56f457faf717f069a25202e71dd6d1 100644 (file)
@@ -2075,10 +2075,9 @@ static enum drbd_state_rv conn_try_disconnect(struct drbd_tconn *tconn, bool for
        enum drbd_state_rv rv;
        if (force) {
                spin_lock_irq(&tconn->req_lock);
-               if (tconn->cstate >= C_WF_CONNECTION)
-                       _conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
+               rv = _conn_request_state(tconn, NS(conn, C_DISCONNECTING), CS_HARD);
                spin_unlock_irq(&tconn->req_lock);
-               return SS_SUCCESS;
+               return rv;
        }
 
        rv = conn_request_state(tconn, NS(conn, C_DISCONNECTING), 0);
@@ -2137,10 +2136,12 @@ int drbd_adm_disconnect(struct sk_buff *skb, struct genl_info *info)
        if (rv < SS_SUCCESS)
                goto fail;
 
+       /* No one else can reconfigure the network while I am here.
+        * The state handling only uses drbd_thread_stop_nowait(),
+        * we want to really wait here until the receiver is no more. */
+       drbd_thread_stop(&tconn->receiver);
        if (wait_event_interruptible(tconn->ping_wait,
-                                    tconn->cstate != C_DISCONNECTING)) {
-               /* Do not test for mdev->state.conn == C_STANDALONE, since
-                  someone else might connect us in the mean time! */
+                                    tconn->cstate == C_STANDALONE)) {
                retcode = ERR_INTR;
                goto fail;
        }
@@ -3043,6 +3044,10 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
                goto out_unlock;
        }
 
+       /* Make sure the network threads have actually stopped,
+        * state handling only does drbd_thread_stop_nowait(). */
+       drbd_thread_stop(&adm_ctx.tconn->receiver);
+
        /* detach */
        idr_for_each_entry(&adm_ctx.tconn->volumes, mdev, i) {
                rv = adm_detach(mdev);
@@ -3066,11 +3071,9 @@ int drbd_adm_down(struct sk_buff *skb, struct genl_info *info)
                }
        }
 
-       /* stop all threads */
-       conn_reconfig_done(adm_ctx.tconn);
-
        /* 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);
 
index 4665ad79b4aeb70b0f443cc0562dbd83727e0505..6da7aebde8dfad8e1f8731a847d1c905138bf88d 100644 (file)
@@ -4226,7 +4226,7 @@ static void drbd_disconnect(struct drbd_tconn *tconn)
                synchronize_rcu();
                kfree(old_conf);
 
-               conn_request_state(tconn, NS(conn, C_STANDALONE), CS_VERBOSE);
+               conn_request_state(tconn, NS(conn, C_STANDALONE), CS_VERBOSE | CS_HARD);
        }
 }
 
index 05628b45cf03aaf699da5dfec565f580271c12f2..8b0f31b6808acfa98faed570d328a86d8f0a0b77 100644 (file)
@@ -604,21 +604,27 @@ is_valid_soft_transition(union drbd_state os, union drbd_state ns)
 static enum drbd_state_rv
 is_valid_conn_transition(enum drbd_conns oc, enum drbd_conns nc)
 {
-       enum drbd_state_rv rv = SS_SUCCESS;
+       /* no change -> nothing to do, at least for the connection part */
+       if (oc == nc)
+               return SS_NOTHING_TO_DO;
 
-       /* Disallow Network errors to configure a device's network part */
-       if ((nc >= C_TIMEOUT && nc <= C_TEAR_DOWN) && oc <= C_DISCONNECTING)
-               rv = SS_NEED_CONNECTION;
+       /* disconnect of an unconfigured connection does not make sense */
+       if (oc == C_STANDALONE && nc == C_DISCONNECTING)
+               return SS_ALREADY_STANDALONE;
+
+       /* from C_STANDALONE, we start with C_UNCONNECTED */
+       if (oc == C_STANDALONE && nc != C_UNCONNECTED)
+               return SS_NEED_CONNECTION;
 
        /* After a network error only C_UNCONNECTED or C_DISCONNECTING may follow. */
        if (oc >= C_TIMEOUT && oc <= C_TEAR_DOWN && nc != C_UNCONNECTED && nc != C_DISCONNECTING)
-               rv = SS_IN_TRANSIENT_STATE;
+               return SS_IN_TRANSIENT_STATE;
 
        /* After C_DISCONNECTING only C_STANDALONE may follow */
        if (oc == C_DISCONNECTING && nc != C_STANDALONE)
-               rv = SS_IN_TRANSIENT_STATE;
+               return SS_IN_TRANSIENT_STATE;
 
-       return rv;
+       return SS_SUCCESS;
 }