Skip to content

test(ffmpeg-ipc): fix Windows hang in IpcConnection multiplexed tests (drain outbound request)#2016

Merged
yuto-trd merged 3 commits into
mainfrom
yuto-trd/multiplexed-test-drain-outbound
Jun 25, 2026
Merged

test(ffmpeg-ipc): fix Windows hang in IpcConnection multiplexed tests (drain outbound request)#2016
yuto-trd merged 3 commits into
mainfrom
yuto-trd/multiplexed-test-drain-outbound

Conversation

@yuto-trd

@yuto-trd yuto-trd commented Jun 25, 2026

Copy link
Copy Markdown
Member

Description

Fixes a Windows-only deterministic hang in IpcConnectionMultiplexedTests (the fixture added in #1826). Four tests started a request via SendAndReceiveAsync but the test server never read the outbound request:

  • ProtocolCorruption_PendingRequestsObserveIOExceptionWithCause
  • AfterReceiveLoopDies_NewRequestsFailFast
  • DroppedResponseHandler_Throws_ReceiveLoopSurvives
  • DisposeWhileRequestPending_ObservesCancellationNotIOException

On Windows, a named-pipe write to a peer that never reads does not complete, so SendAsync's pipe write stayed pending and the request never reached the await-on-response stage these tests exercise. Injecting a malformed frame, disposing, or replying then could not complete the stuck task, so the run hung (ProtocolCorruption / AfterReceiveLoopDies), observed ObjectDisposedException instead of OperationCanceledException (DisposeWhileRequestPending), or timed out (DroppedResponseHandler). On Linux the small write buffers immediately, so CI never caught it. A hang dump (dumpasync) pinned the stuck await at MessageSerializer.WriteMessageAsync inside the initial send — i.e. the request never got past send, not receive.

Fix: read (drain) the outbound request from the server so the client's send completes and the request parks on its response TCS — mirroring a real peer that reads the request before responding — and assert the drained id. Also bound the previously unbounded await-on-fault waits so a future regression fails fast with a clear assertion instead of hanging the whole suite for hours.

No production change: a real worker always reads requests, and a broken pipe fails a blocked write, so IpcConnection itself is unaffected.

Affected areas

  • Beutl.FFmpegIpc / Beutl.FFmpegWorker (media IPC boundary) — test only

Breaking changes

None — test-only.

Test plan

  • tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs: 18/18 green, run 3× consecutively (5s each, no hang) on Windows where it previously hung deterministically at the 60s blame-hang timeout. Full Beutl.FFmpegIpc.Tests project: 28/28 green. dotnet format --verify-no-changes clean.

Fixed issues / References

Summary by CodeRabbit

  • Tests
    • Improved determinism for receive-loop cancellation and failure scenarios.
    • Ensured pending/protocol-faulted requests are fully progressed and observed before triggering cancellation, protocol corruption, or disposal.
    • Added bounded waits to avoid hangs and make test failures surface promptly.
    • Updated assertions so requests reliably fault/fail-fast after the receive loop shuts down, and disposal observes the expected cancellation behavior.

…Windows hang

Four IpcConnectionMultiplexedTests started a request via SendAndReceiveAsync but
the test server never read the outbound request:
ProtocolCorruption_PendingRequestsObserveIOExceptionWithCause,
AfterReceiveLoopDies_NewRequestsFailFast,
DroppedResponseHandler_Throws_ReceiveLoopSurvives, and
DisposeWhileRequestPending_ObservesCancellationNotIOException.

On Windows a named-pipe write to a peer that never reads does not complete, so
SendAsync's pipe write stayed pending and the request never reached the
await-on-response stage these tests exercise. Injecting a malformed frame,
disposing, or replying then could not complete the stuck task, so the run hung
(ProtocolCorruption / AfterReceiveLoopDies), observed ObjectDisposedException
instead of OperationCanceledException (DisposeWhileRequestPending), or timed out
(DroppedResponseHandler). On Linux the small write buffers immediately, so CI
never caught it; a hang dump (dumpasync) pinned the stuck await at
MessageSerializer.WriteMessageAsync inside the initial send.

Fix: read (drain) the outbound request from the server so the client's send
completes and the request parks on its response TCS, mirroring a real peer that
reads the request before responding; assert the drained id while we are at it.
Also bound the previously unbounded await-on-fault waits so a future regression
fails fast with a clear assertion instead of hanging the whole suite.

No production change: a real worker always reads requests, and a broken pipe
fails a blocked write, so IpcConnection itself is unaffected. Follow-up to #1826.
Copilot AI review requested due to automatic review settings June 25, 2026 00:31
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fe60ed04-8255-45dd-bd80-d5d82d44db33

📥 Commits

Reviewing files that changed from the base of the PR and between 7117c6f and c7cba77.

📒 Files selected for processing (1)
  • tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs

📝 Walkthrough

Walkthrough

Updated multiplexed IPC tests to make request-state transitions deterministic by draining server reads before cancellation, protocol corruption, and disposal. Added bounded assertions so faulting request tasks must complete instead of hanging.

Changes

IpcConnectionMultiplexed timing changes

Layer / File(s) Summary
Drain requests before cancellation
tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs
The dropped-response scenarios now drain requests on the server before confirming pending state and triggering cancellation paths.
Bound protocol-corruption faults
tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs
The protocol-corruption scenarios now wait for the request to be read, inject invalid bytes, and assert the request task faults within a bounded wait.
Drain before dispose
tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs
The disposal scenario now drains the pending request before disposing and waits for the response-awaiting state.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 I hopped through tests with careful care,
Drained each request from server air.
Corrupt and cancel, then let it rest,
Bounded waits now keep the rabbit blessed.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing a Windows hang in multiplexed ffmpeg IPC tests by draining the outbound request.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 yuto-trd/multiplexed-test-drain-outbound

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates IpcConnectionMultiplexedTests to avoid a Windows-only deterministic hang by ensuring the test “server” drains outbound requests so the client-side SendAsync can complete and the request reaches the intended “awaiting response” state before fault/cancel/dispose scenarios are exercised.

Changes:

  • Drain outbound requests from the server side (and assert the drained request id) so SendAndReceiveAsync reliably reaches the pending-response phase on Windows.
  • Add explicit time bounds around previously unbounded waits for fault propagation so regressions fail fast instead of hanging the suite.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs 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.

🧹 Nitpick comments (1)
tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs (1)

221-221: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider bounding the drain reads for consistency with the fail-fast goal.

Each new server-side drain (await MessageSerializer.ReadMessageAsync(server) at Lines 221, 240, 434, 472, 515) is awaited without a timeout. The rest of this PR deliberately replaces unbounded awaits with bounded Task.WhenAny(..., Task.Delay(...)) so a regression fails fast instead of hanging the suite. If a future change prevents the client send from reaching the server, these drains would hang indefinitely — the exact failure mode this PR is trying to eliminate. Wrapping the drain in a bounded helper (e.g. an AwaitWithResult-style wrapper) would preserve fail-fast for these reads too.

Also applies to: 240-240, 434-434, 472-472, 515-515

🤖 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 `@tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs` at line 221,
The server-side drain reads in IpcConnectionMultiplexedTests still use unbounded
awaits on MessageSerializer.ReadMessageAsync(server), which can hang the suite
and bypass the fail-fast pattern used elsewhere. Update each drain in the
affected test cases to use the same bounded await approach as the rest of the
PR, ideally via a shared AwaitWithResult-style helper or Task.WhenAny with
Task.Delay, so these reads time out consistently if the client message never
arrives.
🤖 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.

Nitpick comments:
In `@tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs`:
- Line 221: The server-side drain reads in IpcConnectionMultiplexedTests still
use unbounded awaits on MessageSerializer.ReadMessageAsync(server), which can
hang the suite and bypass the fail-fast pattern used elsewhere. Update each
drain in the affected test cases to use the same bounded await approach as the
rest of the PR, ideally via a shared AwaitWithResult-style helper or
Task.WhenAny with Task.Delay, so these reads time out consistently if the client
message never arrives.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 33451c1d-4197-46d6-a31d-af02ae2c8e9c

📥 Commits

Reviewing files that changed from the base of the PR and between a24a93c and 99fdb11.

📒 Files selected for processing (1)
  • tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs

@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: 99fdb11f29

ℹ️ 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 tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
Comment thread tests/Beutl.FFmpegIpc.Tests/IpcConnectionMultiplexedTests.cs Outdated
yuto-trd added 2 commits June 25, 2026 14:38
… comments in English

Review follow-up (#2016). The drain reads added to make these tests pass on
Windows were unbounded awaits: if the client send regresses again, they would
hang before the later time-bounded waits run. Wrap each drain with
AwaitWithResult(... .AsTask(), 5s) so a future regression fails fast with a clear
assertion instead of hanging the runner. Also rewrite the newly added explanatory
comments in English per the AGENTS.md language policy (pre-existing Japanese
comments from #1826 are left untouched).
…ments

Keep the Windows "unread pipe write does not return" footgun and the
ObjectDisposedException-vs-OperationCanceledException distinction; drop the
repeated "bound the drain so a regression fails fast" justification (the 5s
timeout already shows it) and the bounded-wait paraphrase (the assertion
message already states the intent).
@github-actions

Copy link
Copy Markdown
Contributor

No TODO comments were found.

@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
Beutl.Api 9% 4% 1171
Beutl.Configuration 41% 22% 350
Beutl.Controls 0% 0% 5513
Beutl.Core 62% 56% 3069
Beutl.Editor 78% 70% 1742
Beutl.Editor.Components 1% 0% 8552
Beutl.Embedding.MediaFoundation 6% 8% 1376
Beutl.Engine 63% 52% 18123
Beutl.Engine.SourceGenerators 59% 44% 540
Beutl.Extensibility 38% 59% 112
Beutl.Extensions.AVFoundation 5% 12% 246
Beutl.Extensions.FFmpeg 6% 7% 853
Beutl.FFmpegIpc 25% 33% 825
Beutl.Language 7% 50% 1349
Beutl.NodeGraph 24% 15% 2474
Beutl.ProjectSystem 58% 43% 1061
Beutl.Threading 100% 90% 137
Beutl.Utilities 94% 87% 358
Summary 35% (43578 / 124687) 31% (10859 / 35508) 47851

Minimum allowed line rate is 0%

@yuto-trd yuto-trd merged commit 5c44709 into main Jun 25, 2026
4 checks passed
@yuto-trd yuto-trd deleted the yuto-trd/multiplexed-test-drain-outbound branch June 25, 2026 06:29
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