drbd: fix various disconnecting races
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>
diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c
index 1a8fb7a..a16089c 100644
--- a/drivers/block/drbd/drbd_nl.c
+++ b/drivers/block/drbd/drbd_nl.c
@@ -2075,10 +2075,9 @@
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 @@
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 @@
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 @@
}
}
- /* 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);