aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/block
diff options
context:
space:
mode:
authorLars Ellenberg <lars.ellenberg@linbit.com>2011-12-05 08:39:25 -0500
committerPhilipp Reisner <philipp.reisner@linbit.com>2012-05-09 09:16:01 -0400
commit545752d5d8a2e01b4fcb23a61ec732b2b26cbe1a (patch)
tree1614e4de9034bb2135becfebacc20eda30b64576 /drivers/block
parent763eb63625a625e4d160cbb4cce2bcdb40141b97 (diff)
drbd: fix race between disconnect and receive_state
If the asender thread, or request_timer_fn(), or some other part of the code, decided to drop the connection (because of timeout or other), but the receiver just now was processing a P_STATE packet, there was a chance that receive_state() would do a hard state change "re-establishing" an already failed connection without additional handshake. Log excerpt: Remote failed to finish a request within ko-count * timeout peer( Secondary -> Unknown ) conn( Connected -> Timeout ) pdsk( UpToDate -> DUnknown ) asender terminated ... peer( Unknown -> Secondary ) conn( Timeout -> Connected ) pdsk( DUnknown -> UpToDate ) peer_isp( 0 -> 1 ) ... Connection closed peer( Secondary -> Unknown ) conn( Connected -> Unconnected ) pdsk( UpToDate -> DUnknown ) peer_isp( 1 -> 0 ) receiver terminated Impact: while the connection state is erroneously "Connected", requests may be queued and even sent, which would never be acknowledged, and may have been missed by the cleanup. These requests would never be completed. The next drbd_suspend_io() will then lock up, waiting forever for these requests to complete. Fixed in several code paths: Make sure the connection state is NetworkFailure or worse before starting the cleanup in drbd_disconnect(). This should make sure the cleanup won't miss any requests. Disallow receive_state() to "upgrade" the connection state from an error state. This will make sure the "illegal" state transition won't happen. For all connection failure states, relax the safe-guard in sanitize_state() again to silently mask out those state changes (e.g. Timeout -> Connected becomes Timeout -> Timeout). Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com> Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
Diffstat (limited to 'drivers/block')
-rw-r--r--drivers/block/drbd/drbd_main.c2
-rw-r--r--drivers/block/drbd/drbd_receiver.c13
2 files changed, 14 insertions, 1 deletions
diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c
index 3a5b4dec529f..f71a667f5f32 100644
--- a/drivers/block/drbd/drbd_main.c
+++ b/drivers/block/drbd/drbd_main.c
@@ -909,7 +909,7 @@ static union drbd_state sanitize_state(struct drbd_conf *mdev, union drbd_state
909 /* After a network error (+C_TEAR_DOWN) only C_UNCONNECTED or C_DISCONNECTING can follow. 909 /* After a network error (+C_TEAR_DOWN) only C_UNCONNECTED or C_DISCONNECTING can follow.
910 * If you try to go into some Sync* state, that shall fail (elsewhere). */ 910 * If you try to go into some Sync* state, that shall fail (elsewhere). */
911 if (os.conn >= C_TIMEOUT && os.conn <= C_TEAR_DOWN && 911 if (os.conn >= C_TIMEOUT && os.conn <= C_TEAR_DOWN &&
912 ns.conn != C_UNCONNECTED && ns.conn != C_DISCONNECTING && ns.conn <= C_TEAR_DOWN) 912 ns.conn != C_UNCONNECTED && ns.conn != C_DISCONNECTING && ns.conn <= C_CONNECTED)
913 ns.conn = os.conn; 913 ns.conn = os.conn;
914 914
915 /* we cannot fail (again) if we already detached */ 915 /* we cannot fail (again) if we already detached */
diff --git a/drivers/block/drbd/drbd_receiver.c b/drivers/block/drbd/drbd_receiver.c
index 2e9dfc69828f..08e694ef5ed9 100644
--- a/drivers/block/drbd/drbd_receiver.c
+++ b/drivers/block/drbd/drbd_receiver.c
@@ -3169,6 +3169,12 @@ static int receive_state(struct drbd_conf *mdev, enum drbd_packets cmd, unsigned
3169 os = ns = mdev->state; 3169 os = ns = mdev->state;
3170 spin_unlock_irq(&mdev->req_lock); 3170 spin_unlock_irq(&mdev->req_lock);
3171 3171
3172 /* If some other part of the code (asender thread, timeout)
3173 * already decided to close the connection again,
3174 * we must not "re-establish" it here. */
3175 if (os.conn <= C_TEAR_DOWN)
3176 return false;
3177
3172 /* If this is the "end of sync" confirmation, usually the peer disk 3178 /* If this is the "end of sync" confirmation, usually the peer disk
3173 * transitions from D_INCONSISTENT to D_UP_TO_DATE. For empty (0 bits 3179 * transitions from D_INCONSISTENT to D_UP_TO_DATE. For empty (0 bits
3174 * set) resync started in PausedSyncT, or if the timing of pause-/ 3180 * set) resync started in PausedSyncT, or if the timing of pause-/
@@ -3782,6 +3788,13 @@ static void drbd_disconnect(struct drbd_conf *mdev)
3782 if (mdev->state.conn == C_STANDALONE) 3788 if (mdev->state.conn == C_STANDALONE)
3783 return; 3789 return;
3784 3790
3791 /* We are about to start the cleanup after connection loss.
3792 * Make sure drbd_make_request knows about that.
3793 * Usually we should be in some network failure state already,
3794 * but just in case we are not, we fix it up here.
3795 */
3796 drbd_force_state(mdev, NS(conn, C_NETWORK_FAILURE));
3797
3785 /* asender does not clean up anything. it must not interfere, either */ 3798 /* asender does not clean up anything. it must not interfere, either */
3786 drbd_thread_stop(&mdev->asender); 3799 drbd_thread_stop(&mdev->asender);
3787 drbd_free_sock(mdev); 3800 drbd_free_sock(mdev);