Skip to content

Commit b6030e2

Browse files
vahidlazioclaude
andcommitted
fix(pipeline): soften idle backoff — raise full-stop threshold to 5 empties
The previous idle_tier > 1 threshold stopped polling after just 2 empty responses, starving server-initiated data (push notifications, responses) until the client happened to send something. This widens the ramp so the tunnel keeps polling through early idle tiers and only goes full-stop after 5 consecutive empties. Any server response now fully resets idle state instead of barely recovering on small payloads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7b11c59 commit b6030e2

1 file changed

Lines changed: 9 additions & 23 deletions

File tree

src/tunnel_client.rs

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,13 +1616,10 @@ async fn tunnel_loop(
16161616
if inflight.is_empty() && !eof_seen {
16171617
let all_legacy = mux.all_servers_legacy();
16181618

1619-
// If every deployment is legacy and the session has gone
1620-
// idle, stop polling and just wait for client data. Apps
1621-
// maintain their own heartbeats (MQTT PINGREQ, FCM keepalive,
1622-
// etc.) which trigger client writes that send data ops — those
1623-
// act as natural polls. Mixed fleets must keep polling so
1624-
// round-robin can still land on a long-poll-capable peer.
1625-
if all_legacy && (idle_tier > 1 || consecutive_empty > 3) && !client_closed {
1619+
// Legacy-only fleets: after sustained idle, stop polling and
1620+
// wait for client data. Mixed fleets keep polling so
1621+
// round-robin can land on a long-poll-capable peer.
1622+
if all_legacy && (idle_tier > 4 || consecutive_empty > 3) && !client_closed {
16261623
read_buf.reserve(65536);
16271624
match reader.read_buf(&mut read_buf).await {
16281625
Ok(0) => break,
@@ -1638,7 +1635,6 @@ async fn tunnel_loop(
16381635
}
16391636
}
16401637

1641-
// Early backoff: first few empties still poll with delay.
16421638
let keepalive_delay = match idle_tier {
16431639
0 => Duration::from_millis(20),
16441640
1 => Duration::from_millis(80),
@@ -1748,15 +1744,10 @@ async fn tunnel_loop(
17481744
};
17491745
next_write_seq += 1;
17501746
if got_data {
1751-
let bytes = resp.d.as_ref().map(|d| d.len() as u64 * 3 / 4).unwrap_or(0);
1752-
if bytes >= 1024 {
1753-
consecutive_empty = 0;
1754-
idle_tier = idle_tier / 2;
1755-
} else {
1756-
// Small response (heartbeat ACK) — don't reset idle state.
1757-
idle_tier = idle_tier.saturating_sub(1);
1758-
}
1747+
consecutive_empty = 0;
1748+
idle_tier = 0;
17591749
consecutive_data = consecutive_data.saturating_add(1);
1750+
let bytes = resp.d.as_ref().map(|d| d.len() as u64 * 3 / 4).unwrap_or(0);
17601751
total_download_bytes += bytes;
17611752
} else if meta.was_empty_poll && consecutive_data > 0 {
17621753
// Stale empty-poll reply during an active data
@@ -1779,13 +1770,8 @@ async fn tunnel_loop(
17791770
let buf_eof = buffered_resp.eof.unwrap_or(false);
17801771
match write_tunnel_response(&mut writer, &buffered_resp).await? {
17811772
WriteOutcome::Wrote => {
1782-
let buf_bytes = buffered_resp.d.as_ref().map(|d| d.len() as u64 * 3 / 4).unwrap_or(0);
1783-
if buf_bytes >= 1024 {
1784-
consecutive_empty = 0;
1785-
idle_tier = idle_tier / 2;
1786-
} else {
1787-
idle_tier = idle_tier.saturating_sub(1);
1788-
}
1773+
consecutive_empty = 0;
1774+
idle_tier = 0;
17891775
consecutive_data = consecutive_data.saturating_add(1);
17901776
let bytes = buffered_resp.d.as_ref().map(|d| d.len() as u64 * 3 / 4).unwrap_or(0);
17911777
total_download_bytes += bytes;

0 commit comments

Comments
 (0)