Commit 67142a6
committed
fix(config): migrate Channels TUI config writes to async, fixing macOS test deadlock
The Channels config viewmodel bridged async work (label-refresh cancel-and-await,
save, autosave, add-channel) to Termina's synchronous key handlers via
`.GetAwaiter().GetResult()`. That bridge is only safe when no SynchronizationContext
is captured — true in the real Termina loop, but NOT under the xunit v3 test runner,
which installs a MaxConcurrencySyncContext sized to the core count. On macOS CI's
smaller worker pool the blocked `.GetResult()` held the only free worker while the
cancelled probe's continuation was posted back to that same context — a
sync-over-async deadlock. `dotnet test` hung ~30 min and was killed by the job
timeout; it passed on higher-core Linux/Windows runners.
Remove all four sync-over-async bridges in ChannelsConfigViewModel:
- Save/ApplyAddChannel/ApplyResetConfirmation are now async; reset and add are
dispatched fire-and-forget from the page key handlers.
- Autosave handlers enqueue the persist instead of blocking; the on-loop state
mutation is unchanged.
- Add one explicit serialization point (PendingConfigWrite) to preserve the write
ordering the loop-block provided for free, so two rapid mutations cannot race the
disk write + state reload. It runs synchronously inline in the common case.
- ApplyAddChannelAsync settles screen + row focus synchronously before the async
persist so a subsequent keypress navigates from a deterministic position.
Add a deterministic regression test (SingleThreadSynchronizationContext) that drives
the reset-with-in-flight-refresh scenario under a single-worker context: it deadlocks
the old blocking code (watchdog trips) and passes the async path.1 parent 5f72db6 commit 67142a6
4 files changed
Lines changed: 285 additions & 107 deletions
File tree
- src
- Netclaw.Cli.Tests/Tui
- Config
- Netclaw.Cli/Tui/Config
0 commit comments