Skip to content

fix(server): connection-plane robustness hardening (Track B)#230

Merged
pilotspacex-byte merged 6 commits into
mainfrom
fix/conn-plane-robustness-track-b
Jul 7, 2026
Merged

fix(server): connection-plane robustness hardening (Track B)#230
pilotspacex-byte merged 6 commits into
mainfrom
fix/conn-plane-robustness-track-b

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Connection-plane robustness hardening (Track B)

A self-review of Moon's connection/event-loop plane (part of a broader 5-dimension deep review — see tmp/CONN-DEEP-REVIEW.md) surfaced four robustness gaps that bite at high parallel connection counts (1K–10K clients). All four are fixed here, each with red/green tests. Both runtimes build; full lib suites pass (tokio 3115, monoio 3770); clippy/fmt clean.

R-2 — inline-command length cap

src/protocol/inline.rs, src/protocol/frame.rs

The RESP-less inline (telnet) path had no maximum line length, so a client that never sends CRLF (raw non-RESP bytes) grew the connection read buffer without bound — a per-connection memory-exhaustion vector. Added ParseConfig::max_inline_size (default 64 KB, mirrors Redis PROTO_INLINE_MAX_SIZE); parse_inline now rejects an over-cap line (complete or incomplete) with Protocol error: too big inline request.

R-1 — bounded cross-shard spsc_send

src/shard/coordinator.rs

The single dispatch primitive behind every scatter-gather command (MGET/MSET/DEL/EXISTS/SCAN/KEYS/DBSIZE, vector scatter, FT.INFO, graph traverse) retried a full SPSC ring in an unbounded loop { try_push; yield/sleep } — on tokio yield_now() reschedules with no backoff, busy-spinning a full core, and a wedged target could block graceful shutdown forever. Replaced with a bounded retry (CROSS_SHARD_PUSH_MAX_RETRIES × CROSS_SHARD_PUSH_BACKOFF, same budget as push_with_backpressure) returning PushOutcome. On give-up the message is dropped; reply-carrying callers observe the closed reply channel and synthesize a per-shard error via the path they already handle — no call-site signature change.

R-4 — accept-loop backoff on fd exhaustion

src/server/accept_backoff.rs (new), src/server/listener.rs, src/shard/event_loop.rs

All six socket-accept loops (tokio and monoio; sharded, non-sharded, TLS) retried accept() errors with zero backoff, so EMFILE/ENFILE under a connection storm pinned a shard core at 100% and flooded the log. Added AcceptBackoff: capped exponential backoff (1ms → 1s) on resource-exhaustion errors only, plus rate-limited logging; benign per-connection errors (ECONNABORTED) log without sleeping. The io_uring multishot-accept resubmit (opt-in MOON_URING=1 bridge) is documented as a scoped follow-up (synchronous CQE handler, no async context to sleep in).

R-3 — CLIENT KILL force-closes idle connections

src/client_registry.rs, connection handlers, src/shard/conn_accept.rs

CLIENT KILL only set a cooperative kill_flag checked once per batch, so a connection parked in read().await (idle, default timeout 0) was never torn down until it next sent bytes. The registry now stores each connection's socket fd and kill_clients also shutdown(2)s it, so the parked read returns Ok(0)/Err immediately (existing disconnect path). The raw-fd close is race-free: kill_clients holds the registry read lock, and a connection's RegistryGuard deregisters (needs the write lock) strictly before its stream drops — so a visible entry always has a live fd. No-op on non-unix.


Testing: each fix ships red/green unit tests (inline cap boundaries; bounded spsc_send give-up + success; AcceptBackoff classification/backoff curve; CLIENT KILL peer-EOF via UnixStream::pair). Full CI matrix run locally via OrbStack (fmt, clippy ×2, tokio + monoio lib suites).

Deep-review context (Tier-0 multi-tenant isolation and Tier-2 head-of-line-blocking tracks, not in this PR) is in tmp/CONN-DEEP-REVIEW.md.

Summary by CodeRabbit

  • New Features

    • Added a configurable TCP listen backlog for server startup.
    • Added cross-shard flush behavior so FLUSHDB/FLUSHALL clears data consistently across shards.
  • Bug Fixes

    • Improved CLIENT KILL so targeted connections close more reliably.
    • Made transaction commits replay to the correct database.
    • Hardened connection migration to avoid dropping clients when handoff fails.
  • Performance & Reliability

    • Added backpressure handling and rate-limited accept-loop retry behavior to reduce hangs under load.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@TinDang97, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81fa5a90-985b-4bbf-a3b4-f1fa84ed74fd

📥 Commits

Reviewing files that changed from the base of the PR and between f1a01a5 and 842f17c.

📒 Files selected for processing (6)
  • src/client_registry.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/shard/conn_accept.rs
  • tests/mq_integration.rs
  • tests/txn_kv_wiring.rs
  • tests/workspace_integration.rs
📝 Walkthrough

Walkthrough

This PR bundles connection-plane durability and robustness fixes: shard-wide FLUSHDB/FLUSHALL broadcast, WAL v3 XactCommitV2 db-aware crash replay, migration fail-open handling, connected-client/backpressure metrics, lazy FunctionRegistry init, configurable TCP backlog, CLIENT KILL force-close, a max inline command size, bounded cross-shard spsc_send retries, and accept-loop backoff.

Changes

Connection-plane durability and lifecycle wave

Layer / File(s) Summary
FLUSHDB/FLUSHALL cross-shard broadcast
src/server/conn/handler_monoio/mod.rs, src/server/conn/handler_sharded/mod.rs, src/shard/coordinator.rs, tests/flush_cross_shard_scatter.rs
FLUSHDB/FLUSHALL now broadcasts to other shards via coordinate_flush_broadcast, returning an error frame on partial broadcast failure; a new integration test suite validates multi-shard flush isolation and clearing.
WAL v3 XactCommitV2 db-aware replay
src/persistence/wal_v3/record.rs, src/persistence/wal_v3/replay.rs, src/transaction/mod.rs, src/server/conn/handler_monoio/txn.rs, src/server/conn/handler_sharded/txn.rs, src/server/conn/shared.rs
Adds WalRecordType::XactCommitV2 and encode_xact_commit_payload_v2 carrying db_index; CrossStoreTxn tracks db_index from TXN.BEGIN; replay selects the correct database (falling back to db 0) for crash recovery.
Connection migration fail-open
src/server/conn/handler_sharded/mod.rs, src/shard/conn_accept.rs
Migration handoff failures now retry and fall back to serving the connection locally instead of dropping it, with corrected fd ownership.
Connected-client and backpressure metrics
src/admin/metrics_setup.rs, src/client_registry.rs
New gauge/counter helpers track per-shard connected clients and cross-shard backpressure drops.
Lazy FunctionRegistry initialization
src/server/conn/core.rs, src/server/conn/handler_monoio/dispatch.rs, src/server/conn/handler_monoio/mod.rs, src/server/conn/handler_sharded/mod.rs
Per-connection FunctionRegistry is now lazily constructed on first FUNCTION/FCALL use via ensure_function_registry.
Configurable TCP backlog
src/config.rs, src/main.rs, src/shard/conn_accept.rs
Adds --tcp-backlog CLI flag applied to listener sockets at startup.

Estimated code review effort: 4 (Complex) | ~75 minutes

Connection-plane robustness hardening (Track B)

Layer / File(s) Summary
CLIENT KILL force-close
src/client_registry.rs, src/server/conn/handler_monoio/*, src/server/conn/handler_sharded/*, src/shard/conn_accept.rs
ClientLiveState gains kill_fd; kill_clients forcibly shuts down the target socket unless killing self; the raw fd is threaded through registration and migration paths.
Inline command max-size enforcement
src/protocol/frame.rs, src/protocol/inline.rs, src/protocol/parse.rs, fuzz/fuzz_targets/*
DEFAULT_MAX_INLINE_SIZE (64KB) is added and parse_inline now rejects oversized inline lines instead of growing the buffer unbounded.
Bounded cross-shard spsc_send retry
src/shard/coordinator.rs, src/shard/dispatch.rs, src/server/conn/handler_monoio/*, src/server/conn/handler_sharded/*, src/shard/scatter_*.rs, src/transaction/abort.rs
spsc_send now returns a #[must_use] PushOutcome after bounded retries, dropping messages on exhaustion; call sites now branch on or explicitly discard the outcome, surfacing partial-commit/rollback errors.
Accept-loop backoff on fd exhaustion
src/server/accept_backoff.rs, src/server/listener.rs, src/server/mod.rs, src/shard/event_loop.rs, src/shard/uring_handler.rs
New AcceptBackoff applies capped exponential backoff and rate-limited logging on EMFILE/ENFILE-class accept errors across Tokio and Monoio accept loops.

Estimated code review effort: 4 (Complex) | ~80 minutes

Possibly related PRs

  • pilotspace/moon#17: Extends the same connection migration/handler plumbing (MigratedConnectionState, MigrateConnection) touched by this PR's kill_fd and fail-open changes.
  • pilotspace/moon#69: Overlaps src/client_registry.rs/kill_clients CLIENT KILL logic, extended here with force-close and self-scoping.
  • pilotspace/moon#136: Related work on bounded cross-shard push/backpressure (PushOutcome) in the dispatch layer.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the changes, but it misses the required Summary/Checklist/Performance Impact/Notes template structure. Add the required ## Summary, ## Checklist, ## Performance Impact, and ## Notes sections, and fill the checklist and performance details explicitly.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main connection-plane hardening work in this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/conn-plane-robustness-track-b

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TinDang97 TinDang97 force-pushed the fix/conn-plane-robustness-track-b branch from d6d633a to 531038b Compare July 6, 2026 17:38
TinDang97 added a commit that referenced this pull request Jul 7, 2026
Five fixes from a deep-dive review of the connection-plane hardening PR:

1. CLIENT KILL self-kill stays cooperative (R-3 regression): kill_clients
   now takes the executing connection's id and skips the fd shutdown(2)
   for a matching self entry — flag only — so the +count reply is still
   flushed before the handler closes (Redis replies first on self-kill).
   Red/green: test_self_kill_does_not_force_close_socket_fd.

2. R-1 give-up fails loud at side-effect-bearing senders: a dropped
   MqTxnMaterialize leg turns TXN.COMMIT's reply into an explicit
   "MOONERR TXN.COMMIT partial" error (the commit is already WAL-durable;
   only foreign MQ materialization was lost — previously a silent +OK
   with lost messages) and logs at error!; a dropped GraphRollback logs
   at error! (leaked remote graph intents), both on both runtimes.

3. PushOutcome is #[must_use]: no call site can silently drop a
   cross-shard message by accident. All reply-carrying sites (which fail
   loud via the closed reply channel) now discard explicitly (let _ =).

4. spsc_send latency: a bounded ≤64-iteration spin before the first
   timed backoff preserves the old ~10µs-class recovery when the target
   ring is only transiently full — the 100µs timer sleep may round up to
   ~1ms on coarse runtime timers. Wedged-target budget unchanged.

5. Accept backoff cap 1s → 100ms: the sleep runs inside select! arms
   where the shutdown branch cannot preempt it, so the cap bounds
   shutdown latency during an fd-exhaustion storm (≤10 wakeups/s).

Both runtimes: fmt clean, clippy clean, full lib suites green.

Refs: PR #230
author: Tin Dang
TinDang97 added a commit that referenced this pull request Jul 7, 2026
Implements the recommended fixes from the connection-plane architecture
review (multi-connection × multi-db), prioritized for long-term
durability:

D-1 (durability, HIGH): cross-store TXN crash replay hardcoded db 0
  (wal_v3/replay.rs "TODO: support multi-db"). CrossStoreTxn now records
  the BEGIN-time db index; commits write a new XactCommitV2 WAL record
  (0x53) carrying it in the header, and replay restores KV ops into that
  db (out-of-range → db 0 with loud warn; v1 records keep db-0 replay).
  Red/green: test_xact_commit_v2_replays_into_selected_db + fallback test.

R-6 (lifecycle): connection migration failed closed — the source shard
  dropped its socket before the SPSC hand-off was confirmed, losing the
  client exactly under overload. Both runtimes now keep the original
  stream until the push succeeds (bounded retry 8×100µs) and on give-up
  resume serving locally with migration disabled, restoring state from
  the undelivered message. Also fixes the tokio loss path's
  connected_clients counter leak.

Observability: moon_xshard_backpressure_drops_total{target_shard}
  counter + warn at the R-1 give-up point; per-shard
  moon_shard_connected_clients{shard} gauge at registry
  register/deregister (SO_REUSEPORT imbalance / affinity funnels).

P-1 (footprint): FunctionRegistry + LuaEvictionCtx now lazy per
  connection (built on first FUNCTION/FCALL/FCALL_RO) instead of eager —
  zero cost for the >99% of connections that never call Functions.

S-5: --tcp-backlog flag replaces the hardcoded listen(1024)
  (default unchanged; process-wide, set before any listener binds).

Verified: fmt + clippy clean both runtimes; macOS monoio, tokio, and
Linux (OrbStack) checks green; full lib suites green on both runtimes.

Refs: PR #230, tmp/CONN-DEEP-REVIEW.md
author: Tin Dang
@pilotspacex-byte

Copy link
Copy Markdown
Contributor Author

Two follow-up commits from a self-review + architecture pass:

b1a9f10 — review fixes on the original 4 hardenings:

  • CLIENT KILL self-kill stays cooperative (fd shutdown skipped for the executing connection, so the +count reply is delivered — Redis parity)
  • R-1 give-up now fails loud at the two side-effect senders: dropped MqTxnMaterialize ⇒ explicit MOONERR TXN.COMMIT partial error (was silent +OK with lost MQ intents); dropped GraphRollbackerror!
  • PushOutcome is #[must_use]; ≤64-iter spin before the first 100µs backoff (keeps transient-full latency in the old 10µs class); accept backoff capped 1s→100ms (sleep runs inside select! arms — bounds shutdown latency)

8f3f44d — durability & lifecycle wave (from the arch review):

  • D-1: cross-store TXN crash replay restored KV ops into db 0 regardless of SELECT — new XactCommitV2 WAL record carries the db index; replay targets it (red/green tests)
  • R-6: connection migration fails open on both runtimes — source socket kept until SPSC hand-off confirms; on give-up the client is re-served locally instead of dropped (also fixes a connected_clients leak on the tokio loss path)
  • New metrics: moon_xshard_backpressure_drops_total{target_shard}, moon_shard_connected_clients{shard}
  • Lazy per-connection FunctionRegistry (P-1 footprint); --tcp-backlog flag (S-5)

Full matrix locally green: fmt, clippy ×2, monoio 3773 / tokio 3118 lib tests, Linux (OrbStack) check.

TinDang97 added a commit that referenced this pull request Jul 7, 2026
FLUSHDB and FLUSHALL are keyless commands, so key-based routing executed
them only on the shard that owned the issuing connection. On a
`--shards 4` server a client FLUSHALL therefore left ~3/4 of the
keyspace intact (red/green measured 49/64 keys surviving; DBSIZE
scatter-gathers, so the survivors were directly visible — and served
stale reads as ghost keys).

Fix: after the local shard executes the flush, the originating handler
calls the new `coordinate_flush_broadcast` (src/shard/coordinator.rs),
which sends the original FLUSH frame to every peer shard as a
single-command `ShardMessage::MultiExecute` leg. Reusing MultiExecute
means each remote shard gets, for free, the existing remote-arm
behavior: dispatch execution, per-shard AOF/WAL persistence via
`wal_append_and_fanout` (fail-loud `AOF_APPEND_LOST_ERR`), and
`auto_flush_indexes`. The broadcast collects every leg's ack; any
failed / unreachable leg turns the client reply into a loud
`MOONERR FLUSH partial: shard N leg failed or unreachable — local
shard flushed; retry the FLUSH command` plus a `tracing::error!`, never
a silent partial flush.

Hooked in both sharded handler stacks:
- handler_sharded (tokio): after the R3 auto_flush_indexes block.
- handler_monoio: after the with_shard closure exits (the broadcast
  awaits cross-shard acks, so it cannot run inside the closure),
  guarded on a successful local response.

Red/green TDD (tests/flush_cross_shard_scatter.rs, spawns a real
`--shards 4` server, honors MOON_BIN):
- RED  (pre-fix HEAD binary): 0/3 — FLUSHALL 49/64 survived, FLUSHDB
  20/32 survived, db-1 FLUSHDB left db-1 keys on remote shards.
- GREEN (fixed binary): 3/3 — DBSIZE 0 on all shards, ghost-key GETs
  empty, db-0 keys survive a db-1 FLUSHDB.
(The first red run false-passed: the harness passed a nonexistent
`--persistence-dir` flag so the server never started and the tests
skipped; fixed to `--dir` before trusting either color.)

Documented limits (CHANGELOG): the flush is not atomic across shards
(same relaxed semantics as SWAPDB); FLUSH inside MULTI/EXEC still
executes local-only (follow-up); FLUSHALL still clears only the
selected db per the documented v0.1.5 single-active-DB behavior —
all-dbs semantics needs dispatch+replay parity and is a separate
follow-up.

Refs: tmp/CONN-DEEP-REVIEW.md D-2 (Tier 1), PR #230
author: Tin Dang
TinDang97 added 4 commits July 7, 2026 10:46
Deep review of Moon's connection/event-loop plane surfaced four robustness
gaps that bite at high parallel connection counts (1K-10K clients). All four
are fixed here, each with red/green tests; both runtimes build, full lib
suites pass (tokio 3115, monoio 3770), clippy/fmt clean.

R-2 — inline-command length cap (src/protocol/inline.rs, frame.rs)
  The RESP-less inline path had no maximum line length, so a client that
  never sends CRLF (raw non-RESP bytes) grew the connection read buffer
  without bound — a per-connection memory-exhaustion vector. Added
  ParseConfig::max_inline_size (default 64 KB, mirrors Redis
  PROTO_INLINE_MAX_SIZE); parse_inline now rejects an over-cap line (complete
  or incomplete) with "Protocol error: too big inline request".

R-1 — bounded cross-shard spsc_send (src/shard/coordinator.rs)
  The single dispatch primitive behind every scatter-gather command
  (MGET/MSET/DEL/EXISTS/SCAN/KEYS/DBSIZE, vector scatter, FT.INFO, graph
  traverse) retried a full SPSC ring in an UNBOUNDED loop { try_push;
  yield/sleep } — on tokio yield_now() reschedules with no backoff,
  busy-spinning a full core, and a wedged target could block graceful
  shutdown forever. Replaced with a bounded retry (CROSS_SHARD_PUSH_MAX_RETRIES
  x CROSS_SHARD_PUSH_BACKOFF, same budget as push_with_backpressure) returning
  PushOutcome. On give-up the message is dropped; reply-carrying callers
  observe the closed reply channel and synthesize a per-shard error via the
  path they already handle — no call-site signature change.

R-4 — accept-loop backoff on fd exhaustion (src/server/accept_backoff.rs,
  listener.rs, shard/event_loop.rs)
  All six socket-accept loops (tokio and monoio; sharded, non-sharded, TLS)
  retried accept() errors with zero backoff, so EMFILE/ENFILE under a
  connection storm pinned a shard core at 100% and flooded the log. Added
  AcceptBackoff: capped exponential backoff (1ms -> 1s) on resource-exhaustion
  errors only, plus rate-limited logging; benign per-connection errors
  (ECONNABORTED) log without sleeping. The io_uring multishot-accept resubmit
  (opt-in MOON_URING=1 bridge) is documented as a scoped follow-up
  (synchronous CQE handler, no async context to sleep in).

R-3 — CLIENT KILL force-closes idle connections (src/client_registry.rs,
  handlers, conn_accept.rs)
  CLIENT KILL only set a cooperative kill_flag checked once per batch, so a
  connection parked in read().await (idle, default timeout 0) was never torn
  down until it next sent bytes. The registry now stores each connection's
  socket fd and kill_clients also shutdown(2)s it, so the parked read returns
  Ok(0)/Err immediately (existing disconnect path). The raw-fd close is
  race-free: kill_clients holds the registry read lock, and a connection's
  RegistryGuard deregisters (needs the write lock) strictly before its stream
  drops — so a visible entry always has a live fd. No-op on non-unix.

Review report: tmp/CONN-DEEP-REVIEW.md (full 5-dimension findings, incl.
Tier-0 multi-tenant isolation and Tier-2 head-of-line-blocking tracks not in
this PR).

author: Tin Dang
Five fixes from a deep-dive review of the connection-plane hardening PR:

1. CLIENT KILL self-kill stays cooperative (R-3 regression): kill_clients
   now takes the executing connection's id and skips the fd shutdown(2)
   for a matching self entry — flag only — so the +count reply is still
   flushed before the handler closes (Redis replies first on self-kill).
   Red/green: test_self_kill_does_not_force_close_socket_fd.

2. R-1 give-up fails loud at side-effect-bearing senders: a dropped
   MqTxnMaterialize leg turns TXN.COMMIT's reply into an explicit
   "MOONERR TXN.COMMIT partial" error (the commit is already WAL-durable;
   only foreign MQ materialization was lost — previously a silent +OK
   with lost messages) and logs at error!; a dropped GraphRollback logs
   at error! (leaked remote graph intents), both on both runtimes.

3. PushOutcome is #[must_use]: no call site can silently drop a
   cross-shard message by accident. All reply-carrying sites (which fail
   loud via the closed reply channel) now discard explicitly (let _ =).

4. spsc_send latency: a bounded ≤64-iteration spin before the first
   timed backoff preserves the old ~10µs-class recovery when the target
   ring is only transiently full — the 100µs timer sleep may round up to
   ~1ms on coarse runtime timers. Wedged-target budget unchanged.

5. Accept backoff cap 1s → 100ms: the sleep runs inside select! arms
   where the shutdown branch cannot preempt it, so the cap bounds
   shutdown latency during an fd-exhaustion storm (≤10 wakeups/s).

Both runtimes: fmt clean, clippy clean, full lib suites green.

Refs: PR #230
author: Tin Dang
Implements the recommended fixes from the connection-plane architecture
review (multi-connection × multi-db), prioritized for long-term
durability:

D-1 (durability, HIGH): cross-store TXN crash replay hardcoded db 0
  (wal_v3/replay.rs "TODO: support multi-db"). CrossStoreTxn now records
  the BEGIN-time db index; commits write a new XactCommitV2 WAL record
  (0x53) carrying it in the header, and replay restores KV ops into that
  db (out-of-range → db 0 with loud warn; v1 records keep db-0 replay).
  Red/green: test_xact_commit_v2_replays_into_selected_db + fallback test.

R-6 (lifecycle): connection migration failed closed — the source shard
  dropped its socket before the SPSC hand-off was confirmed, losing the
  client exactly under overload. Both runtimes now keep the original
  stream until the push succeeds (bounded retry 8×100µs) and on give-up
  resume serving locally with migration disabled, restoring state from
  the undelivered message. Also fixes the tokio loss path's
  connected_clients counter leak.

Observability: moon_xshard_backpressure_drops_total{target_shard}
  counter + warn at the R-1 give-up point; per-shard
  moon_shard_connected_clients{shard} gauge at registry
  register/deregister (SO_REUSEPORT imbalance / affinity funnels).

P-1 (footprint): FunctionRegistry + LuaEvictionCtx now lazy per
  connection (built on first FUNCTION/FCALL/FCALL_RO) instead of eager —
  zero cost for the >99% of connections that never call Functions.

S-5: --tcp-backlog flag replaces the hardcoded listen(1024)
  (default unchanged; process-wide, set before any listener binds).

Verified: fmt + clippy clean both runtimes; macOS monoio, tokio, and
Linux (OrbStack) checks green; full lib suites green on both runtimes.

Refs: PR #230, tmp/CONN-DEEP-REVIEW.md
author: Tin Dang
FLUSHDB and FLUSHALL are keyless commands, so key-based routing executed
them only on the shard that owned the issuing connection. On a
`--shards 4` server a client FLUSHALL therefore left ~3/4 of the
keyspace intact (red/green measured 49/64 keys surviving; DBSIZE
scatter-gathers, so the survivors were directly visible — and served
stale reads as ghost keys).

Fix: after the local shard executes the flush, the originating handler
calls the new `coordinate_flush_broadcast` (src/shard/coordinator.rs),
which sends the original FLUSH frame to every peer shard as a
single-command `ShardMessage::MultiExecute` leg. Reusing MultiExecute
means each remote shard gets, for free, the existing remote-arm
behavior: dispatch execution, per-shard AOF/WAL persistence via
`wal_append_and_fanout` (fail-loud `AOF_APPEND_LOST_ERR`), and
`auto_flush_indexes`. The broadcast collects every leg's ack; any
failed / unreachable leg turns the client reply into a loud
`MOONERR FLUSH partial: shard N leg failed or unreachable — local
shard flushed; retry the FLUSH command` plus a `tracing::error!`, never
a silent partial flush.

Hooked in both sharded handler stacks:
- handler_sharded (tokio): after the R3 auto_flush_indexes block.
- handler_monoio: after the with_shard closure exits (the broadcast
  awaits cross-shard acks, so it cannot run inside the closure),
  guarded on a successful local response.

Red/green TDD (tests/flush_cross_shard_scatter.rs, spawns a real
`--shards 4` server, honors MOON_BIN):
- RED  (pre-fix HEAD binary): 0/3 — FLUSHALL 49/64 survived, FLUSHDB
  20/32 survived, db-1 FLUSHDB left db-1 keys on remote shards.
- GREEN (fixed binary): 3/3 — DBSIZE 0 on all shards, ghost-key GETs
  empty, db-0 keys survive a db-1 FLUSHDB.
(The first red run false-passed: the harness passed a nonexistent
`--persistence-dir` flag so the server never started and the tests
skipped; fixed to `--dir` before trusting either color.)

Documented limits (CHANGELOG): the flush is not atomic across shards
(same relaxed semantics as SWAPDB); FLUSH inside MULTI/EXEC still
executes local-only (follow-up); FLUSHALL still clears only the
selected db per the documented v0.1.5 single-active-DB behavior —
all-dbs semantics needs dispatch+replay parity and is a separate
follow-up.

Refs: tmp/CONN-DEEP-REVIEW.md D-2 (Tier 1), PR #230
author: Tin Dang
@TinDang97 TinDang97 force-pushed the fix/conn-plane-robustness-track-b branch from 7497ff4 to f1a01a5 Compare July 7, 2026 03:56
Main (RSS/CPU wave 5) tightened scripts/audit-unsafe.sh: the literal
`SAFETY:` token must sit within the 3 lines directly above the
`unsafe {` line. Three Track B blocks had multi-line SAFETY comments
whose marker line drifted out of range after the rebase; restructured
the comments (context prose first, SAFETY invariant last) with no
semantic change. audit-unsafe + audit-unwrap + fmt + check green on
both runtimes.

author: Tin Dang

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/server/conn/handler_sharded/write.rs (1)

140-162: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't silently drop the workspace-cleanup hop.

If this send backpressures, the workspace record is already removed but the key sweep never runs, and this branch still returns OK. Please branch on PushOutcome here and surface/retry the failure before acknowledging WS.DROP.

Suggested fix
-                                    let _ = crate::shard::coordinator::spsc_send(
-                                        &ctx.dispatch_tx,
-                                        ctx.shard_id,
-                                        cleanup_owner,
-                                        msg,
-                                        &ctx.spsc_notifiers,
-                                    )
-                                    .await;
-                                    match reply_rx.recv().await {
-                                        Ok(count) => {
-                                            tracing::debug!(
-                                                "WS.DROP cleanup: deleted {} keys on shard {}",
-                                                count,
-                                                cleanup_owner
-                                            );
-                                        }
-                                        Err(_) => {
-                                            tracing::warn!(
-                                                "WS.DROP cleanup: reply channel closed for shard {}",
-                                                cleanup_owner
-                                            );
-                                        }
-                                    }
+                                    if !matches!(
+                                        crate::shard::coordinator::spsc_send(
+                                            &ctx.dispatch_tx,
+                                            ctx.shard_id,
+                                            cleanup_owner,
+                                            msg,
+                                            &ctx.spsc_notifiers,
+                                        )
+                                        .await,
+                                        crate::shard::dispatch::PushOutcome::Pushed
+                                    ) {
+                                        responses.push(Frame::Error(Bytes::from_static(
+                                            b"ERR workspace cleanup backpressure",
+                                        )));
+                                        return true;
+                                    }
+                                    match reply_rx.recv().await {
+                                        Ok(count) => {
+                                            tracing::debug!(
+                                                "WS.DROP cleanup: deleted {} keys on shard {}",
+                                                count,
+                                                cleanup_owner
+                                            );
+                                        }
+                                        Err(_) => {
+                                            tracing::warn!(
+                                                "WS.DROP cleanup: reply channel closed for shard {}",
+                                                cleanup_owner
+                                            );
+                                        }
+                                    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/conn/handler_sharded/write.rs` around lines 140 - 162, In the
workspace-drop cleanup path inside the write handler, do not ignore the result
of shard::coordinator::spsc_send when sending the cleanup hop. Update the
WS.DROP branch in the sharded write flow to inspect PushOutcome before awaiting
reply_rx.recv(), and if the send is backpressured or otherwise fails, surface or
retry the failure instead of continuing to return OK. Use the existing
cleanup_owner, reply_rx, and spsc_send call site in the write handler to locate
the branch and make sure acknowledgement only happens after the cleanup hop is
successfully queued.
src/server/conn/handler_monoio/write.rs (1)

145-153: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Don't silently drop the workspace-cleanup hop.

If this send backpressures, the workspace record is already removed but the key sweep never runs, and this branch still returns OK. Please branch on PushOutcome here and surface/retry the failure before acknowledging WS.DROP.

Suggested fix
-                                    let _ = crate::shard::coordinator::spsc_send(
-                                        &ctx.dispatch_tx,
-                                        ctx.shard_id,
-                                        owner,
-                                        msg,
-                                        &ctx.spsc_notifiers,
-                                    )
-                                    .await;
-                                    let _ = reply_rx.recv().await;
+                                    if !matches!(
+                                        crate::shard::coordinator::spsc_send(
+                                            &ctx.dispatch_tx,
+                                            ctx.shard_id,
+                                            owner,
+                                            msg,
+                                            &ctx.spsc_notifiers,
+                                        )
+                                        .await,
+                                        crate::shard::dispatch::PushOutcome::Pushed
+                                    ) {
+                                        responses.push(Frame::Error(Bytes::from_static(
+                                            b"ERR workspace cleanup backpressure",
+                                        )));
+                                        return true;
+                                    }
+                                    let _ = reply_rx.recv().await;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/conn/handler_monoio/write.rs` around lines 145 - 153, The
workspace-cleanup path in the monoio write handler is swallowing the result of
the coordinator hop, so a backpressured send can still end in an OK reply
without running the key sweep. Update the branch in the write handler around
spsc_send and reply_rx.recv to inspect the returned PushOutcome, and only
acknowledge WS.DROP after the cleanup hop succeeds; on failure, surface or retry
the error instead of discarding it.
src/shard/event_loop.rs (2)

322-347: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Hoist per-error format!() out of the accept-error hot path.

ctx is recomputed with format!() on every accept error inside the loop, even though shard_id_copy never changes. As per coding guidelines, format!() must not be used on hot paths in shard event loops (src/shard/event_loop.rs is explicitly listed). Compute it once before the loop.

As per coding guidelines, "Do not use Box::new(), Vec::new(), String::new(), Arc::new(), clone(), format!(), or to_string() on hot paths in command dispatch, protocol parsing, shard event loops, or I/O drivers."

♻️ Proposed fix
                 monoio::spawn(async move {
                     let mut accept_backoff = crate::server::accept_backoff::AcceptBackoff::new();
+                    let ctx = format!("Shard {shard_id_copy}: per-shard accept error");
                     loop {
                         match listener.accept().await {
                             Ok((stream, _addr)) => {
                                 accept_backoff.reset();
                                 ...
                             }
                             Err(e) => {
                                 // R-4: capped backoff + rate-limited log so an
                                 // fd-exhaustion storm can't hot-spin this shard.
-                                let ctx = format!("Shard {shard_id_copy}: per-shard accept error");
                                 accept_backoff.record_error(&ctx, &e).await;
                             }
                         }
                     }
                 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shard/event_loop.rs` around lines 322 - 347, The accept-error path in the
shard event loop is recomputing a formatted context string on every loop
iteration, which violates the hot-path guidelines. In the monoio::spawn accept
loop, hoist the per-shard context creation out of the loop in the surrounding
closure before calling listener.accept(), and then pass that precomputed value
into AcceptBackoff::record_error. Use the existing shard_id_copy and the
accept_backoff/record_error flow to locate and update the code without changing
the loop’s behavior.

Source: Coding guidelines


1065-1136: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Same per-error format!() allocation on the tokio per-shard accept branch.

ctx is rebuilt via format!() inside the Err arm of the tokio::select! accept branch on every accept failure. Hoist it out, computed once alongside per_shard_accept_backoff, since shard_id is constant for the life of the loop.

As per coding guidelines, "Do not use Box::new(), Vec::new(), String::new(), Arc::new(), clone(), format!(), or to_string() on hot paths in command dispatch, protocol parsing, shard event loops, or I/O drivers."

♻️ Proposed fix
         #[cfg(feature = "runtime-tokio")]
         let mut per_shard_accept_backoff = crate::server::accept_backoff::AcceptBackoff::new();
+        #[cfg(feature = "runtime-tokio")]
+        let per_shard_accept_ctx = format!("Shard {shard_id}: per-shard accept error");
...
                         Err(e) => {
-                            let ctx = format!("Shard {shard_id}: per-shard accept error");
-                            per_shard_accept_backoff.record_error(&ctx, &e).await;
+                            per_shard_accept_backoff.record_error(&per_shard_accept_ctx, &e).await;
                         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shard/event_loop.rs` around lines 1065 - 1136, The tokio per-shard accept
error path in the shard event loop rebuilds the same context string on every
failure, which violates the hot-path allocation guideline. Hoist the accept
error context out of the `tokio::select!` branch in `event_loop.rs`, near
`per_shard_accept_backoff`, so it is computed once per shard/loop instead of
inside the `Err` arm. Keep the existing `record_error` call in the
`per_shard_accept_backoff` handling, but pass the precomputed context value
there. Use the `per_shard_accept_backoff` setup and the per-shard accept branch
as the main anchors when updating `event_loop`.

Source: Coding guidelines

src/server/conn/handler_sharded/txn.rs (1)

151-151: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

MQ commit should use txn.db_index

conn.selected_db can change after TXN.BEGIN, but txn.db_index is the transaction’s BEGIN-time db used for KV WAL/replay. Using the live connection db here can materialize self-intents and MqTxnMaterialize into a different database than the transaction’s KV state. Update both this local apply and the foreign-shard payload to use txn.db_index.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/conn/handler_sharded/txn.rs` at line 151, The MQ commit path is
using the live connection database instead of the transaction’s BEGIN-time
database, which can desync materialization from KV WAL/replay. Update the local
apply in txn::handle_commit and the foreign-shard payload in the sharded
transaction flow to use txn.db_index rather than conn.selected_db, so
self-intents and MqTxnMaterialize are applied against the same database as the
transaction state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shard/conn_accept.rs`:
- Around line 782-845: The fail-open migration fallback is dropping
already-buffered bytes because both resume paths call
handle_connection_sharded_monoio with BytesMut::new(). Update the local-recovery
logic in conn_accept.rs to pass the saved remainder from
state.read_buf_remainder into the initial_read_buf argument when retrying
locally, including both the branch after the initial monoio handoff and the
branch after a full SPSC push failure. Keep the fix centered around
handle_connection_sharded_monoio and the MigrateConnectionPayload/state recovery
path.

---

Outside diff comments:
In `@src/server/conn/handler_monoio/write.rs`:
- Around line 145-153: The workspace-cleanup path in the monoio write handler is
swallowing the result of the coordinator hop, so a backpressured send can still
end in an OK reply without running the key sweep. Update the branch in the write
handler around spsc_send and reply_rx.recv to inspect the returned PushOutcome,
and only acknowledge WS.DROP after the cleanup hop succeeds; on failure, surface
or retry the error instead of discarding it.

In `@src/server/conn/handler_sharded/txn.rs`:
- Line 151: The MQ commit path is using the live connection database instead of
the transaction’s BEGIN-time database, which can desync materialization from KV
WAL/replay. Update the local apply in txn::handle_commit and the foreign-shard
payload in the sharded transaction flow to use txn.db_index rather than
conn.selected_db, so self-intents and MqTxnMaterialize are applied against the
same database as the transaction state.

In `@src/server/conn/handler_sharded/write.rs`:
- Around line 140-162: In the workspace-drop cleanup path inside the write
handler, do not ignore the result of shard::coordinator::spsc_send when sending
the cleanup hop. Update the WS.DROP branch in the sharded write flow to inspect
PushOutcome before awaiting reply_rx.recv(), and if the send is backpressured or
otherwise fails, surface or retry the failure instead of continuing to return
OK. Use the existing cleanup_owner, reply_rx, and spsc_send call site in the
write handler to locate the branch and make sure acknowledgement only happens
after the cleanup hop is successfully queued.

In `@src/shard/event_loop.rs`:
- Around line 322-347: The accept-error path in the shard event loop is
recomputing a formatted context string on every loop iteration, which violates
the hot-path guidelines. In the monoio::spawn accept loop, hoist the per-shard
context creation out of the loop in the surrounding closure before calling
listener.accept(), and then pass that precomputed value into
AcceptBackoff::record_error. Use the existing shard_id_copy and the
accept_backoff/record_error flow to locate and update the code without changing
the loop’s behavior.
- Around line 1065-1136: The tokio per-shard accept error path in the shard
event loop rebuilds the same context string on every failure, which violates the
hot-path allocation guideline. Hoist the accept error context out of the
`tokio::select!` branch in `event_loop.rs`, near `per_shard_accept_backoff`, so
it is computed once per shard/loop instead of inside the `Err` arm. Keep the
existing `record_error` call in the `per_shard_accept_backoff` handling, but
pass the precomputed context value there. Use the `per_shard_accept_backoff`
setup and the per-shard accept branch as the main anchors when updating
`event_loop`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f3d749b-4bec-4a6e-977d-1c30e35da9a6

📥 Commits

Reviewing files that changed from the base of the PR and between 73adcc6 and f1a01a5.

📒 Files selected for processing (37)
  • .gitignore
  • CHANGELOG.md
  • fuzz/fuzz_targets/inline_parse.rs
  • fuzz/fuzz_targets/resp_parse.rs
  • fuzz/fuzz_targets/resp_parse_differential.rs
  • src/admin/metrics_setup.rs
  • src/client_registry.rs
  • src/config.rs
  • src/main.rs
  • src/persistence/wal_v3/record.rs
  • src/persistence/wal_v3/replay.rs
  • src/protocol/frame.rs
  • src/protocol/inline.rs
  • src/protocol/parse.rs
  • src/server/accept_backoff.rs
  • src/server/conn/core.rs
  • src/server/conn/handler_monoio/dispatch.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_monoio/txn.rs
  • src/server/conn/handler_monoio/write.rs
  • src/server/conn/handler_sharded/dispatch.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/server/conn/handler_sharded/txn.rs
  • src/server/conn/handler_sharded/write.rs
  • src/server/conn/shared.rs
  • src/server/listener.rs
  • src/server/mod.rs
  • src/shard/conn_accept.rs
  • src/shard/coordinator.rs
  • src/shard/dispatch.rs
  • src/shard/event_loop.rs
  • src/shard/scatter_aggregate.rs
  • src/shard/scatter_hybrid.rs
  • src/shard/uring_handler.rs
  • src/transaction/abort.rs
  • src/transaction/mod.rs
  • tests/flush_cross_shard_scatter.rs

Comment thread src/shard/conn_accept.rs
Comment on lines +782 to +845
let _ = handle_connection_sharded_monoio(
stream, peer2, &conn_ctx, sd2, cid,
false, // can_migrate
BytesMut::new(), pw2,
Some(&state), kill_fd,
)
.await;
} else {
let msg = ShardMessage::MigrateConnection(Box::new(
let mut pending_msg = Some(ShardMessage::MigrateConnection(Box::new(
crate::shard::dispatch::MigrateConnectionPayload {
fd: dup_fd,
state,
},
));
)));
let target_idx = ChannelMesh::target_index(shard_id, target_shard);
let push_result = {
let mut producers = dtx2.borrow_mut();
producers[target_idx].try_push(msg)
};
match push_result {
Ok(()) => {
_migrated = true;
notifiers2[target_shard].notify_one();
tracing::info!(
"Shard {}: migrated connection {} to shard {} (monoio)",
shard_id, cid, target_shard
);
// Bounded retry: migration is elective — a briefly-full
// ring is worth a few 100µs waits, but a target that
// stays full gets the fail-open path, never a lost conn.
for attempt in 0..8u32 {
if attempt > 0 {
monoio::time::sleep(std::time::Duration::from_micros(100)).await;
}
Err(returned_msg) => {
if let ShardMessage::MigrateConnection(payload) = returned_msg {
// SAFETY: fd is a valid dup'd socket from libc::dup above.
// SPSC push failed, so ownership returns to us for cleanup.
drop(unsafe {
std::os::unix::io::OwnedFd::from_raw_fd(payload.fd)
});
#[allow(clippy::unwrap_used)]
// pending_msg is always re-filled on Err below
let msg = pending_msg.take().unwrap();
let push_result = {
let mut producers = dtx2.borrow_mut();
producers[target_idx].try_push(msg)
};
match push_result {
Ok(()) => {
_migrated = true;
notifiers2[target_shard].notify_one();
tracing::info!(
"Shard {}: migrated connection {} to shard {} (monoio)",
shard_id, cid, target_shard
);
break;
}
tracing::warn!(
"Shard {}: migration SPSC full, connection {} lost (monoio)",
shard_id, cid
);
Err(returned_msg) => pending_msg = Some(returned_msg),
}
}
if _migrated {
drop(stream); // hand-off confirmed: target owns dup_fd
} else if let Some(ShardMessage::MigrateConnection(payload)) = pending_msg {
// SAFETY: dup_fd is a valid dup'd socket from libc::dup
// above; the push never succeeded so we still own it.
drop(unsafe {
std::os::unix::io::OwnedFd::from_raw_fd(payload.fd)
});
tracing::warn!(
"Shard {}: migration SPSC full, keeping connection {} local (monoio)",
shard_id, cid
);
// Fail-open (R-6): resume serving on this shard with
// the state recovered from the undelivered message.
let _ = handle_connection_sharded_monoio(
stream, peer2, &conn_ctx, sd2, cid,
false, // can_migrate: pin locally, no retry loop
BytesMut::new(), pw2,
Some(&payload.state), kill_fd,
)
.await;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant handler and state definitions.
rg -n --no-heading \
  "read_buf_remainder|take_migration_read_buf|initial_read_buf|MigrateConnection|handle_connection_sharded_monoio|ConnectionState::new" \
  src/shard/conn_accept.rs src -g '*.rs'

# Show the relevant slice around the reviewed lines.
sed -n '740,880p' src/shard/conn_accept.rs

# Inspect the connection handler/state definitions that consume initial_read_buf.
fd -a 'handler_monoio.rs' src
fd -a 'connection_state.rs' src

Repository: pilotspace/moon

Length of output: 16492


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the handler setup that consumes initial_read_buf in monoio.
sed -n '106,170p' src/server/conn/handler_monoio/mod.rs

# Show the migration result construction where read_buf_remainder is captured.
sed -n '1938,1968p' src/server/conn/handler_monoio/mod.rs

# Show the spawn path that reconstructs migrated connections.
sed -n '400,445p' src/shard/conn_accept.rs

Repository: pilotspace/moon

Length of output: 6009


Preserve the buffered remainder on fail-open migration
Both fail-open paths restart the handler with BytesMut::new(), so any bytes already read into state.read_buf_remainder are dropped and the connection can desync. Thread that remainder into initial_read_buf when resuming locally in both branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shard/conn_accept.rs` around lines 782 - 845, The fail-open migration
fallback is dropping already-buffered bytes because both resume paths call
handle_connection_sharded_monoio with BytesMut::new(). Update the local-recovery
logic in conn_accept.rs to pass the saved remainder from
state.read_buf_remainder into the initial_read_buf argument when retrying
locally, including both the branch after the initial monoio handoff and the
branch after a full SPSC push failure. Keep the fix centered around
handle_connection_sharded_monoio and the MigrateConnectionPayload/state recovery
path.

S-5 added `tcp_backlog` to ServerConfig; three integration-test files
build the struct with exhaustive literals (no ..Default spread) and
failed to compile in CI (E0063). Added `tcp_backlog: 1024` (the
default) to the 4 literals. Verified with `cargo test --no-run` on
both runtimes (0 errors) — the local gate previously compiled only
--lib, which is how this slipped past.

author: Tin Dang
@pilotspacex-byte pilotspacex-byte merged commit 9917462 into main Jul 7, 2026
11 checks passed
TinDang97 added a commit that referenced this pull request Jul 7, 2026
is_resource_exhaustion (accept-loop backoff, PR #230) matched
libc::EMFILE/ENFILE/ENOBUFS/ENOMEM without a cfg guard; libc is not
linked on Windows targets, so every main-push Windows check has failed
E0433 since the merge (PR CI never caught it -- the Windows job is
skipped on PRs). Split the function: unix keeps the errno match,
Windows matches WSAEMFILE (10024) / WSAENOBUFS (10055) and
ErrorKind::OutOfMemory.

author: Tin Dang
TinDang97 added a commit that referenced this pull request Jul 7, 2026
is_resource_exhaustion (accept-loop backoff, PR #230) matched
libc::EMFILE/ENFILE/ENOBUFS/ENOMEM without a cfg guard; libc is not
linked on Windows targets, so every main-push Windows check has failed
E0433 since the merge (PR CI never caught it -- the Windows job is
skipped on PRs). Split the function: unix keeps the errno match,
Windows matches WSAEMFILE (10024) / WSAENOBUFS (10055) and
ErrorKind::OutOfMemory.

author: Tin Dang
pilotspacex-byte added a commit that referenced this pull request Jul 7, 2026
#242)

* perf(persistence): coalesce each AOF group-commit batch into one write

The monoio AOF writer was write-syscall-bound under appendfsync always
with pipelining. strace -c on aof-writer-0 during an 8s always-P16 SET
window (GCE c3-standard-8, 2 shards):

  write:     127,812 calls, 1.25s   (~1 syscall per record, 9us each)
  fdatasync:   2,144 calls, 0.20s

The writer spent 6x more time issuing write(2) than fsyncing: both
monoio paths wrote each record individually on the raw unbuffered File
-- the TopLevel path one write per message via commit_group_commit_batch,
the PerShard framed path a header+body write PAIR per record. Redis
batches ~120 records into one write via aof_buf (its main thread did
32,791 writes total for MORE throughput), which is where the remaining
0.85x always-P16 gap lived: each ~60-record batch paid ~540us of
serialized write syscalls before its fsync could start.

Fix, both monoio paths:
- commit_group_commit_batch (TopLevel): multi-message batches coalesce
  into one contiguous buffer and ONE sink.write_all; single-message
  batches keep the zero-copy direct write.
- PerShard framed loop: records are framed into a reusable batch_buf
  ([u64 lsn][u32 len] header + body, channel order) and written with
  ONE write_all; buffer high-water capped at 1MB.
- tokio writer loops already amortize via BufWriter -- untouched.

Failure semantics unchanged: a failed batch write acks every AppendSync
waiter WriteFailed (none Synced) and engages the torn-stream latch; the
single per-batch fsync still runs strictly after all bytes are written,
so fsync-before-ack (H1) is preserved. The everysec path shares the
same write code and gets the same syscall reduction.

wal_group_commit sink tests updated to pin the coalesced contract: one
write per batch, bytes concatenated in channel order, fsync after it,
whole-batch write failure (no torn half-batch acks).

Validation:
- wal_group_commit 9/9 green (contract tests updated).
- crash_matrix_per_shard_aof --ignored 3/3 green (SIGKILL durability
  contract intact, including appendfsync always).
- fmt, clippy x2, cargo test --lib x2 (monoio 3868 / tokio 3143),
  unwrap ratchet green.
- GCE always-P16 A/B vs Redis to be posted on the PR before merge.

author: Tin Dang

* fix(server): cfg-gate accept-backoff libc errnos for Windows

is_resource_exhaustion (accept-loop backoff, PR #230) matched
libc::EMFILE/ENFILE/ENOBUFS/ENOMEM without a cfg guard; libc is not
linked on Windows targets, so every main-push Windows check has failed
E0433 since the merge (PR CI never caught it -- the Windows job is
skipped on PRs). Split the function: unix keeps the errno match,
Windows matches WSAEMFILE (10024) / WSAENOBUFS (10055) and
ErrorKind::OutOfMemory.

author: Tin Dang

---------

Co-authored-by: Tin Dang <tindang.ht97@gmail.com>
pilotspacex-byte added a commit that referenced this pull request Jul 7, 2026
Follow-up to the accept_backoff Windows fix in PR #242: the module's
resource_exhaustion_classification unit test also referenced
libc::EMFILE/ENFILE/ECONNABORTED, so the main-push Windows check kept
failing E0433 on the test build. cfg-split the test the same way as
the function under test: unix keeps the libc errnos, Windows uses
WSAEMFILE (10024) / WSAENOBUFS (10055) / WSAECONNABORTED (10053).

The Windows job runs only on main pushes (skipped on PRs), which is
why neither the original break (PR #230) nor the partial fix could be
validated pre-merge.

author: Tin Dang

Co-authored-by: Tin Dang <tindang.ht97@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants