Skip to content

test(relay): restart the crash drill on a fresh port behind a stable address - #5160

Merged
kixelated merged 8 commits into
mainfrom
quest/m1/test-flakes-2/relay-restart-rebind
Oct 10, 2026
Merged

kixelated merged 8 commits into
mainfrom
quest/m1/test-flakes-2/relay-restart-rebind

Conversation

@kixelated

@kixelated kixelated commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Completes quest/m1/test-flakes-2/relay-restart-rebind.md.

Root cause

relay_killed_mid_group_aborts_then_resumes restarted the relay on its old ephemeral UDP port. That port sits free for the whole kill-to-restart window, which is at least the clients' 2s idle timeout, because the restart waits for both reconnect loops to report Disconnected. Any process's bind(0) can take it during that window, and then the restart fails with Address already in use.

Measured with a temporary instrumented build (not committed). It snapshots the sockets this process holds on the port right after RelayHost::kill, then on a failed restart maps the port's /proc/net/udp inode to its owning pid:

  • Natural load (8 parallel loops of the full drills binary, load average about 40-60): about 450 runs. One restart failed with failed to bind UDP socket: Address already in use. Right after the kill, the process held nothing on that port (the kill took 3.7ms), and the restart failed 2.3s later. So the socket was released and then taken by someone else.
  • Retained ownership ruled out: in about 1,600 instrumented kills, the old relay's socket was never held after kill returned. The slowest kill took 24ms, well inside the 1s shutdown_timeout.
  • Owner captured: I added an ephemeral-bind churn that holds about 3,000 127.0.0.1:0 sockets, standing in for other tests on a loaded machine. With it, 61 of 750 restarts failed (8%, close to the 3000/28232 share of the port range). Every failure's port belonged to the competing ephemeral socket, never to this process.
  • Fixed build under the same churn: 353 of 353 runs passed (706 restarts).

The seed did not matter: this is a port-allocation race, not a shaper decision.

Fix

Clients now dial a stable address and the relay comes back on a fresh port behind it:

  • moq-shaper gains Shaper::retarget(target). It forwards to a new target from then on and keeps each client's flow, so the new target sees the same source addresses. It refuses a target the flows' already-bound sockets cannot reach (a different family, or loopback vs. not).
  • The drill dials a shaper in both lanes: unimpaired on loopback (same as the flapping-peer drill's loopback lane), and with the usual impairment() on impaired. Path::retarget points the shaper at the replacement after both loops report Disconnected.
  • The replacement relay binds 127.0.0.1:0 before the kill, while the original still holds its port, so the two can never share one. A restart that needed the old port back would fail both lanes on every run with Address already in use, so this is the regression check.
  • relay_config(Option<u16>) becomes relay_config(), since nothing pins a port any more.

Crash semantics are unchanged: the relay's runtime is still dropped with no CONNECTION_CLOSE, both loops still have to report Disconnected and then Connected, and the interrupted group and subscription still have to abort. just test drill-sensitivity reconnect-stops-after-session-loss still fails the loopback lane with subscriber: reconnect loop gave up.

Impact

  • Public API: moq_shaper::Shaper::retarget is added. moq-shaper is publish = false and test-only. No published crate changes.
  • Wire: none.
  • Docs: test/drill/README.md (Killing the relay) and rs/moq-shaper/README.md are updated.

Tests

  • just check: passed.
  • cargo test -p moq-shaper: passed, including the new a_retarget_moves_the_flow_and_ignores_the_old_target.
  • cargo test -p moq-relay --test drills relay_killed: both lanes pass.
  • just test drill-sensitivity reconnect-stops-after-session-loss: the drill still fails without the reconnect behavior.

Follow-ups

Other flakes seen during the natural-load stress, with 8 full drills runs in parallel.

  • bursts_cross_a_cluster::impaired failed 5 times with "no burst overran a bottleneck, so none was exercised": Cluster burst overrun.
  • bursts_cross_a_flapping_peer::impaired failed once with "stalled with the route withdrawn: nothing arrived within 21s". No quest tracks this.
  • Connect timeouts in bursts_cross_a_*::impaired and once in interrupted_publisher_republishes_new_content::impaired: Impaired handshake, whose scope main broadened to all impaired drills.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits October 9, 2026 17:22
…address

relay_killed_mid_group_aborts_then_resumes restarted the relay on its old
ephemeral UDP port, which sat free for the ~2s idle-timeout window between
the kill and the restart. Any process's bind(0) could take it there, failing
the restart with "Address already in use". Instrumented runs showed the old
relay never kept its socket past the kill; the port was always held by a
competing ephemeral bind.

Clients now dial a moq-shaper in both lanes (unimpaired on loopback), and the
replacement relay binds a fresh port that `Shaper::retarget` points the
shaper at. The drill holds the dead port itself, so a restart that needs it
back fails every run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Outcome (quest-start, run unattended)

The quest is done as scoped. The cause was measured, not assumed. A competing ephemeral bind(0) takes the freed relay port during the ~2s window between the kill and the restart. The old relay never kept its socket after kill: 0 of about 1,600 instrumented kills. The drill now restarts on a fresh port behind a shaper that Shaper::retarget points at the new relay. It also holds the dead port itself, so a restart that needs the old port back fails every run.

Choices for the reviewer to confirm. Each is a recommendation, not a blocker:

  1. The loopback lane of this drill now goes through an unimpaired shaper. Recommend keeping it. A stable client-facing address needs a forwarder, and bursts_cross_a_flapping_peer::loopback already does the same.
  2. Shaper::retarget is the name and shape (&self, returns anyhow::Result, refuses a target the bound flow sockets cannot reach). Recommend keeping it. The crate is publish = false and test-only, and the method sits next to cut().

Suggested follow-ups (not started):

  • A quest for bursts_cross_a_cluster::impaired failing "no burst overran a bottleneck" under heavy parallel load: 5 of about 450 full-suite runs. Recommended.
  • A quest for bursts_cross_a_flapping_peer::impaired stalling "with the route withdrawn": once in about 450 runs. Recommended, at lower priority.
  • Widen Impaired handshake to cover interrupted_publisher_republishes_new_content::impaired, which also hit a connect timeout once. Recommended.

(Written by Claude Opus 5.5)

@kixelated
kixelated marked this pull request as ready for review October 10, 2026 01:16
@kixelated

Copy link
Copy Markdown
Collaborator Author

Decisions confirmed by the maintainer

  1. The loopback lane of relay_killed_mid_group_aborts_then_resumes runs through an unimpaired shaper, matching bursts_cross_a_flapping_peer::loopback.
  2. Shaper::retarget(&self, target) -> anyhow::Result<()> stays as shaped, in the test-only moq-shaper crate (publish = false).

The suggested follow-up quests from the outcome comment are not started; they are left for the maintainer to plan.

(Written by Claude Opus 5.5)

@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →Review in Change Stack →

Warning

Review limit reached

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

Next included review available in 34 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0172ab30-ac0e-4ff1-baee-a5f9b93834f5

📥 Commits

Reviewing files that changed from the base of the PR and between c922f8c and 1a6d38c.


📒 Files selected for processing (6)
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/relay-restart-rebind.md
  • rs/moq-relay/tests/drills.rs
  • rs/moq-shaper/README.md
  • rs/moq-shaper/src/lib.rs
  • test/drill/README.md

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 273be696-45ba-4dad-b2d5-ddc806b082c5





📥 Commits

Reviewing files that changed from the base of the PR and between 0bddd90 and c922f8c.






📒 Files selected for processing (6)
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/relay-restart-rebind.md
  • rs/moq-relay/tests/drills.rs
  • rs/moq-shaper/README.md
  • rs/moq-shaper/src/lib.rs
  • test/drill/README.md





💤 Files with no reviewable changes (2)
  • quest/m1/test-flakes-2/README.md
  • quest/m1/test-flakes-2/relay-restart-rebind.md





Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.







Walkthrough

The shaper now supports changing its forwarding target while preserving existing client flows. Relay hosts in the drills bind on ephemeral loopback ports and expose their bound addresses. The relay restart drill starts a replacement before stopping the original, then retargets the existing path. Other drills use the updated relay configuration. The documentation describes the fresh-port restart flow, and the quest list no longer links to the removed restart/rebind quest.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to c922f

The restart drill no longer depends on reclaiming a released port. No merge-blocking issue is established by the supplied evidence.

Pre-merge checks | Passed 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage Passed Docstring coverage is 95.83% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 24 functions across 2 files. (2 skipped: 2 …
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.
Title check Passed The title clearly and concisely describes the main change: restarting the relay drill on a fresh port while preserving a stable client-facing address.
Description check Passed The description directly explains the port-allocation race, the Shaper::retarget fix, the drill changes, and the validation results.





✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch quest/m1/test-flakes-2/relay-restart-rebind





✨ Simplify code
  • Commit to this branch
  • Create a new PR










  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-10T12:49:06.511680Z 1a6d38c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1932b2ca99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-relay/tests/drills.rs Outdated
// Take the dead relay's port the way another process's ephemeral bind can,
// so the restart below never depends on getting it back. A failed bind means
// someone else already holds it, which tests the same thing.
let _taken = std::net::UdpSocket::bind(relay.addr()).ok();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Verify the old relay port remains unavailable

If another process owns this port when bind runs but releases it before the replacement starts, .ok() leaves _taken empty and the subsequent ephemeral bind can select the old port again. A build that still depends on rebinding the original address could therefore pass this regression check. Preserve the old address and assert that the replacement differs, or otherwise ensure the old port stays unavailable.

AGENTS.md reference: AGENTS.md:L18-L18

Useful? React with 👍 / 👎.

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.

Agreed, fixed in 5da6c96. The drill now keeps the dead relay's address and asserts the restarted relay binds a different one, so a restart that reclaims the old port fails even when the reservation lost the race to a process that later released it. No retries or sleeps. This also covers the same point in the kixelated (OpenAI) review.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed commit: 1932b2c

P2: The port-collision regression can pass without exercising the collision. At rs/moq-relay/tests/drills.rs:600–617, a competing process can own the old port when the reservation fails, then release it during the disconnect waits. With _taken == None, a restart that still explicitly rebinds that port can succeed. This confirms the existing independent Codex finding. Make the regression establish that the old address remains occupied through restart, or preserve and check the replacement address so rebinding cannot silently pass; avoid adding retries or timing sleeps.

Direction: Keep the stable shaper address and fresh-port restart. Reusing the existing shaper is a focused solution; the flow preservation and old-target filtering fit the crash semantics. No additional actionable findings in the six-file diff and relevant surrounding code.

Verification: GitHub-only static review; no tests executed. The listed Check and Platform runs were queued when inspected. Author-reported stress and sensitivity results were not independently reproduced.

(Written by OpenAI)

The port reservation can lose to another process that later releases the
port, so also assert the replacement relay binds a different address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the final head 5da6c96, which addresses the port-reservation finding.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Grok review of 5da6c965

Solid fix at the cause: clients dial a stable shaper address, and the replacement relay binds a fresh port. The measurement in the description backs up the root cause, and holding the dead port makes the regression check deterministic. I found no blocking issues.

Non-blocking

  1. rs/moq-relay/tests/drills.rs: assert_ne!(relay.addr(), old, …) can flake in a rare case. If UdpSocket::bind(old).ok() fails because another process holds the port, and that process then releases it before the restart, the kernel can hand the same port to the fresh bind(0). The restart would work, but the assert fails it. The odds are tiny, but this PR exists to remove load flakes. A fix: only assert when _taken.is_some(), or retry the dead-port bind until it succeeds.
  2. drills.rs: reconnect attempts between RelayHost::start and path.retarget still go to the old port. When the drill holds that port, _taken swallows them. The reconnect loop recovers on its next attempt, so this only costs some backoff. Retargeting right after the new relay binds (before expect_status(... Connected)) already keeps the window short, so this is fine. It's just worth a comment that early Initials are dropped on purpose.
  3. rs/moq-shaper/src/lib.rs retarget: datagrams already queued in a link keep the old to, as the doc says. Replies, though, are filtered against the current target, so a late reply from the old target after a retarget is dropped. That's the intended behavior and the test covers it. But the doc comment only mentions datagrams heading upstream, so a sentence about the reply side would help.
  4. retarget with an unchanged local_ip but a different host (two non-loopback IPv4 targets) is allowed. That's correct for an UNSPECIFIED bind. It's just noting the check is about reachability, not "same host".

CI: only Auto-merge (skipping) has reported so far. Wait for the drills to run in both lanes.

Verdict: MERGE (once CI is green)

This is an automated review, not the maintainer's decision
(Written by Grok)

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed commit: 5da6c96
Scope: one-commit delta from 1932b2c; PR base unchanged.

P2: Make distinct-port allocation deterministic before asserting it. At rs/moq-relay/tests/drills.rs:618–621, the new assertion also rejects correct bind(0) behavior: if another process wins the reservation at line 604 and then releases the port during the disconnect waits, the kernel may legitimately allocate that same port to the replacement. The drill then flakes despite no old-port dependency.

The previous false-pass finding is addressed, but my earlier suggestion to check the address was incomplete without guaranteeing the allocation. One deterministic option is to start the replacement while the original still owns its port, keep the shaper pointed at the original through the kill and both Disconnected observations, then retarget. This preserves the client-visible outage without a reservation race, retries, or sleeps.

Direction: Keep the stable-address approach; fix the collision setup rather than relying on the allocator to choose a different number.

Verification: GitHub-only static delta review and surrounding drill inspection; no tests executed or stress reproduction. GitHub currently reports the PR as not mergeable.

(Written by OpenAI)

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5da6c965af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread rs/moq-relay/tests/drills.rs Outdated
let relay = RelayHost::start(relay_config()).await;
// Whoever held the old port may have released it by now, so check the
// address too: a restart that reclaimed it must not pass.
assert_ne!(relay.addr(), old, "the restarted relay reused the dead relay's port");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid rejecting valid ephemeral port reuse

When the reservation loses to another process and that process releases the old port during the disconnect waits, bind(0) may legitimately choose that port for the replacement even though the code no longer depends on rebinding it. This new assertion cannot distinguish that allocator collision from explicitly requesting the old address, so the corrected drill can still fail intermittently under the exact contention it is intended to tolerate. Assert that the requested bind port is zero, or otherwise validate the configuration rather than the allocator's chosen result.

Useful? React with 👍 / 👎.

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.

Agreed, the assertion could reject a legitimate ephemeral reuse. Fixed in c922f8c using the deterministic setup from the kixelated (OpenAI) review: the replacement relay now starts before the kill, while the original still holds its port, so the two can never share one. The reservation and the address assertion are gone. Clients keep dialing the dead relay through the shaper until both loops report Disconnected, then retarget points them at the replacement, so the client-visible outage is unchanged.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 9, 2026 19:07
The replacement relay now binds while the original still holds its port,
so the two can never share one. This replaces the port reservation and
the address assertion, which could reject a legitimate ephemeral reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the final head c922f8c, which starts the replacement relay before the kill instead of reserving the dead port.

(Written by Claude Opus 5.5)

@kixelated

Copy link
Copy Markdown
Collaborator Author

Follow-up review at c922f8c5 (since 5da6c965; the main merge is excluded)

The push starts the replacement RelayHost before relay.kill(), so the two relays can never share a port, and drops the bind-the-dead-port trick and the assert_ne! on the address. That fixes my earlier flake finding: the rare false failure when the dead-port bind failed and was then released is gone, and the guarantee now holds by construction. The README matches the new ordering.

Still open (non-blocking, from the earlier review): early reconnect Initials are still swallowed, and the retarget doc still doesn't cover the reply side. One small nit: the replacement is idle for the whole abort/disconnect phase. That's fine, but a short comment saying nothing dials it until retarget would help future readers.

Verdict: MERGE once CI is green.

This is an automated review, not the maintainer's decision
(Written by Grok)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: c922f8c569

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the final head be7b56d. Since your clean review of c922f8c it merges origin/main (one quest README conflict) and adds one doc sentence to Shaper::retarget: the old target's late replies are dropped.

(Written by Claude Opus 5.5)

@kixelated kixelated left a comment

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.

Automated review by review (OpenAI)

Reviewed commit: be7b56d

Both earlier port-allocation P2s are addressed. In rs/moq-relay/tests/drills.rs:598–616, the replacement binds while the original still owns its port, then the shaper switches only after both clients report Disconnected. Removing the racy reservation and allocator-result assertion eliminates the false-pass/false-failure cases described in my previous review and the independent Codex finding, while retaining the interrupted-group, subscription-abort, and resumed-delivery checks.

No new actionable findings. Keep this simpler deterministic setup and the existing test-only shaper API. The late-reply documentation now matches the target filter.

Scope and limits: reviewed the substantive delta since 5da6c96, accounting for merged main changes, and rechecked the current PR diff and relevant surroundings. GitHub-only static review; no tests or stress runs executed. Check and Platform were queued when inspected.

(Written by OpenAI)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: be7b56db5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the final head 1a6d38c. Since your clean review of be7b56d it only merges origin/main (no conflicts, including #5156's moq-tokio deferred-FIN fix). relay_killed_mid_group_aborts_then_resumes passes in both lanes locally on this head.

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 1a6d38ca81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary

Changes: the crash drill now dials a moq-shaper in both lanes (unimpaired on loopback) and restarts the relay on a fresh ephemeral port. The replacement binds before the kill, while the original still holds its port, so the two never share one. Shaper::retarget moves the shaper to the replacement after both reconnect loops report Disconnected. This removes the Address already in use restart flake at its cause: during the 2s idle-timeout window, any process's ephemeral bind could take the freed port.

Decisions (accepted by the maintainer):

  • An unimpaired shaper lane on loopback, matching the flapping-peer drill.
  • retarget lives in the test-only moq-shaper crate (publish = false), so no published API changes. Wire: none.

Review findings:

  • The OpenAI and Codex P2s on the dead-port reservation (a false pass, then a false failure from assert_ne!) were fixed by starting the replacement before the kill.
  • Grok's non-blocking nits: the drill comment covers early reconnect Initials reaching the dead relay until the retarget, and the retarget doc now says the old target's late replies are dropped.
  • OpenAI reviewed be7b56db5 and Codex reviewed 1a6d38ca8 (the final head), both with no findings.

Since the interrupted merge: merged origin/main twice (one quest README conflict, resolved to main's list without this quest), refreshed the description to the final approach, and reran relay_killed_mid_group_aborts_then_resumes in both lanes plus cargo test -p moq-shaper locally.

Follow-up: bursts_cross_a_flapping_peer::impaired stalled once with the route withdrawn, and no quest tracks it. The cluster burst overrun and impaired connect timeouts are covered by existing quests.

(Written by Claude Opus 5.5)

@kixelated
kixelated merged commit 8fe2585 into main Oct 10, 2026
6 checks passed
@kixelated
kixelated deleted the quest/m1/test-flakes-2/relay-restart-rebind branch October 10, 2026 13:00
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