zhaohai666 opened a new pull request, #1404:
URL: https://github.com/apache/rocketmq-clients/pull/1404
# fix(rust): keep the telemetry session alive across server rolling
deployments
## Summary
This PR fixes a cluster of interrelated session-lifecycle defects in the
Rust client
(branch `fix/rust-telemetry-session-defects`, **4 commits** touching
`rust/src/session.rs`,
`rust/src/client.rs`, `rust/src/simple_consumer.rs`,
`rust/src/push_consumer.rs`,
`rust/tests/rolling_restart_it.rs`), all aligned with the Java reference
client.
Before the fix, every rolling deployment of the server permanently broke the
client's
long-lived session: the `Settings` object never grew (consistently 88 bytes)
because the client
degraded into one-off telemetry streams lasting 3–43 ms, one per 30-second
sync cycle. The
accumulation of dead endpoints made every settings report cycle fail and
interrupted consumption
for 8–50 seconds. Two secondary defects made things worse: the gRPC deadline
of `receive` did not
match the documented contract (100% failure when `long_polling_timeout` was
set below 5s), and
settings pushed by the server were silently discarded.
**Root causes, all fixed here**
1. `sync_settings` rebuilt the telemetry stream on a *shadow* session — a
throw-away clone — so
the rebuilt stream was lost immediately.
2. The session map only ever grew: dead endpoints were never evicted, so
every report cycle kept
failing on them.
3. No HTTP/2 keep-alive, so a server-side rolling deployment silently broke
the long-lived
connection.
4. After a failover the new server instance answers unary RPCs with `40015
cannot find client` /
`50001 settings is null`; the client only logged those errors and never
re-registered.
5. Server-pushed settings were discarded by `SimpleConsumer` and had no
effect on the receive
deadline even for `PushConsumer`.
6. Five code paths panicked (`unwrap`) on session lookup when the server was
unreachable.
---
## Fix 1: reuse the registered session and keep the session map bounded
(`74ccc3a6`)
- `SessionManager::sync_settings` now syncs on the **registered** session
and evicts it when the
sync is unrecoverable, instead of rebuilding the stream on a shadow clone
that is dropped right
away.
- Sessions are tracked with a shared last-activity timestamp
(`shadow_session()` shares the
`Arc<AtomicU64>`), plus `is_healthy()` / `idle_duration()`. A periodic
task (every 30s) drops
sessions whose telemetry stream is gone or that nobody has used for 30
minutes, mirroring
`ClientManagerImpl#clearIdleRpcClients` in Java.
- HTTP/2 keep-alive is configured on every endpoint (300s interval / 30s
timeout /
`keep_alive_while_idle`), aligned with `RpcClientImpl.KEEP_ALIVE_*` of the
Java client. The
interval must stay at 300s because grpc-java servers reject faster client
pings with
`GOAWAY(ENHANCE_YOUR_CALM)`.
- The receive deadline is now `long_polling_timeout + timeout`, as
documented and as the Java
client computes it (`awaitDuration.plus(tolerance)`). With the old
deadline the server could
hold the response for exactly as long as the client was willing to wait,
so every poll failed
once `long_polling_timeout` was short. Verified against a live cluster:
the old behaviour
produced `code 40000 "The deadline time remaining is not enough for
polling"` from the server.
- `PushConsumer` applies server-pushed settings (`receive_batch_size`,
`long_polling_timeout`, `fifo`), mirroring `PushSubscriptionSettings#sync`.
## Fix 2: propagate session errors instead of panicking (`07b4c5cb`)
`send_message`, `recall_message`, `receive_message`, `ack_message` and
`change_invisible_duration` called `.unwrap()` on the session lookup. A
server-side rolling
deployment made the lookup fail, and the panic took down the whole client
process — an outage
amplifier on top of an outage. All five paths now propagate the error with
`?`.
## Fix 3: proactive, error-driven session recovery (`9c5c0ff2`)
Mirrors the Java client's `ClientSessionImpl#renewRequestObserver` and
`ClientImpl#recoverTransport`:
- The telemetry stream task notifies the client layer through a
stream-closed channel the moment
the server closes the stream (EOF or error) — no more waiting for the next
periodic sync.
- `Client::start` listens on that channel and schedules a reconnect that
retries on the
**registered** session **every second**. The delay is deliberately fixed,
not exponential: with
a growing back-off the client keeps missing the moment the new instance
becomes ready, which
measurements confirmed. `SessionManager::reconnect_session` takes the
session out of the map
for the duration of the RPC (so other callers are never blocked on the
network round trip) and
puts it back on failure, so an idle client always keeps a driver for
recovery.
Re-establishing the stream also re-registers the client with the new
server instance.
- A heartbeat *business* error (e.g. `40015 cannot find client`, `50001
settings is null` after a
failover) now triggers the same reconnect instead of being only logged.
The heartbeat interval
drops from 30s to 10s (`ClientImpl#HEART_BEAT_PERIOD`), and a session is
evicted only after 2
consecutive transport failures (`HEART_BEAT_FAILURE_THRESHOLD`), so a
transient blip no longer
tears down a working connection.
- The long-polling timeout lives behind a shared handle
(`Arc<parking_lot::RwLock<Duration>>`): when the server pushes new
settings, the new value
takes effect on the receive deadline **and** on the request body of every
consumer without
recreating anything.
- `SimpleConsumer` no longer discards server-pushed settings: it applies the
pushed
`long_polling_timeout` (`SimpleSubscriptionSettings#sync`).
## Fix 4: integration-test robustness (`a6f99a46`)
The live integration test tolerates the client recovering faster than the
external readiness
probe.
---
## Testing
**Offline suite** (`cargo fmt`, `cargo clippy --all-features -- -D warnings`
— the CI gate,
`cargo test --all-features`): all green, 98 lib tests including new coverage
for
`reconnect_session` (stream rebuilt on the registered session / session kept
on failure /
no-op for unknown endpoints), settings-sync eviction, shared long-polling
timeout, and the
receive-deadline contract.
**Live-cluster verification** (RocketMQ 5.5.1, remoting broker 10911 + gRPC
proxy 8081 in
CLUSTER mode, `kill -9` of the proxy to simulate a rolling deployment):
| Metric | Before | After |
|---|---|---|
| Telemetry stream pattern after deployment | one-off 3–43 ms streams, one
per 30s cycle, forever | single long-lived stream, re-established within one
retry cycle |
| Dead-endpoint accumulation | every settings report fails, 8–50s
consumption gaps per cycle | none (eviction + 2-failure threshold) |
| Client-side recovery after the new instance is ready | 30s sync polling,
no re-registration | ≤ 2s (next 1s retry tick) |
| Measured end-to-end recovery window (`kill -9` → first successful receive)
| 42–70s | **5.9s** when broker routes are warm (~20s when the proxy must
rebuild routes from broker heartbeats, which no client can skip) |
| Server-pushed settings | discarded by SimpleConsumer; no deadline effect |
applied by both consumers; deadline + request body updated live |
| Unreachable server during `receive`/`send`/... | process panic | error
propagated |
The remaining window is server-side: the restarted proxy itself needs time
to start and to
rebuild topic routes; the client reconnects on the first retry tick once the
gRPC port accepts
connections. `SimpleConsumer` applying a server-pushed
`long_polling_timeout: 20s` was observed
in the live test logs.
The rolling-restart scenario is captured in
`rust/tests/rolling_restart_it.rs`. It is opt-in
(`ROCKETMQ_ROLLING_IT=1`) because it needs a live cluster plus an external
driver that kills and
restarts the proxy while the test measures the interruption window; in a
plain `cargo test` run
it returns immediately.
---
## Commits
- `74ccc3a6` — stop rebuilding telemetry streams on shadow sessions and keep
the session map bounded
- `07b4c5cb` — propagate session errors instead of panicking when the server
is unreachable
- `9c5c0ff2` — recover the telemetry stream proactively instead of waiting
for periodic syncs
- `a6f99a46` — tolerate the client recovering faster than the readiness
probe (test)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]