From 3ef26343550e1716219d5f0686b0021705bcc4bc Mon Sep 17 00:00:00 2001
From: Lars Ellenberg <lars.ellenberg@linbit.com>
Date: Tue, 19 Mar 2019 16:46:24 +0100
Subject: [PATCH] drbd: fixes ahead/behind with multi-volume resources

When "pulling ahead", the first volume to notice
and act on "congestion" now prepares for all other volumes
to pull ahead on their respective next incoming write.

Similarly, the first volume to transition from L_AHEAD -> L_SYNC_SOURCE
schedules the same transition for all other volumes as well.

If we transition several times between L_AHEAD <-> L_SYNC_SOURCE
and back,  there is this scenario:

L_ESTABLISHED -> L_AHEAD -> L_SYNC_SOURCE -> L_AHEAD
 a) during first L_AHEAD, we send, and set, out-of-sync,
 b) during L_SYNC_SOURCE, we send a normal write (above),
 c) during second L_AHEAD, we again send + set out-of-sync
all for the same block.
The "normal write during resync" may set the block
in-sync on completion/destruction.
We must make sure that won't race
race with the second set out-of-sync.

In drbd_send_and_submit(), we introduce a dependency
via req->destroy_next, where the older (WRITE) requests
hold a kref on the younger ones, so we can be sure the
destructor is processed in oldest-to-youngest order.

Even if the P_RS_WRITE_ACK for the write
during-resync (b) is received *after* the completion
of the second send+set out-of-sync (c), the ordering
of the destructors will only temporarily set
in-sync, then set out-of-sync again "soon enough",
at least on the sending node.

As an optimization, if during L_AHEAD the same
block(s) are overwritten several times, we may skip
the send-out-of-sync, if we know we told the peer
before. But we must NOT skip, if there is still a
normal write in-flight to the peer (as per the
scenario above).  We check using our interval tree.

To ensure serialization of writes-during-resync
and the next set-out-of-sync in the next ahead/behin phase,
the receive_out_of_sync() now waits for all pending activity,
similar to receive_Barrier().
---
 drbd/drbd_int.h      |  1 +
 drbd/drbd_main.c     | 34 ++++++++++++++++---
 drbd/drbd_receiver.c | 10 ++++++
 drbd/drbd_req.c      | 51 ++++++++++++++++++++---------
 drbd/drbd_sender.c   | 78 ++++++++++++++++++++++++++++++++++++++++++--
 drbd/drbd_state.c    |  7 ++++
 6 files changed, 159 insertions(+), 22 deletions(-)

diff --git a/drbd/drbd_int.h b/drbd/drbd_int.h
index 416239cd268e..0e93e8b299d9 100644
--- a/drbd/drbd_int.h
+++ b/drbd/drbd_int.h
@@ -767,6 +767,7 @@ enum {
 	SEND_STATE_AFTER_AHEAD_C,
 	NOTIFY_PEERS_LOST_PRIMARY,
 	CHECKING_PEER,		/* used by make_new_urrent_uuid() to check liveliness */
+	CONN_CONGESTED,
 };
 
 /* flag bits per resource */
diff --git a/drbd/drbd_main.c b/drbd/drbd_main.c
index d9d6b3060be6..b04c87afbd2e 100644
--- a/drbd/drbd_main.c
+++ b/drbd/drbd_main.c
@@ -434,12 +434,23 @@ void tl_release(struct drbd_connection *connection,
 			expect_epoch = req->epoch;
 			expect_size ++;
 		} else {
+			const u16 s = r->net_rq_state[idx];
 			if (r->epoch != expect_epoch)
 				break;
 			if (!(r->local_rq_state & RQ_WRITE))
 				continue;
-			/* if (s & RQ_DONE): not expected */
-			/* if (!(s & RQ_NET_MASK)): not expected */
+			/* probably a "send_out_of_sync", during Ahead/Behind mode,
+			 * while at least one volume already started to resync again.
+			 * I'd very much prefer these to be in their own epoch,
+			 * or better yet, "simultaneously" go from Ahead/Behind -> SyncSource/SyncTarget
+			 * but that is currently not the case. FIXME.
+			 */
+			if ((s & RQ_NET_MASK) && !(s & RQ_EXP_BARR_ACK))
+				continue;
+			if (s & RQ_NET_DONE || (s & RQ_NET_MASK) == 0) {
+				drbd_warn(connection, "unexpected state flags: 0x%x during BarrierAck #%u\n",
+					s, barrier_nr);
+			}
 			expect_size++;
 		}
 		if (y_block_id && (struct drbd_request*)(unsigned long)y_block_id == r) {
@@ -483,10 +494,25 @@ void tl_release(struct drbd_connection *connection,
 	}
 
 	if (expect_size != set_size) {
-		if (!o_block_id)
+		if (!o_block_id) {
 			drbd_err(connection, "BAD! BarrierAck #%u received with n_writes=%u, expected n_writes=%u!\n",
 				 barrier_nr, set_size, expect_size);
-		else
+#if 0
+/* DEBUGGING AID */
+			list_for_each_entry(req, &resource->transfer_log, tl_requests)
+				if (req->epoch == expect_epoch)
+					break;
+			list_for_each_entry_from(req, &resource->transfer_log, tl_requests) {
+				if (req->epoch != expect_epoch)
+					break;
+				drbd_info(req->device, "XXX %u %llu+%u 0x%x 0x%x\n",
+					req->epoch,
+					(unsigned long long)req->i.sector, req->i.size >> 9,
+					req->local_rq_state, req->net_rq_state[idx]
+				);
+			}
+#endif
+		} else
 			drbd_err(connection, "BAD! ConfirmedStable [%p,%p] received with n_writes=%u, expected n_writes=%u!\n",
 				 req, req_y, set_size, expect_size);
 		goto bail;
diff --git a/drbd/drbd_receiver.c b/drbd/drbd_receiver.c
index b350fd1b09ae..877dd01dcb56 100644
--- a/drbd/drbd_receiver.c
+++ b/drbd/drbd_receiver.c
@@ -7289,6 +7289,15 @@ static int receive_out_of_sync(struct drbd_connection *connection, struct packet
 
 	sector = be64_to_cpu(p->sector);
 
+	/* see also process_one_request(), before drbd_send_out_of_sync().
+	 * Make sure any pending write requests that potentially may
+	 * set in-sync have drained, before setting it out-of-sync.
+	 * That should be implicit, because of the "epoch" and P_BARRIER logic,
+	 * But let's just double-check.
+	 */
+	conn_wait_active_ee_empty_or_disconnect(connection);
+	conn_wait_done_ee_empty_or_disconnect(connection);
+
 	mutex_lock(&device->bm_resync_fo_mutex);
 	switch (peer_device->repl_state[NOW]) {
 	case L_WF_SYNC_UUID:
@@ -7737,6 +7746,7 @@ void conn_disconnect(struct drbd_connection *connection)
 	int vnr, i;
 
 	clear_bit(CONN_DRY_RUN, &connection->flags);
+	clear_bit(CONN_CONGESTED, &connection->flags);
 
 	if (connection->cstate[NOW] == C_STANDALONE)
 		return;
diff --git a/drbd/drbd_req.c b/drbd/drbd_req.c
index 0ae20a8a3537..89e69e74bf13 100644
--- a/drbd/drbd_req.c
+++ b/drbd/drbd_req.c
@@ -747,11 +747,22 @@ static void mod_rq_state(struct drbd_request *req, struct bio_and_error *m,
 		ktime_get_accounting(req->net_done_kt[peer_device->node_id]);
 
 		if (peer_device->repl_state[NOW] == L_AHEAD &&
-		    atomic_read(ap_in_flight) == 0 &&
-		    !test_and_set_bit(AHEAD_TO_SYNC_SOURCE, &peer_device->flags)) {
-			peer_device->start_resync_side = L_SYNC_SOURCE;
-			peer_device->start_resync_timer.expires = jiffies + HZ;
-			add_timer(&peer_device->start_resync_timer);
+		    atomic_read(ap_in_flight) == 0) {
+			struct drbd_peer_device *pd;
+			int vnr;
+			/* The first peer device to notice that it is time to
+			 * go Ahead -> SyncSource tries to trigger that
+			 * transition for *all* peer devices currently in
+			 * L_AHEAD for this connection. */
+			idr_for_each_entry(&peer_device->connection->peer_devices, pd, vnr) {
+				if (pd->repl_state[NOW] != L_AHEAD)
+					continue;
+				if (test_and_set_bit(AHEAD_TO_SYNC_SOURCE, &pd->flags))
+					continue; /* already done */
+				pd->start_resync_side = L_SYNC_SOURCE;
+				pd->start_resync_timer.expires = jiffies + HZ;
+				add_timer(&pd->start_resync_timer);
+			}
 		}
 
 		/* in ahead/behind mode, or just in case,
@@ -1283,6 +1294,9 @@ static void __maybe_pull_ahead(struct drbd_device *device, struct drbd_connectio
 	u32 cong_fill = 0, cong_extents = 0;
 	struct drbd_peer_device *peer_device = conn_peer_device(connection, device->vnr);
 
+	if (connection->agreed_pro_version < 96)
+		return;
+
 	rcu_read_lock();
 	nc = rcu_dereference(connection->transport.net_conf);
 	if (nc) {
@@ -1293,8 +1307,7 @@ static void __maybe_pull_ahead(struct drbd_device *device, struct drbd_connectio
 		on_congestion = OC_BLOCK;
 	}
 	rcu_read_unlock();
-	if (on_congestion == OC_BLOCK ||
-	    connection->agreed_pro_version < 96)
+	if (on_congestion == OC_BLOCK)
 		return;
 
 	if (on_congestion == OC_PULL_AHEAD && peer_device->repl_state[NOW] == L_AHEAD)
@@ -1307,21 +1320,29 @@ static void __maybe_pull_ahead(struct drbd_device *device, struct drbd_connectio
 	if (!get_ldev_if_state(device, D_UP_TO_DATE))
 		return;
 
-	if (cong_fill &&
-	    atomic_read(&connection->ap_in_flight) +
-	    atomic_read(&connection->rs_in_flight) >= cong_fill) {
-		drbd_info(device, "Congestion-fill threshold reached\n");
-		congested = true;
+	/* if an other volume already found that we are congested, short circuit. */
+	congested = test_bit(CONN_CONGESTED, &connection->flags);
+
+	if (!congested && cong_fill) {
+		int n = atomic_read(&connection->ap_in_flight) +
+			atomic_read(&connection->rs_in_flight);
+		if (n >= cong_fill) {
+			drbd_info(device, "Congestion-fill threshold reached (%d >= %d)\n", n, cong_fill);
+			congested = true;
+		}
 	}
 
-	if (device->act_log->used >= cong_extents) {
-		drbd_info(device, "Congestion-extents threshold reached\n");
+	if (!congested && device->act_log->used >= cong_extents) {
+		drbd_info(device, "Congestion-extents threshold reached (%d >= %d)\n",
+			device->act_log->used, cong_extents);
 		congested = true;
 	}
 
 	if (congested) {
 		struct drbd_resource *resource = device->resource;
 
+		set_bit(CONN_CONGESTED, &connection->flags);
+
 		/* start a new epoch for non-mirrored writes */
 		start_new_tl_epoch(resource);
 
@@ -1484,7 +1505,7 @@ static int drbd_process_write_request(struct drbd_request *req)
 				in_tree = true;
 			}
 			_req_mod(req, QUEUE_FOR_NET_WRITE, peer_device);
-		} else if (drbd_set_out_of_sync(peer_device, req->i.sector, req->i.size))
+		} else
 			_req_mod(req, QUEUE_FOR_SEND_OOS, peer_device);
 	}
 
diff --git a/drbd/drbd_sender.c b/drbd/drbd_sender.c
index b5035bc1a500..0cda7a28121b 100644
--- a/drbd/drbd_sender.c
+++ b/drbd/drbd_sender.c
@@ -2684,6 +2684,43 @@ static void maybe_send_barrier(struct drbd_connection *connection, unsigned int
 	}
 }
 
+static bool is_write_in_flight(struct drbd_peer_device *peer_device, struct drbd_interval *in)
+{
+	struct drbd_device *device = peer_device->device;
+	struct drbd_request *req;
+	struct drbd_interval *i;
+	sector_t sector = in->sector;
+	int size = in->size;
+	int idx = peer_device->node_id;
+	int s;
+	bool in_flight = false;
+
+	if (idx < 0 || idx >= DRBD_NODE_ID_MAX) {
+		drbd_warn(peer_device, "is_write_in_flight: BAD idx: %d\n", idx);
+		return false;
+	}
+
+	spin_lock_irq(&device->resource->req_lock);
+	drbd_for_each_overlap(i, &device->write_requests, sector, size) {
+		if (i == in)
+			continue;
+		if (!i->local)
+			continue;
+		/* don't care for i->completed, in DRBD_PROT_A we
+		 * are more interested in RQ_NET_DONE instead */
+		req = container_of(i, struct drbd_request, i);
+		s = req->net_rq_state[idx];
+		if ((s & RQ_NET_SENT) == 0) /* not even sent: ignore */
+			continue;
+		if ((s & RQ_NET_DONE) == RQ_NET_DONE) /* already done: ignore */
+			continue;
+		in_flight = true;
+		break;
+	}
+	spin_unlock_irq(&device->resource->req_lock);
+	return in_flight;
+}
+
 static int process_one_request(struct drbd_connection *connection)
 {
 	struct bio_and_error m;
@@ -2693,7 +2730,7 @@ static int process_one_request(struct drbd_connection *connection)
 			conn_peer_device(connection, device->vnr);
 	unsigned s = drbd_req_state_by_peer_device(req, peer_device);
 	bool do_send_unplug = req->local_rq_state & RQ_UNPLUG;
-	int err;
+	int err = 0;
 	enum drbd_req_event what;
 
 	/* pre_send_jif[] is used in net_timeout_reached() */
@@ -2729,12 +2766,47 @@ static int process_one_request(struct drbd_connection *connection)
 			 * No more barriers will be sent, until we leave AHEAD mode again. */
 			maybe_send_barrier(connection, req->epoch);
 
+			/* make sure the state change to L_AHEAD/L_BEHIND
+			 * arrives before the first set-out-of-sync information */
 			if (!peer_device->todo.was_ahead) {
 				peer_device->todo.was_ahead = true;
 				drbd_send_current_state(peer_device);
 			}
-			err = drbd_send_out_of_sync(peer_device, &req->i);
-			what = OOS_HANDED_TO_NETWORK;
+
+			/* Scenario:
+			 * L_ESTABLISHED -> L_AHEAD -> L_SYNC_SOURCE -> L_AHEAD
+			 *  a) during first L_AHEAD, we send, and set, out-of-sync,
+			 *  b) during L_SYNC_SOURCE, we send a normal write (above),
+			 *  c) during second L_AHEAD, we again send + set out-of-sync
+			 * all for the same block.
+			 * The "normal write during resync" may set the block
+			 * in-sync on completion/destruction.
+			 * We must make sure that won't race
+			 * race with the second set out-of-sync.
+			 *
+			 * In drbd_send_and_submit(), we introduce a dependency
+			 * via req->destroy_next, where the older (WRITE) requests
+			 * hold a kref on the younger ones, so we can be sure the
+			 * destructor is processed in oldest-to-youngest order.
+			 *
+			 * Even if the P_RS_WRITE_ACK for the write
+			 * during-resync (b) is received *after* the completion
+			 * of the second send+set out-of-sync (c), the ordering
+			 * of the destructors will only temporarily set
+			 * in-sync, then set out-of-sync again "soon enough",
+			 * at least on this node.
+			 *
+			 * As an optimization, if during L_AHEAD the same
+			 * block(s) are overwritten several times, we may skip
+			 * the send-out-of-sync, if we know we told the peer
+			 * before. But we must NOT skip, if there is still a
+			 * normal write in-flight to the peer (as per the
+			 * scenario above).  We check using our interval tree.
+			 */
+			if (drbd_set_out_of_sync(peer_device, req->i.sector, req->i.size) ||
+			    is_write_in_flight(peer_device, &req->i))
+				err = drbd_send_out_of_sync(peer_device, &req->i);
+			what = OOS_HANDED_TO_NETWORK; /* Well, most of the time, anyways. */
 		}
 	} else {
 		maybe_send_barrier(connection, req->epoch);
diff --git a/drbd/drbd_state.c b/drbd/drbd_state.c
index d79e82a50624..1bf7064a01cc 100644
--- a/drbd/drbd_state.c
+++ b/drbd/drbd_state.c
@@ -2396,9 +2396,16 @@ static void finish_state_change(struct drbd_resource *resource, struct completio
 			if (repl_state[OLD] == L_AHEAD && repl_state[NEW] == L_SYNC_SOURCE) {
 				set_bit(SEND_STATE_AFTER_AHEAD, &peer_device->flags);
 				set_bit(SEND_STATE_AFTER_AHEAD_C, &connection->flags);
+
+				clear_bit(CONN_CONGESTED, &connection->flags);
 				wake_up(&connection->sender_work.q_wait);
 			}
 
+			/* We start writing locally without replicating the changes,
+			 * better start a new data generation */
+			if (repl_state[OLD] != L_AHEAD && repl_state[NEW] == L_AHEAD)
+				create_new_uuid = true;
+
 			if (lost_contact_to_peer_data(peer_disk_state)) {
 				if (role[NEW] == R_PRIMARY && !test_bit(UNREGISTERED, &device->flags) &&
 				    (disk_state[NEW] == D_UP_TO_DATE || one_peer_disk_up_to_date[NEW]))
-- 
2.26.2

