fix(envd): fix data race in fan-out when subscriber is removed mid-iteration#2643
Conversation
❌ 2 Tests Failed:
View the full list of 6 ❄️ flaky test(s)
To view more test analytics, go to the Test Analytics Dashboard |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b9a572a6d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Code Review
This pull request addresses a race condition in the MultiplexedChannel by creating a shallow copy of the subscribers slice before iteration, which prevents slice corruption during concurrent modifications. A regression test has been added to verify the fix and ensure stable delivery to subscribers. I have no further feedback.
8b9a572 to
17fdd0a
Compare
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit a354a07. Bugbot is set up for automated code reviews on this repo. Configure here. |
40f5326 to
8756a4c
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8756a4c722e294965dc13379a942b911c06fe157. Configure here.
…teration reproducer
…eration run() snapshots the m.channels slice header under RLock and iterates after unlocking. remove() used append(channels[:i], channels[i+1:]...) which shifts the shared backing array in-place, corrupting the concurrent iteration — a subscriber can receive a value twice while another misses it entirely. Fix remove() to allocate a new backing array via slices.Concat so the old slice that run() holds remains stable. This moves the allocation to the cold path (subscriber disconnect) instead of the hot path (every fan-out delivery). Fixes: a67f983 ("fix(envd): fix fan-out deadlock when process subscriber disconnects (#2579)")
340d4c1 to
5431989
Compare

run() snapshots the m.channels slice header under RLock and iterates after unlocking. remove() used append(channels[:i], channels[i+1:]...) which shifts the shared backing array in-place, corrupting the concurrent iteration — a subscriber can receive a value twice while another misses it entirely.
Fix remove() to allocate a new backing array via slices.Concat so the old slice that run() holds remains stable. This moves the allocation to the cold path (subscriber disconnect) instead of the hot path (every fan-out delivery).
Fixes: a67f983 ("fix(envd): fix fan-out deadlock when process subscriber disconnects (#2579)")