Skip to content

fix(server): accept-loop HOL block, migration fd ownership, park auth gates (#438 F3–F6 + conn-secondary) - #446

Merged
TinDang97 merged 2 commits into
mainfrom
fix/438-f3-f6-conn-secondary
Aug 7, 2026
Merged

fix(server): accept-loop HOL block, migration fd ownership, park auth gates (#438 F3–F6 + conn-secondary)#446
TinDang97 merged 2 commits into
mainfrom
fix/438-f3-f6-conn-secondary

Conversation

@TinDang97

@TinDang97 TinDang97 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Continues the #438 hardening tail (sequential campaign, after F1/F2 in #445). Four findings from the c10k review's Cluster F plus the conn-secondary triple.

F3 — central accept loop head-of-line block (routing F10)

Every central-listener delivery blocked on the routed shard's bounded (4096) conn channel; one wedged shard froze accepts for all shards. The monoio legs were worse than reviewed: flume::Sender::send there is synchronous, so a full channel stalled the whole listener thread, TLS accepts included. All five delivery sites now try_send and rotate to the next shard with room (try_route_conn, unit-tested); only when every channel refuses does the loop fall back to the pre-F3 blocking send (server-wide saturation ⇒ back-pressure is correct) with a warn.

F4 — migration fd lifecycle (routing F12)

MigrateConnectionPayload carried the socket as a raw i32 with no Drop: a migration message still queued at shutdown, or drained but never spawned, leaked the fd and stranded the client. The payload now owns the socket as an OwnedFd end to end (producer → SPSC → pending-migrations → target-shard spawn), so every undelivered path closes the socket and the client sees a FIN. This also discharges the F1-drain interaction flagged by CodeRabbit on #445: queued migrations dropped by a shutting-down shard now close deterministically. Net −3 unsafe blocks (raw from_raw_fd conversions became safe From impls). Resumed-parked conns stay deliberately can_migrate:false — rationale documented at the spawn site (no MigrateConnection arm in the resume wrapper; migration sampling exists for hot conns, this path is only reachable by idling past --conn-park-secs).

F5 — migrated ConnectionContext requirepass (sec L3)

Both migrated-spawn sites built the context with requirepass: None. Session auth was unaffected (travels in MigratedConnectionState), but AUTH on a migrated conn wrongly answered "no password is set" and any future auth re-derivation would have failed open. Now reads the real value from runtime config.

F6 — unauthenticated conns could task-park (sec L2)

On an auth-enabled server, silent pre-AUTH sockets downshifted and task-parked into the ~3.3 KB watcher state — a maxclients-worth of connections holdable indefinitely at near-zero, invisible cost. The stage-2 parkable predicate now requires conn.authenticated. Red/green on macOS monoio: parked_clients=2 pre-fix → =1 post-fix (the authed idle sibling is the in-test positive control that parking machinery stayed live). No-auth servers unaffected.

conn-secondary

  • is_sweep_cancel bare-125: any ECANCELED read as "sweep cancel → re-park"; for a dead fd that's a permanent park→wake→park spin at 100% CPU (the E11 class). Park arms now require sweep/drain provenance via consume-once IdleSlot::was_swept_cancel (flag set by sweep/cancel_all_parked before firing, re-armed false at every park). blocking.rs keeps the plain errno check where a stray 125 only re-arms a level-triggered poll (no spin possible). Unit-tested.
  • registry counter leak: register() over a still-present id double-counted TOTAL_CLIENTS/shard gauges permanently (skews shard_overloaded routing). Now balances against the replaced entry; the kept_registration miss arm (invariant violation, not a code path) warns loudly. Unit-tested.
  • timeout read-once: already fixed by the D1 chore-sweep rework — both shard chores re-read runtime_config.timeout every second; CONFIG SET timeout is live. No change; verified.

Gates

  • fmt + clippy -D warnings, default and runtime-tokio,jemalloc
  • New unit tests green under both feature sets; parked_idle_parity 7/7 + shutdown_drain 4/4 + tls_park_keyupdate on macOS monoio and tokio
  • Full suites: macOS monoio (28 suites; one cross_shard_consistency_red load flake, solo-green, documented family) · Linux VM monoio (lib 4519/4519) · Linux VM tokio CI-parity (exit 0, 190 suites)
  • client_tracking_invalidation flake proven pre-existing: 3×-vs-3× merge-base A/B on the VM (1/3 failing on unmodified main vs 2/3 on branch, same missing-second-key push signature) — queued for the test-hygiene sweep
  • Bench gates waived — no hot-path cost (accept path, cold migration/teardown path, and two Cell ops on the already-cold cancelled-read path; predicate adds one bool test per park decision)

Refs #438 (F3 = routing F10, F4 = routing F12, F5 = sec L3, F6 = sec L2, conn-secondary)

Summary by CodeRabbit

  • Bug Fixes
    • Improved connection routing to avoid head-of-line blocking during shard delivery.
    • Preserved authentication settings when connections migrate between shards.
    • Prevented unauthenticated idle connections from being parked.
    • Improved connection cleanup during migration failures and shutdown.
    • Fixed client counts and metrics when registrations are replaced.
    • Strengthened idle-connection cancellation handling to avoid false errors.
  • Tests
    • Added coverage for connection routing, authentication, parking behavior, migration cleanup, and client registration accounting.

… gates (#438 F3-F6 + conn-secondary)

F3 (routing F10) — the central accept loop delivered every connection
with a blocking send on the routed shard's bounded (4096) conn channel:
one wedged shard froze accepts for ALL shards. The monoio legs were
worse than reviewed — `flume::Sender::send` there is synchronous, so a
full channel stalled the entire listener THREAD (plain + TLS legs
alike). All five delivery sites (tokio plain/TLS, monoio plain/TLS x2
select shapes) now `try_send` and rotate to the next shard with room
(`try_route_conn`); the affinity hint is best-effort and any live shard
serves the client. Only when EVERY channel refuses does the loop fall
back to the pre-F3 blocking send — server-wide saturation, where
back-pressure is the correct behaviour — with a warn. Unit-tested
(rotation, disconnected-skip, all-full payload return, target-first).

F4 (routing F12) — MigrateConnectionPayload carried the migrated
socket as a raw i32 with no Drop: a migration message still queued in
an SPSC ring at shutdown, or drained into pending_migrations but never
spawned, leaked the fd and stranded the client on a connection no task
would ever serve (silent, until the fd table filled). The payload now
owns the socket as an OwnedFd end to end — producer (tokio into_std;
monoio dup wrapped at birth) → ring → pending_migrations →
spawn_migrated_* — so every undelivered path closes the socket and the
client gets a FIN. This also discharges the F1-drain interaction
flagged on PR #445: queued migrations dropped by a shutting-down shard
now close deterministically. Three `unsafe from_raw_fd` blocks became
safe ownership conversions (net -3 unsafe). Unit test pins the drop
semantics; a revert to a raw fd no longer compiles. Resumed-parked
conns stay deliberately can_migrate:false — the resume wrapper has no
MigrateConnection arm, and migration sampling exists for HOT conns
while this path is only reachable by idling past --conn-park-secs;
rationale documented at the spawn site.

F5 (sec L3) — both migrated-connection spawn sites built the target
shard's ConnectionContext with requirepass: None. Session auth was
unaffected (it travels in MigratedConnectionState), but AUTH issued on
a migrated conn wrongly answered "no password is set", and any future
auth-re-derivation from the context would have failed open. The real
requirepass is now read from runtime config at spawn.

F6 (sec L2) — unauthenticated connections could task-park: on an
auth-enabled server an attacker could open maxclients silent sockets,
never AUTH, and hold each at the parked ~3.3 KB watcher footprint
indefinitely and invisibly. The stage-2 parkable predicate now requires
conn.authenticated — pre-AUTH conns keep their full handler task
(costly enough to surface in monitoring, still reaped by `timeout N`);
no-auth servers see no change (authenticated=true from accept).
Red/green on macOS monoio: parked_clients=2 pre-fix, =1 post-fix
(authed idle sibling doubles as the positive control that the park
machinery stayed live).

conn-secondary —
* is_sweep_cancel matched any bare errno 125, so an ECANCELED from a
  non-sweep source read as "sweep cancel -> re-park": for a dead fd
  that is a permanent park->wake->park spin at 100% CPU (the exact E11
  failure class the errno check was built to prevent). The idle-park
  slot now records provenance (`swept`, set by sweep/drain before
  firing, re-armed false at every park) and the park arms require it
  via consume-once `was_swept_cancel`; blocking.rs keeps the plain
  errno check where a stray 125 only re-arms a level-triggered poll
  (no spin possible). Unit-tested (provenance required, consume-once,
  stale-flag cleared on re-park, drain marks too).
* client_registry::register double-counted TOTAL_CLIENTS and the shard
  gauges when inserting over a still-present id (the kept_registration
  miss-arm fail-safe racing an entry back into existence): permanent
  +1 skew feeding shard_overloaded routing. register() now balances
  against the replaced entry, and the miss arm (an invariant
  violation, not a code path) warns loudly. Unit-tested.
* `timeout` read-once: already fixed by the D1 chore-sweep rework —
  both shard chores re-read runtime_config.timeout every second and
  CONFIG SET timeout is live (verified, comment at the monoio chore
  says exactly this). No change.

Gates: fmt + clippy -D warnings (default and tokio,jemalloc), new unit
tests green under both feature sets, parked_idle_parity 7/7 +
shutdown_drain 4/4 + tls_park_keyupdate on macOS monoio AND tokio
(F6 leg degenerates to parked=0 under tokio as documented), full
suites on macOS monoio (28 suites; one cross_shard_consistency_red
load flake, solo-green — same documented family as cdg6e), Linux VM
monoio (lib 4519/4519; client_tracking_invalidation push-delivery
flake fires on UNMODIFIED main too — 3x-vs-3x merge-base A/B, 1/3 main
vs 2/3 branch, same missing-second-key signature; pre-existing, queued
for the test-hygiene sweep) and Linux VM tokio CI-parity (exit 0,
190 suites).
Bench gates waived — no hot-path cost: try_route_conn replaces a
blocking send with try_send attempts on the accept path (not the
command path); the OwnedFd is layout-identical to i32 with a Drop that
only runs on the cold migration/teardown path; the park-arm provenance
check is two Cell ops on the already-cold cancelled-read path; the
parkable predicate gains one bool test evaluated once per park
decision.

Refs #438 (F3 = routing F10, F4 = routing F12, F5 = sec L3,
F6 = sec L2, conn-secondary)
author: Tin Dang
@coderabbitai

coderabbitai Bot commented Aug 7, 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: 39 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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 Plus

Run ID: 978ad062-6a02-4870-a706-8af2ca0306fc

📥 Commits

Reviewing files that changed from the base of the PR and between c7c4f0b and 44f9df5.

📒 Files selected for processing (4)
  • src/client_registry.rs
  • src/server/listener.rs
  • src/shard/dispatch.rs
  • tests/parked_idle_parity.rs
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/438-f3-f6-conn-secondary

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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix accept-loop HOL blocking, migration fd ownership, and auth-gated task parking

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Prevent central accept loop HOL blocking by rotating deliveries across shard channels.
• Make migration payloads own sockets (OwnedFd) to avoid fd leaks and stranded clients.
• Block task-parking for unauthenticated conns; fix park cancel provenance and metrics counting.
Diagram

graph TD
  A["Central Listener"] --> B["try_route_conn"] --> C["Shard Conn Channels"] --> D["Shard Connection Handler"]
  D --> E["Idle Park Registry"] --> D
  D --> F["Client Registry + Metrics"]
  D --> G["Migration SPSC + Pending Queue"] --> D
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Dedicated acceptors per shard (no central routing)
  • ➕ Eliminates central accept loop as a shared bottleneck
  • ➕ Avoids cross-shard channel delivery entirely
  • ➖ Higher operational complexity (multiple listeners, port sharing, SO_REUSEPORT behavior)
  • ➖ Harder to preserve consistent routing/affinity and TLS handling symmetry
2. Increase channel capacity / make channels unbounded
  • ➕ Lower chance of routed shard being full
  • ➕ Minimal code changes
  • ➖ Masks overload instead of handling it; can increase memory pressure
  • ➖ Does not address monoio sync send HOL blocking when a shard wedges
3. Drop-on-full with immediate client close (instead of fallback blocking)
  • ➕ Protects listener latency under saturation
  • ➕ Predictable bounded work per accept
  • ➖ Changes overload semantics; may reduce reliability under load
  • ➖ Harder to tune versus intentional back-pressure when the server is truly saturated

Recommendation: The PR’s approach (best-effort rotation via try_send, with explicit fallback to blocking only when all shard channels refuse) is the best balance: it removes shard-specific HOL failure while preserving correct back-pressure under true server-wide saturation. The OwnedFd migration change is the right durability/safety fix because it makes fd lifecycle correct by construction, and the auth-gated parking closes a real resource-amplification vector without breaking no-auth deployments.

Files changed (11) +520 / -92

Bug fix (9) +411 / -91
client_registry.rsPrevent double-counting metrics on replacing client registration +45/-2

Prevent double-counting metrics on replacing client registration

• Makes 'register()' balance counters when inserting over an existing client id, avoiding permanently inflated TOTAL_CLIENTS/shard gauges. Adds a unit test asserting replacing-register does not increment the global counter.

src/client_registry.rs

idle_park.rsTrack sweep provenance for ECANCELED to avoid park→wake→park CPU spins +69/-0

Track sweep provenance for ECANCELED to avoid park→wake→park CPU spins

• Adds a per-slot 'swept' flag and 'was_swept_cancel()' to distinguish sweep/drain cancels from unrelated errno 125 errors. Updates sweep/drain to set provenance before canceling and adds tests for consume-once and stale-flag clearing behavior.

src/server/conn/handler_monoio/idle_park.rs

mod.rsGate task-parking on authentication and improve registry invariant logging +25/-2

Gate task-parking on authentication and improve registry invariant logging

• Requires 'conn.authenticated' for stage-2 park eligibility to prevent unauthenticated connections from entering low-cost parked state on auth-enabled servers. Switches cancel classification to provenance-checked 'was_swept_cancel()' and adds loud warning when a kept-registration guard exists but the registry entry is missing.

src/server/conn/handler_monoio/mod.rs

mod.rsCarry migrated sockets as OwnedFd and remove unsafe from_raw_fd recovery +15/-10

Carry migrated sockets as OwnedFd and remove unsafe from_raw_fd recovery

• Constructs migration messages with an 'OwnedFd' instead of a raw fd, ensuring shutdown/undelivered paths close sockets deterministically. Replaces unsafe 'from_raw_fd' recovery on failed enqueue with safe 'TcpStream::from(OwnedFd)' and preserves the raw fd only for kill/metrics paths.

src/server/conn/handler_sharded/mod.rs

listener.rsAvoid accept-loop HOL blocking by rotating try_send across shards +124/-16

Avoid accept-loop HOL blocking by rotating try_send across shards

• Introduces 'try_route_conn()' to attempt non-blocking delivery to the routed shard then rotate across others when full/disconnected. Applies the helper across tokio and monoio plain/TLS accept paths, falling back to blocking send only when every channel refuses, and adds targeted unit tests for routing behavior.

src/server/listener.rs

conn_accept.rsMake migrated spawns use OwnedFd and propagate requirepass into context +61/-55

Make migrated spawns use OwnedFd and propagate requirepass into context

• Updates migrated connection spawn APIs to accept 'MigrateFd' (OwnedFd on unix) and removes unsafe raw-fd reconstruction. Fixes migrated 'ConnectionContext' initialization to carry the actual 'requirepass' from runtime config.

src/shard/conn_accept.rs

dispatch.rsType migration sockets as OwnedFd (MigrateFd) and add drop-closes test +63/-1

Type migration sockets as OwnedFd (MigrateFd) and add drop-closes test

• Defines 'MigrateFd' as 'OwnedFd' on unix and uses it in 'MigrateConnectionPayload' to make ownership explicit. Adds a unix-only test that dropping an undelivered payload closes the socket (fcntl EBADF).

src/shard/dispatch.rs

event_loop.rsStore pending migrations as OwnedFd and log raw fd safely +7/-4

Store pending migrations as OwnedFd and log raw fd safely

• Changes 'pending_migrations' to hold 'MigrateFd' so shutdown drops close sockets instead of leaking fds. Adjusts migrated-accept logging to print 'as_raw_fd()' from the owned handle.

src/shard/event_loop.rs

spsc_handler.rsDrain migrate messages by value to move OwnedFd into pending queue +2/-1

Drain migrate messages by value to move OwnedFd into pending queue

• Unboxes 'MigrateConnectionPayload' during SPSC drain so the 'OwnedFd' can be moved into 'pending_migrations'. Updates the pending queue type to 'MigrateFd' accordingly.

src/shard/spsc_handler.rs

Tests (1) +64 / -1
parked_idle_parity.rsAdd integration test ensuring unauthenticated conns never task-park +64/-1

Add integration test ensuring unauthenticated conns never task-park

• Adds a spawn helper supporting extra CLI args and introduces an auth-enabled test verifying only an authenticated idle connection can increase 'parked_clients'. Validates the F6 fix on monoio while remaining vacuously safe under tokio (no task parking).

tests/parked_idle_parity.rs

Documentation (1) +45 / -0
CHANGELOG.mdDocument fixes for accept HOL, migration fd leaks, auth parking, and secondary issues +45/-0

Document fixes for accept HOL, migration fd leaks, auth parking, and secondary issues

• Adds detailed release notes for F3–F6 and conn-secondary findings, explaining behavior changes and security impact. Captures the rationale for back-pressure fallback and for keeping resumed-parked conns non-migratable.

CHANGELOG.md

@qodo-code-review

qodo-code-review Bot commented Aug 7, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Flaky global-state tests ✓ Resolved 🐞 Bug ☼ Reliability
Description
Two new unit tests rely on process-global state under Rust’s parallel test runner: TOTAL_CLIENTS can
change between atomic loads, and raw fd numbers can be reused after close, making the assertions
nondeterministic. This can cause intermittent CI failures unrelated to product correctness.
Code

src/client_registry.rs[R1314-1317]

+        let _a = register(ID, "t:1".into(), "default".into(), 908, -1);
+        let t1 = TOTAL_CLIENTS.load(Ordering::Relaxed);
+        let _b = register(ID, "t:1".into(), "default".into(), 909, -1);
+        let t2 = TOTAL_CLIENTS.load(Ordering::Relaxed);
Relevance

●●● Strong

Team has accepted fixes to avoid flaky assertions on process-global atomics in parallel tests.

PR-#361
PR-#35

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The registry test explicitly samples TOTAL_CLIENTS twice with no isolation, which can be perturbed
by other tests. The fd test retains a raw integer and checks it after dropping the owner; because fd
numbers can be reused quickly, the check can observe a different open fd.

src/client_registry.rs[1303-1327]
src/shard/dispatch.rs[1637-1682]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The new tests:
- `replacing_register_does_not_double_count` reads `TOTAL_CLIENTS` before/after a replace-register and asserts equality, but other tests can concurrently register/deregister clients.
- `dropped_payload_closes_socket` checks closure via `fcntl(raw_fd)` on a raw integer after dropping the owning `OwnedFd`, but the fd number may be reused by other tests/threads.

### Issue Context
Rust unit tests execute in parallel by default; both `TOTAL_CLIENTS` and file descriptor integers are process-global resources.

### Fix Focus Areas
- src/client_registry.rs[1303-1327]
- src/shard/dispatch.rs[1637-1682]

### Expected fix
- For the registry test: avoid asserting equality on a global total that can change concurrently. Prefer asserting per-id behavior (e.g., that the registry entry is replaced and `deregister(ID)` clears it) and/or isolate with a test-only global mutex shared by all tests that touch `TOTAL_CLIENTS`.
- For the fd-closure test: avoid relying on raw fd integer non-reuse. Options include serializing this test (global mutex) and additionally checking `errno == EBADF`, or using a different invariant that can’t be confused by numeric reuse (e.g., a dedicated helper that opens no other fds while validating).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Backpressure drops accepted conns ✓ Resolved 🐞 Bug ☼ Reliability
Description
When try_route_conn() returns Err(payload), listener::run_sharded falls back to blocking send on
only the originally routed shard; if that shard’s receiver is disconnected, the fallback immediately
errors and drops the accepted connection even though other shards may be alive but temporarily full.
This defeats the intended “server-wide saturation → backpressure” behavior in partial-disconnect
scenarios.
Code

src/server/listener.rs[R505-508]

+                        if let Err(payload) = try_route_conn(&conn_txs, target_shard, (stream, false)) {
+                            warn!("All shard conn channels full; back-pressuring accept on shard {}", target_shard);
+                            if conn_txs[target_shard].send_async(payload).await.is_err() {
+                                error!("Failed to send connection to shard {}", target_shard);
Relevance

●● Moderate

Reliability concern seems plausible, but no close historical precedent on this specific
shard-fallback behavior.

PR-#95
PR-#291

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
try_route_conn() rotates past both full and disconnected senders. The current fallback blocks only
on the originally routed sender, so a disconnected routed shard causes immediate send failure and
connection drop instead of backpressure on a live-but-full shard.

src/server/listener.rs[32-57]
src/server/listener.rs[411-510]
src/server/listener.rs[641-693]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`try_route_conn()` rotates past both full and disconnected shard channels. When it returns `Err(payload)`, the caller currently assumes “all channels full” and blocks only on the originally routed shard.

If that routed shard is disconnected (receiver dropped), the fallback `send_async`/`send` fails immediately and the accepted connection is dropped, even though another shard may be alive and would accept the connection once capacity frees.

### Issue Context
The new routing helper intentionally treats `Full` and `Disconnected` similarly during the try-send rotation, but the fallback needs a *live* shard to backpressure on.

### Fix Focus Areas
- src/server/listener.rs[32-57]
- src/server/listener.rs[424-510]
- src/server/listener.rs[641-693]

### Expected fix
- Replace the single-shard fallback (`conn_txs[target_shard].send_async(...)` / `.send(...)`) with a second rotation that attempts a blocking send across shards:
 - Try `send_async(payload).await` (Tokio) / `send(payload)` (monoio) on each shard in rotation order, continuing to the next shard on immediate `Disconnected` errors.
 - Block on the first connected shard encountered (this provides real backpressure).
- Consider adjusting the warning message to reflect “full or disconnected” rather than “all full” if you keep that log.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. spawn_moon_with uses find_moon_binary 📘 Rule violation ▣ Testability
Description
This integration test spawns the server via common::find_moon_binary(), which can fall back to
target/{release,debug}/moon when MOON_BIN is unset. This violates the requirement that
integration tests must require an explicit MOON_BIN for server binary resolution.
Code

tests/parked_idle_parity.rs[R21-22]

+fn spawn_moon_with(dir: &std::path::Path, port: u16, extra: &[&str]) -> std::process::Child {
    Command::new(common::find_moon_binary())
Relevance

● Weak

Requests to require explicit MOON_BIN (avoid find_moon_binary fallback) were previously rejected for
integration tests.

PR-#427
PR-#421

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 992389 requires integration tests to explicitly set/require MOON_BIN and avoid
helpers that guess target/release/moon. The modified test still spawns using
common::find_moon_binary(), and that helper explicitly falls back to target/release/moon /
target/debug/moon when MOON_BIN is unset.

Rule 992389: Integration tests must set MOON_BIN explicitly for server binaries
tests/parked_idle_parity.rs[21-35]
tests/common/mod.rs[133-166]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An integration test spawns `moon` via `common::find_moon_binary()`, which falls back to guessed paths if `MOON_BIN` is not set.

## Issue Context
Compliance requires integration tests to set/require `MOON_BIN` explicitly and not rely on fallback binary guessing.

## Fix Focus Areas
- tests/parked_idle_parity.rs[21-35]
- tests/common/mod.rs[133-166]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. MigrateFd uses cfg(unix) 📘 Rule violation ≡ Correctness
Description
Connection migration types/APIs are still compiled under #[cfg(unix)] / #[cfg(all(..., unix))],
which includes macOS. This violates the requirement to gate connection migration to Linux-only and
can unintentionally expose/enable migration on unsupported platforms.
Code

src/shard/dispatch.rs[R374-377]

+#[cfg(unix)]
+pub type MigrateFd = std::os::fd::OwnedFd;
+#[cfg(not(unix))]
+pub type MigrateFd = RawSocketFd;
Relevance

● Weak

Linux-only gating for migration was explicitly rejected previously; team kept broader non-Linux
gating.

PR-#444

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 351131 requires all connection migration functionality be gated with
#[cfg(target_os = "linux")] rather than broad unix gating. The PR adds/uses MigrateFd under
#[cfg(unix)] and the migrated-connection spawn API remains `#[cfg(all(feature = "runtime-tokio",
unix))]`, which would compile on macOS.

Rule 351131: Gate connection migration to Linux only
src/shard/dispatch.rs[366-387]
src/shard/conn_accept.rs[420-428]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Connection migration code is gated with `#[cfg(unix)]` / `#[cfg(all(..., unix))]`, which includes macOS. The compliance checklist requires migration to be gated to Linux only.

## Issue Context
The PR introduces/updates migration fd ownership (`MigrateFd` as `OwnedFd`) and migrated-connection spawn APIs, but the platform gates remain `unix` rather than `target_os = "linux"`.

## Fix Focus Areas
- src/shard/dispatch.rs[374-387]
- src/shard/conn_accept.rs[420-428]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 55 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/server/listener.rs Outdated
Comment thread src/client_registry.rs Outdated

@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: 5

🧹 Nitpick comments (1)
src/client_registry.rs (1)

1311-1327: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert the global count after deregistration.

The test checks t2 == t1 before cleanup and checks only live_handle(ID).is_none() afterward. It never verifies that deregister(ID) restores TOTAL_CLIENTS to t1. Add a post-deregistration count assertion under the same test serialization.

🤖 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/client_registry.rs` around lines 1311 - 1327, Extend
replacing_register_does_not_double_count after deregister(ID) to load
TOTAL_CLIENTS with Ordering::Relaxed and assert it equals the pre-replacement
baseline t1, while retaining the existing live_handle assertion and test
serialization.
🤖 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/client_registry.rs`:
- Around line 1315-1322: Make the registry test containing the TOTAL_CLIENTS
assertion run under the existing test serializer so reads of the process-global
counter cannot race with other registry-mutating tests. If no suitable
serializer exists, add a test-only parking_lot::Mutex and acquire its guard for
the test’s full registration, assertion, and deregistration scope, ensuring
cleanup still occurs before releasing the guard.

In `@src/server/listener.rs`:
- Around line 43-56: The try_route_conn function must distinguish full live
channels from disconnected channels: track a full channel index while iterating,
continue probing other shards, and return that index with the payload only when
all channels are unavailable. Update its callers to use the returned live full
shard for fallback blocking, and add coverage for a disconnected target with
another live full shard.

In `@src/shard/conn_accept.rs`:
- Around line 1021-1026: The new unsafe code requires explicit approval: approve
the OwnedFd::from_raw_fd transfer in src/shard/conn_accept.rs lines 1021-1026,
and approve the libc::fcntl(F_GETFD) assertions in src/shard/dispatch.rs lines
1669-1679, or move each operation behind an already-approved isolated
module/test utility.

In `@src/shard/event_loop.rs`:
- Around line 1002-1008: The Rust sources exceed the 1500-line limit; split the
responsibilities into focused modules while preserving behavior. In
src/shard/event_loop.rs lines 1002-1008, extract migration queue draining and
handoff; in src/server/conn/handler_sharded/mod.rs lines 195-205, separate
connection lifecycle from dispatch; in src/shard/conn_accept.rs lines 410-427,
separate Tokio and Monoio acceptance and migration setup; and in
src/shard/dispatch.rs lines 366-386, extract migration payload definitions and
related tests. Ensure all resulting files remain within the size limit and
existing symbols and behavior continue to work.

In `@tests/parked_idle_parity.rs`:
- Around line 454-463: Update the assertion following the parked_clients check
to close the authenticated connection (`authed`), then poll `INFO clients` until
`parked_clients` reaches 0 while `silent` remains open. Assert this post-close
state so the test specifically detects parking of the unauthenticated connection
rather than relying only on the aggregate gauge.

---

Nitpick comments:
In `@src/client_registry.rs`:
- Around line 1311-1327: Extend replacing_register_does_not_double_count after
deregister(ID) to load TOTAL_CLIENTS with Ordering::Relaxed and assert it equals
the pre-replacement baseline t1, while retaining the existing live_handle
assertion and test serialization.
🪄 Autofix

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 Plus

Run ID: 5c644b11-b013-469f-be6f-83edee2cefa1

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf488f and c7c4f0b.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • src/client_registry.rs
  • src/server/conn/handler_monoio/idle_park.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/server/listener.rs
  • src/shard/conn_accept.rs
  • src/shard/dispatch.rs
  • src/shard/event_loop.rs
  • src/shard/spsc_handler.rs
  • tests/parked_idle_parity.rs

Comment thread src/client_registry.rs Outdated
Comment thread src/server/listener.rs Outdated
Comment thread src/shard/conn_accept.rs
Comment thread src/shard/event_loop.rs
Comment on lines +1002 to 1008
// F4 (#438): the fd is an OwnedFd — entries still queued when the event
// loop exits (shutdown) drop here and CLOSE their sockets, giving the
// client a FIN instead of a permanently stranded silent connection.
let mut pending_migrations: Vec<(
crate::shard::dispatch::RawSocketFd,
crate::shard::dispatch::MigrateFd,
crate::server::conn::affinity::MigratedConnectionState,
)> = Vec::new();

@coderabbitai coderabbitai Bot Aug 7, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Split the oversized Rust source files.

These changed files exceed the 1500-line limit.

  • src/shard/event_loop.rs#L1002-L1008: Extract migration queue draining and handoff into a focused module.
  • src/server/conn/handler_sharded/mod.rs#L195-L205: Split connection lifecycle and dispatch responsibilities into modules.
  • src/shard/conn_accept.rs#L410-L427: Split Tokio and Monoio connection acceptance and migration setup.
  • src/shard/dispatch.rs#L366-L386: Extract migration payload definitions and related tests into focused modules.

As per coding guidelines, “No single Rust file should exceed 1500 lines.”

📍 Affects 4 files
  • src/shard/event_loop.rs#L1002-L1008 (this comment)
  • src/server/conn/handler_sharded/mod.rs#L195-L205
  • src/shard/conn_accept.rs#L410-L427
  • src/shard/dispatch.rs#L366-L386
🤖 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 1002 - 1008, The Rust sources exceed
the 1500-line limit; split the responsibilities into focused modules while
preserving behavior. In src/shard/event_loop.rs lines 1002-1008, extract
migration queue draining and handoff; in src/server/conn/handler_sharded/mod.rs
lines 195-205, separate connection lifecycle from dispatch; in
src/shard/conn_accept.rs lines 410-427, separate Tokio and Monoio acceptance and
migration setup; and in src/shard/dispatch.rs lines 366-386, extract migration
payload definitions and related tests. Ensure all resulting files remain within
the size limit and existing symbols and behavior continue to work.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Skipping in this PR: all three files exceeded the 1500-line limit before this change (this PR adds ~30 lines to each), and a module split mid-hardening-campaign would bury the behavioral diff under a mechanical one. Noting the split (event_loop migration-drain extraction, handler_sharded lifecycle/dispatch split) as a follow-up refactor candidate after the #438 tail lands.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@TinDang97, acknowledged. The file-size finding remains valid, but deferring the mechanical module split keeps this hardening PR focused.

Would you like me to create a GitHub follow-up issue for the event_loop migration-drain extraction and handler_sharded lifecycle/dispatch split after #438 lands?

You are interacting with an AI system.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, please create the follow-up issue for the module split (event_loop migration-drain extraction + handler_sharded lifecycle/dispatch split), referencing this PR and the 1500-line guideline.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="pilotspace/moon"
body=$(cat <<'EOF'
## Summary

Refactor the oversized Rust modules that remain after the Cluster F hardening work.

This follow-up is intentionally separate from PR `#446`. The current PR changes connection lifecycle behavior. A mechanical module split in the same change would make that behavior harder to review.

## Required changes

- Extract the pending migration queue drain and connection handoff from `src/shard/event_loop.rs` into a focused module.
- Split connection lifecycle responsibilities from dispatch responsibilities in `src/server/conn/handler_sharded/mod.rs`.
- Preserve the current connection migration, shutdown, registration, and dispatch behavior.

## Rationale

The repository coding guideline requires that no Rust source file exceeds 1500 lines. These files already exceeded that limit before PR `#446`. The hardening PR adds only limited changes, so this refactor is deferred to keep the behavioral diff focused.

## Affected areas

- `src/shard/event_loop.rs`
- `src/server/conn/handler_sharded/mod.rs`

## Backlinks

- PR: https://github.com/pilotspace/moon/pull/446
- Review discussion: https://github.com/pilotspace/moon/pull/446#discussion_r3736083965
- Requested by: `@TinDang97`

## Acceptance criteria

- The migration-drain and handoff logic is in a focused module.
- Connection lifecycle and dispatch responsibilities are separated into focused modules.
- Each resulting Rust source file complies with the 1500-line guideline.
- Existing public and internal behavior remains unchanged.
- The relevant unit and consistency tests pass.
- Formatting and Clippy complete without new warnings for both runtime feature configurations.
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Refactor oversized shard connection modules after `#446`" \
  --body "$body" \
  --assignee "TinDang97"

Length of output: 2056


@TinDang97, created follow-up issue #447.

It covers the event_loop migration-drain extraction and the handler_sharded lifecycle/dispatch split. It references PR #446, the review discussion, and the 1500-line Rust file guideline.

You are interacting with an AI system.

Comment thread tests/parked_idle_parity.rs
…ic tests, F6 identity check

Review findings (Qodo + CodeRabbit, converging on one real defect):

1. Fallback could drop a conn a live shard would take (both bots,
   CONFIRMED): when try_route_conn exhausted the rotation, the blocking
   fallback targeted the ORIGINALLY routed shard — if that receiver was
   disconnected while another shard was merely full, the fallback
   errored instantly and the accepted conn was dropped. The helper now
   reports the first LIVE-but-full shard from the rotation
   (Err((Option<usize>, payload))); callers back-pressure on THAT
   shard, and only drop (with an error log) when every receiver is
   gone — i.e. shutdown, where dropping is correct. New unit tests:
   disconnected-target-with-live-full-shard, all-disconnected.

2. Flaky global-state unit tests (both bots): the TOTAL_CLIENTS bracket
   could false-fail if an unrelated test registered between its two
   loads, and a failed assert skipped cleanup; it now retries with a
   fresh id (bug reads +1 on EVERY attempt; fix needs one undisturbed
   attempt) and deregisters before the verdict. The F4 fd-drop test
   probed the raw fd number, which the parallel runner could reuse
   between close and probe; it now observes the close through the
   PEER's read() -> EOF — no fd-number reasoning at all.

3. F6 test identified WHICH conn parked (CodeRabbit): parked_clients=1
   also holds if the silent conn parked and the authed one didn't. The
   test now closes the authed conn and requires the gauge to drain to
   0 while the silent conn stays open.

Skipped with rationale (replied on-thread):
- "Split oversized files" — pre-existing sizes, out of scope for a
  hardening PR; noted for a future refactor wave.
- New-unsafe flag on the monoio dup wrap — consolidates two prior
  unsafe sites at the same location; net unsafe count for the PR is
  -3, SAFETY comment present.

Gates: fmt + clippy -D warnings (both feature sets), listener/registry/
migrate-fd unit tests green both feature sets, parked_idle_parity
monoio 3/4 full-green runs (the one bad run failed on connect/read
setup races in OTHER tests — the documented harness flake class queued
for the test-hygiene sweep; the new assertions never tripped) and
tokio 7/7.

Refs #438, #446
author: Tin Dang
@TinDang97
TinDang97 merged commit cf3cee4 into main Aug 7, 2026
40 checks passed
@TinDang97
TinDang97 deleted the fix/438-f3-f6-conn-secondary branch August 7, 2026 13:43
TinDang97 added a commit that referenced this pull request Aug 7, 2026
…iene) (#449)

Pid-only temp-dir names (`temp_dir().join(format!("...-{}", pid))`) at
eight spawn sites across six suites resurrected STALE data dirs: a
crashed run leaves its dir behind, the pid is eventually reused, and
the next run's server silently reloads the leftover persistence state
(the documented CWD/stale-reload trap) — failures then point at
whatever assertion tripped over the ghost data, not at the cause.

- info_memory_allocator_pagecache, memory_doctor_response,
  memory_prometheus_kinds: the shared Moon harness now owns a
  tempfile::TempDir (random unique name; removed on drop AFTER the
  child is killed — hand-written Drop body runs before field drops).
- vector_exact_rerank (3 sites): RAII tempdir; also removes the
  manual pre/post remove_dir_all pairs, so cleanup now happens even
  when an assert panics mid-test.
- vector_db_isolation (2 sites): unique random dirs via
  tempfile::Builder + keep(). Deliberately NOT RAII: the restart test
  shares one dir between two Moon values (kill_keep_dir → same-port
  respawn), so the existing PathBuf ownership + manual cleanup are
  kept and only the collision-prone NAME is fixed.
- tls_park_keyupdate: RAII tempdir; early-return and end-of-test
  manual removes deleted.

parked_idle_parity deadline-poll fixes (the flakes deferred from the
#444/#445 rounds, all environmental classes observed in CI or today's
gate runs):
- CLIENT KILL registry-removal assert now POLLS with a 10s deadline —
  the client-side close arrives instantly via shutdown(2) but the
  registry entry is released only when the killed handler task gets
  scheduled; the read-once assert fired 3/3 on the starved 2-vCPU
  runner and passed solo (documented assert-too-soon race).
- All 13 TcpStream::connect sites use a connect_retry helper (10s
  deadline, 50ms backoff): under full-suite load a freshly listening
  server can still refuse the first attempts, which failed tests at
  the connect line (seen live today at the F6 test's connect).
- Read deadlines 10s → 30s in the two reply helpers; deadline-bound,
  so green runs spend no extra time.

The client_tracking_invalidation multikey second-key push flake is
PRODUCT-side (a real RESP3 client would miss the same invalidation) —
filed as #448 with the merge-base A/B evidence instead of being
papered over here.

Gates: fmt + clippy -D warnings (default and tokio,jemalloc); all six
converted suites + parked_idle_parity green on macOS monoio (31
tests); tokio leg green on the four suites that run there. Bench
gates waived — tests-only change, no src/ code touched.

Refs #444 #445 #446, closes nothing (#448 filed for the product flake)
author: Tin Dang
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.

1 participant