Skip to content

Commit 83fb5ae

Browse files
committed
Fix ReactiveUI scheduler hang in unit batches
ReactiveUI 24's coalescing Avalonia scheduler can retain queued dispatcher work across headless view-model instances. Scope the scheduler to each MainWindowViewModel and run the settings-panel flow test in Avalonia's async test context so CI unit batch 7 completes reliably.
1 parent 56365e8 commit 83fb5ae

2 files changed

Lines changed: 8 additions & 5 deletions

File tree

src/RoyalTerminal.Avalonia.App/ViewModels/MainWindowViewModel.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Reactive;
1111
using System.Reactive.Linq;
1212
using System.Runtime.InteropServices;
13+
using Avalonia.Threading;
1314
using RoyalTerminal.Avalonia.App;
1415
using RoyalTerminal.Avalonia.Controls;
1516
using RoyalTerminal.Avalonia.Rendering;
@@ -58,6 +59,7 @@ public sealed class MainWindowViewModel : ReactiveObject
5859
private TerminalRenderMode _activeRenderMode = TerminalRenderMode.RenderedAuto;
5960
private readonly ITerminalModeResolver _modeResolver;
6061
private readonly ITerminalThemeCatalog _themeCatalog;
62+
private readonly AvaloniaScheduler _uiScheduler;
6163
private readonly IReadOnlyList<TerminalThemePreset> _themePresets;
6264
private readonly bool _showMacOsTitleBarLogos;
6365
private readonly Dictionary<TerminalRenderMode, ModeThemeState> _modeThemes = [];
@@ -213,6 +215,7 @@ internal MainWindowViewModel(
213215
_modeResolver = modeResolver ?? throw new ArgumentNullException(nameof(modeResolver));
214216
_themeCatalog = themeCatalog ?? throw new ArgumentNullException(nameof(themeCatalog));
215217
ArgumentNullException.ThrowIfNull(shellOptions);
218+
_uiScheduler = new AvaloniaScheduler(Dispatcher.UIThread);
216219

217220
_showMacOsTitleBarLogos = shellOptions.ShowMacOsTitleBarLogos;
218221
_themePresets = _themeCatalog.Presets;
@@ -2334,7 +2337,7 @@ private IObservable<Unit> PrepareSettingsPanel()
23342337
{
23352338
return PrepareSettingsPanelInteraction
23362339
.Handle(Unit.Default)
2337-
.ObserveOn(AvaloniaScheduler.Instance)
2340+
.ObserveOn(_uiScheduler)
23382341
.Do(_ => IsSettingsPanelOpen = true);
23392342
}
23402343

tests/RoyalTerminal.Tests/MainWindowViewModelFlowTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,20 +2187,20 @@ public void PaneCommands_RouteSplitFocusAndResizeRequests()
21872187
Assert.Equal(1, closeRequests);
21882188
}
21892189

2190-
[Fact]
2191-
public void SettingsPanel_OpenClose_TogglesOverlayAfterPreparation()
2190+
[AvaloniaFact]
2191+
public async Task SettingsPanel_OpenClose_TogglesOverlayAfterPreparation()
21922192
{
21932193
MainWindowViewModel viewModel = new();
21942194
using IDisposable preparationRegistration = viewModel.PrepareSettingsPanelInteraction.RegisterHandler(context =>
21952195
{
21962196
context.SetOutput(Unit.Default);
21972197
});
21982198

2199-
viewModel.PrepareSettingsPanelCommand.Execute().Wait();
2199+
await viewModel.PrepareSettingsPanelCommand.Execute().ToTask();
22002200

22012201
Assert.True(viewModel.IsSettingsPanelOpen);
22022202

2203-
viewModel.CloseSettingsPanelCommand.Execute().Wait();
2203+
await viewModel.CloseSettingsPanelCommand.Execute().ToTask();
22042204

22052205
Assert.False(viewModel.IsSettingsPanelOpen);
22062206
}

0 commit comments

Comments
 (0)