Patch 1/2 of this patchset removed the pin's most visible symptom:
hundreds of poll passes a second. A peer that has stopped reading is
now entirely silent -- still never probed, never disconnected, nothing
logged, and no longer costing the CPU that used to give it away.
Let the ordinary probe and timeout run for such a session instead. A
session whose peer appears to take nothing for two probe intervals is
terminated, and says so through the existing "no response to inactivity
probe" message, extended to name the bytes still queued.
That rule is already written down in raft_conn_update_probe_interval()
-- "Connection will be dropped if inactivity will last twice that time"
-- and a stalled raft connection does not reach it either today; only
raft's 500 message backlog cap eventually does. This makes the
documented rule hold for every jsonrpc session, raft's included.
What matters is not how fast a peer reads but whether the server can see
it reading. All the server sees is its own queue shrinking, and that
happens only when the socket send buffer has room -- which the peer
controls, by acknowledging data already sent and by reopening its
receive window as it reads. A peer that moves the queue within the
interval is unaffected, however slowly it consumes; a peer that moves
nothing is indistinguishable from one that stopped, and is now treated
as one. On this host, at the 5 s default inactivity_probe, that line
falls between 10 and 13 kB/s, and it scales inversely with
inactivity_probe. Raising that setting is how a deployment gives its
consumers longer to become visible.
Testing. Applied on top of patch 1/2 of this patchset, whose CPU fix
this does not touch; what changes is which sessions survive. At the 5 s
TCP default:
client without this patch with it
stops reading kept dropped at t+12.1 s
reads 4 kB/s kept dropped at t+28 s
reads 20 kB/s kept kept
Signed-off-by: Aeliton G. Silva <[email protected]>
Assisted-by: Claude Opus 5, Claude Code
---
lib/reconnect.c | 31 ++++++++------
python/ovs/reconnect.py | 31 ++++++++------
tests/reconnect.at | 92 ++++++++++++++++++++++++++++++++++++-----
3 files changed, 119 insertions(+), 35 deletions(-)
diff --git a/lib/reconnect.c b/lib/reconnect.c
index 1f9bf83df..f033cecf6 100644
--- a/lib/reconnect.c
+++ b/lib/reconnect.c
@@ -500,9 +500,10 @@ reconnect_connect_failed(struct reconnect *fsm, long long
int now, int error)
* the probe interval timer, so that the connection is known not to be idle.
*
* 'queued_bytes' is data queued for the peer that could not be sent. While
- * it is nonzero the FSM stops asking to be woken up to attempt a receive:
- * the caller evidently cannot get data to this peer, so no receive it makes
- * can settle anything, and waking to try only burns CPU. */
+ * it is nonzero the probe interval is allowed to expire on its own schedule
+ * rather than asking for a fast wake-up to attempt a receive: the caller
+ * evidently cannot get data to this peer, so no receive it makes can settle
+ * anything, and the ordinary probe and timeout should run their course. */
void
reconnect_activity(struct reconnect *fsm, long long int now,
size_t queued_bytes)
@@ -566,13 +567,10 @@ reconnect_deadline__(const struct reconnect *fsm, long
long int now)
case S_ACTIVE:
if (fsm->probe_interval) {
- if (fsm->queued_bytes) {
- return LLONG_MAX;
- }
-
long long int base = MAX(fsm->last_activity, fsm->state_entered);
long long int expiration = base + fsm->probe_interval;
- if (now < expiration || fsm->last_receive_attempt >= expiration) {
+ if (now < expiration || fsm->last_receive_attempt >= expiration
+ || fsm->queued_bytes) {
/* We still have time before the expiration or the time has
* already passed and there was no activity. In the first case
* we need to wait for the expiration, in the second - we're
@@ -591,7 +589,8 @@ reconnect_deadline__(const struct reconnect *fsm, long long
int now)
case S_IDLE:
if (fsm->probe_interval) {
long long int expiration = fsm->state_entered +
fsm->probe_interval;
- if (now < expiration || fsm->last_receive_attempt >= expiration) {
+ if (now < expiration || fsm->last_receive_attempt >= expiration
+ || fsm->queued_bytes) {
return expiration;
} else {
return now + 1;
@@ -663,9 +662,17 @@ reconnect_run(struct reconnect *fsm, long long int now)
return RECONNECT_PROBE;
case S_IDLE:
- VLOG_ERR("%s: no response to inactivity probe after %.3g "
- "seconds, disconnecting",
- fsm->name, (now - fsm->state_entered) / 1000.0);
+ if (fsm->queued_bytes) {
+ VLOG_ERR("%s: no response to inactivity probe after %.3g "
+ "seconds, with %"PRIuSIZE" bytes still queued for "
+ "the peer, disconnecting", fsm->name,
+ (now - fsm->state_entered) / 1000.0,
+ fsm->queued_bytes);
+ } else {
+ VLOG_ERR("%s: no response to inactivity probe after %.3g "
+ "seconds, disconnecting",
+ fsm->name, (now - fsm->state_entered) / 1000.0);
+ }
return RECONNECT_DISCONNECT;
case S_RECONNECT:
diff --git a/python/ovs/reconnect.py b/python/ovs/reconnect.py
index 2ff4fc6c4..382c1dbf1 100644
--- a/python/ovs/reconnect.py
+++ b/python/ovs/reconnect.py
@@ -94,14 +94,12 @@ class Reconnect(object):
@staticmethod
def deadline(fsm, now):
if fsm.probe_interval:
- if fsm.queued_bytes:
- return None
-
base = max(fsm.last_activity, fsm.state_entered)
expiration = base + fsm.probe_interval
if (now < expiration or
fsm.last_receive_attempt is None or
- fsm.last_receive_attempt >= expiration):
+ fsm.last_receive_attempt >= expiration or
+ fsm.queued_bytes):
# We still have time before the expiration or the time has
# already passed and there was no activity. In the first
# case we need to wait for the expiration, in the second -
@@ -133,7 +131,8 @@ class Reconnect(object):
expiration = fsm.state_entered + fsm.probe_interval
if (now < expiration or
fsm.last_receive_attempt is None or
- fsm.last_receive_attempt >= expiration):
+ fsm.last_receive_attempt >= expiration or
+ fsm.queued_bytes):
return expiration
else:
return now + 1
@@ -141,9 +140,16 @@ class Reconnect(object):
@staticmethod
def run(fsm, now):
- vlog.err("%s: no response to inactivity probe after %.3g "
- "seconds, disconnecting"
- % (fsm.name, (now - fsm.state_entered) / 1000.0))
+ if fsm.queued_bytes:
+ vlog.err("%s: no response to inactivity probe after %.3g "
+ "seconds, with %d bytes still queued for the peer, "
+ "disconnecting"
+ % (fsm.name, (now - fsm.state_entered) / 1000.0,
+ fsm.queued_bytes))
+ else:
+ vlog.err("%s: no response to inactivity probe after %.3g "
+ "seconds, disconnecting"
+ % (fsm.name, (now - fsm.state_entered) / 1000.0))
return DISCONNECT
class Reconnect(object):
@@ -496,10 +502,11 @@ class Reconnect(object):
not to be idle.
'queued_bytes' is data queued for the peer that could not be sent.
- While it is nonzero the FSM stops asking to be woken up to attempt a
- receive: the caller evidently cannot get data to this peer, so no
- receive it makes can settle anything, and waking to try only burns
- CPU."""
+ While it is nonzero the probe interval is allowed to expire on its own
+ schedule rather than asking for a fast wake-up to attempt a receive:
+ the caller evidently cannot get data to this peer, so no receive it
+ makes can settle anything, and the ordinary probe and timeout should
+ run their course."""
self.queued_bytes = queued_bytes
if self.state != Reconnect.Active:
self._transition(now, Reconnect.Active)
diff --git a/tests/reconnect.at b/tests/reconnect.at
index 650ca3485..d6fd3ccf1 100644
--- a/tests/reconnect.at
+++ b/tests/reconnect.at
@@ -1365,7 +1365,7 @@ listening
])
######################################################################
-RECONNECT_CHECK([no wake-up while data is queued],
+RECONNECT_CHECK([peer with queued data is dropped],
[enable
# Connection succeeds.
@@ -1375,9 +1375,12 @@ connected
# Data is queued for the peer that we could not send.
activity 1000
-# Long past the probe interval the FSM asks for no wake-up at all, and
-# nothing happens: the connection is neither probed nor disconnected.
-advance 60000
+# Past the probe interval the connection is probed, on the ordinary
+# schedule rather than after a fast wake-up.
+timeout
+run
+
+# And a probe interval later, with nothing taken, it is given up on.
timeout
run
],
@@ -1398,15 +1401,25 @@ connected
# Data is queued for the peer that we could not send.
activity 1000
-# Long past the probe interval the FSM asks for no wake-up at all, and
-# nothing happens: the connection is neither probed nor disconnected.
-advance 60000
+# Past the probe interval the connection is probed, on the ordinary
+# schedule rather than after a fast wake-up.
+timeout
+ advance 5000 ms
+
+### t=6000 ###
+ in ACTIVE for 5000 ms (0 ms backoff)
+run
+ should send probe
+ in IDLE for 0 ms (0 ms backoff)
-### t=61000 ###
- in ACTIVE for 60000 ms (0 ms backoff)
+# And a probe interval later, with nothing taken, it is given up on.
timeout
- no timeout
+ advance 5000 ms
+
+### t=11000 ###
+ in IDLE for 5000 ms (0 ms backoff)
run
+ should disconnect
])
######################################################################
@@ -1445,16 +1458,19 @@ advance 10000
### t=11000 ###
in ACTIVE for 10000 ms (0 ms backoff)
run
+ should send probe
+ in IDLE for 0 ms (0 ms backoff)
# The queue drains, so the ordinary probe interval applies again.
activity 0
+ in ACTIVE for 0 ms (0 ms backoff)
created 1000, last activity 11000, last connected 1000
receive-attempted LLONG_MAX
timeout
advance 5000 ms
### t=16000 ###
- in ACTIVE for 15000 ms (0 ms backoff)
+ in ACTIVE for 5000 ms (0 ms backoff)
run
should send probe
in IDLE for 0 ms (0 ms backoff)
@@ -1507,3 +1523,57 @@ timeout
### t=6000 ###
in ACTIVE for 5000 ms (1000 ms backoff)
])
+
+######################################################################
+RECONNECT_CHECK([peer taking data is kept],
+ [enable
+run
+connected
+
+# A peer that keeps taking data is kept, however much is still queued:
+# every drain moves the expiration out, so the probe never comes due.
+activity 1000
+advance 4000
+run
+activity 500
+advance 4000
+run
+activity 200
+advance 4000
+run
+],
+ [### t=1000 ###
+enable
+ in BACKOFF for 0 ms (0 ms backoff)
+run
+ should connect
+connected
+ in ACTIVE for 0 ms (0 ms backoff)
+ created 1000, last activity 1000, last connected 1000
+ 1 successful connections out of 1 attempts, seqno 1
+ connected
+ last connected 0 ms ago, connected 0 ms total
+
+# A peer that keeps taking data is kept, however much is still queued:
+# every drain moves the expiration out, so the probe never comes due.
+activity 1000
+advance 4000
+
+### t=5000 ###
+ in ACTIVE for 4000 ms (0 ms backoff)
+run
+activity 500
+ created 1000, last activity 5000, last connected 1000
+advance 4000
+
+### t=9000 ###
+ in ACTIVE for 8000 ms (0 ms backoff)
+run
+activity 200
+ created 1000, last activity 9000, last connected 1000
+advance 4000
+
+### t=13000 ###
+ in ACTIVE for 12000 ms (0 ms backoff)
+run
+])
--
2.43.0
--
_'Esta mensagem é direcionada apenas para os endereços constantes no
cabeçalho inicial. Se você não está listado nos endereços constantes no
cabeçalho, pedimos-lhe que desconsidere completamente o conteúdo dessa
mensagem e cuja cópia, encaminhamento e/ou execução das ações citadas estão
imediatamente anuladas e proibidas'._
* **'Apesar do Magazine Luiza tomar
todas as precauções razoáveis para assegurar que nenhum vírus esteja
presente nesse e-mail, a empresa não poderá aceitar a responsabilidade por
quaisquer perdas ou danos causados por esse e-mail ou por seus anexos'.*
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev