From eacb6ab3a17048c222d28319f7aa45296761f3bf Mon Sep 17 00:00:00 2001
From: Lars Ellenberg <lars.ellenberg@linbit.com>
Date: Fri, 25 May 2018 13:27:28 +0200
Subject: [PATCH] drbd: cleanup peer_requests waiting for activity log on
 disconnect

We have to properly cleanup peer_reqeuests that had been queued,
but have been blocking on the activity log, or we have a deadlock
potential where we would deadlock on disconnect, if the receiver
had been blocking in receive_Barrier, waiting for those requests
to drain.
---
 drbd/drbd_int.h      |  1 +
 drbd/drbd_receiver.c | 33 ++++++++++++++++++++++++++++++++-
 drbd/drbd_req.c      | 10 ++++++++++
 3 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/drbd/drbd_int.h b/drbd/drbd_int.h
index 169a8079..2bbfc2d3 100644
--- a/drbd/drbd_int.h
+++ b/drbd/drbd_int.h
@@ -1989,6 +1989,7 @@ extern int drbd_submit_peer_request(struct drbd_device *,
 				    struct drbd_peer_request *, const unsigned,
 				    const unsigned, const int);
 extern void drbd_cleanup_after_failed_submit_peer_request(struct drbd_peer_request *peer_req);
+extern void drbd_cleanup_peer_requests_wfa(struct drbd_device *device, struct list_head *cleanup);
 extern int drbd_free_peer_reqs(struct drbd_resource *, struct list_head *, bool is_net_ee);
 extern struct drbd_peer_request *drbd_alloc_peer_req(struct drbd_peer_device *, gfp_t) __must_hold(local);
 extern void __drbd_free_peer_req(struct drbd_peer_request *, int);
diff --git a/drbd/drbd_receiver.c b/drbd/drbd_receiver.c
index 5ce65d0f..00a19957 100644
--- a/drbd/drbd_receiver.c
+++ b/drbd/drbd_receiver.c
@@ -2872,6 +2872,32 @@ void drbd_cleanup_after_failed_submit_peer_request(struct drbd_peer_request *pee
 	change_cstate(connection, C_PROTOCOL_ERROR, CS_HARD);
 }
 
+/* Possibly "cancel" and forget about all peer_requests that had still been
+ * waiting for the activity log (wfa) when the connection to their peer failed,
+ * and pretend we never received them.
+ */
+void drbd_cleanup_peer_requests_wfa(struct drbd_device *device, struct list_head *cleanup)
+{
+	struct drbd_peer_request *peer_req, *pr_tmp;
+
+	spin_lock_irq(&device->resource->req_lock);
+	list_for_each_entry(peer_req, cleanup, wait_for_actlog) {
+		list_del(&peer_req->w.list); /* should be on the "->active_ee" list */
+		list_del_init(&peer_req->recv_order);
+		drbd_remove_peer_req_interval(device, peer_req);
+	}
+	spin_unlock_irq(&device->resource->req_lock);
+
+	list_for_each_entry_safe(peer_req, pr_tmp, cleanup, wait_for_actlog) {
+		atomic_dec(&peer_req->peer_device->wait_for_actlog);
+		dec_unacked(peer_req->peer_device);
+		list_del_init(&peer_req->wait_for_actlog);
+		drbd_may_finish_epoch(peer_req->peer_device->connection, peer_req->epoch, EV_PUT | EV_CLEANUP);
+		drbd_free_peer_req(peer_req);
+		put_ldev(device);
+	}
+}
+
 /* We may throttle resync, if the lower device seems to be busy,
  * and current sync rate is above c_min_rate.
  *
@@ -8512,8 +8538,13 @@ found:
 		struct drbd_device *device = peer_device->device;
 		u64 in_sync_b, mask;
 
+		D_ASSERT(peer_device, peer_req->flags & EE_IN_ACTLOG);
+
 		if (get_ldev(device)) {
-			in_sync_b = node_ids_to_bitmap(device, in_sync);
+			if ((peer_req->flags & EE_WAS_ERROR) == 0)
+				in_sync_b = node_ids_to_bitmap(device, in_sync);
+			else
+				in_sync_b = 0;
 			mask = ~node_id_to_mask(device->ldev->md.peers,
 						connection->peer_node_id);
 
diff --git a/drbd/drbd_req.c b/drbd/drbd_req.c
index a737b526..e556ce5c 100644
--- a/drbd/drbd_req.c
+++ b/drbd/drbd_req.c
@@ -1843,6 +1843,8 @@ struct incoming_pending_later {
 	struct list_head pending;
 	/* currently blocked e.g. by concurrent resync requests */
 	struct list_head later;
+	/* need cleanup */
+	struct list_head cleanup;
 };
 
 struct waiting_for_act_log {
@@ -1856,6 +1858,7 @@ static void ipb_init(struct incoming_pending_later *ipb)
 	INIT_LIST_HEAD(&ipb->more_incoming);
 	INIT_LIST_HEAD(&ipb->pending);
 	INIT_LIST_HEAD(&ipb->later);
+	INIT_LIST_HEAD(&ipb->cleanup);
 }
 
 static void wfa_init(struct waiting_for_act_log *wfa)
@@ -1958,6 +1961,11 @@ static bool prepare_al_transaction_nonblock(struct drbd_device *device,
 		goto out;
 
 	while ((peer_req = wfa_next_peer_request(wfa))) {
+		if (peer_req->peer_device->connection->cstate[NOW] < C_CONNECTED) {
+			list_move_tail(&peer_req->wait_for_actlog, &wfa->peer_requests.cleanup);
+			made_progress = true;
+			continue;
+		}
 		err = drbd_al_begin_io_nonblock(device, &peer_req->i);
 		if (err == -ENOBUFS)
 			break;
@@ -2150,6 +2158,8 @@ void do_submit(struct work_struct *ws)
 			if (!made_progress)
 				break;
 		}
+		if (!list_empty(&wfa.peer_requests.cleanup))
+			drbd_cleanup_peer_requests_wfa(device, &wfa.peer_requests.cleanup);
 
 		drbd_al_begin_io_commit(device);
 
-- 
2.34.1

