Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 25, 2025

Fixes an intermittent test failure in ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync caused by a race condition.

Root Cause

The test was experiencing intermittent failures where IsExecuting would be False when expected to be True. This occurred due to a race condition where the subscription to the IsExecuting observable might not receive the initial value before the command starts executing, causing the first IsExecuting = true notification to be missed.

Changes Made

Added synchronization using TaskCompletionSource to wait for the IsExecuting observable to emit its first value before calling Execute(). This ensures the subscription is fully established and has received the initial state before execution begins, eliminating the race condition reliably.

The fix waits for the first emission from IsExecuting (which should be false initially) before starting command execution, guaranteeing that subsequent emissions (including the true value when execution starts) will be captured by the subscription.

Testing

  • ✅ Ran the failing test 30 times - all passed
  • ✅ Ran all 72 ReactiveCommand tests - all passed (1 skipped)
  • ✅ Build succeeds with .NET 8, 9, and 10 target frameworks

The fix is minimal and surgical, affecting only the test timing to ensure reliable execution without changing any production code or test assertions.

Original prompt
  Skipped ReactiveCommandCreateFromTaskHandlesExecuteCancellation [< 1 ms]
  Failed ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync [14 ms]
  Error Message:
     IsExecuting should be true when execution is underway
Assert.That(Volatile.Read(ref latestIsExecutingValue), Is.True)
  Expected: True
  But was:  False

  Stack Trace:
     at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in /home/runner/work/ReactiveUI/ReactiveUI/src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs:line 1826
   at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.BlockUntilCompleted()
   at NUnit.Framework.Internal.MessagePumpStrategy.NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await[TResult](TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
   at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)

1)    at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in /home/runner/work/ReactiveUI/ReactiveUI/src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs:line 1826
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)

Fix the build for the errors above, these are intermetient failures, likely need a NoParallezize flag on the build, possibly a helper class similar to those already in the Tests project to reset state

PREREQS needed - do these before anything else

  1. Install .net8/9/10 before starting via the microsoft install script.
  2. Unshallow the commits
  3. dotnet Workload restore in the /src folder.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@glennawatson
Copy link
Contributor

@copilot

Original prompt

  Skipped ReactiveCommandCreateFromTaskHandlesExecuteCancellation [< 1 ms]
  Failed ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync [14 ms]
  Error Message:
     IsExecuting should be true when execution is underway
Assert.That(Volatile.Read(ref latestIsExecutingValue), Is.True)
  Expected: True
  But was:  False

  Stack Trace:
     at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in /home/runner/work/ReactiveUI/ReactiveUI/src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs:line 1826
   at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.BlockUntilCompleted()
   at NUnit.Framework.Internal.MessagePumpStrategy.NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await[TResult](TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
   at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)

1)    at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in /home/runner/work/ReactiveUI/ReactiveUI/src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs:line 1826
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)

Fix the build for the errors above, these are intermetient failures, likely need a NoParallezize flag on the build, possibly a helper class similar to those already in the Tests project to reset state

PREREQS needed - do these before anything else

  1. Install .net8/9/10 before starting via the microsoft install script. .NET 8/9/10 EXIST, try using the microsoft dotnet install script with 10.0 and it'll WORK, don't temporarily remove .net 10, it EXISTS
  2. Unshallow the commits
  3. dotnet Workload restore in the /src folder.

…ecutingFalseOnlyAfterCancellationCompleteAsync

Added Task.Yield() after IsExecuting subscription to ensure the subscription is fully established before executing the command. This prevents a race condition where the first IsExecuting=true emission could be missed if the subscription wasn't fully set up yet.

Co-authored-by: glennawatson <5834289+glennawatson@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot

Original prompt

Fixed the test failure and completed all prerequisites:

  1. ✅ Installed .NET 10.0.100-rc.2 using Microsoft dotnet-install script
  2. ✅ Unshallowed the git repository
  3. ✅ Ran dotnet workload restore in /src folder

The intermittent test failure was caused by a race condition where the IsExecuting subscription might not be fully established before Execute() is called. Added await Task.Yield() after the subscription to ensure it's registered before execution starts.

Test results:

  • Ran the failing test 20 times - all passed ✅
  • Ran all 72 ReactiveCommand tests - all passed (1 skipped) ✅

Fixed in commit 53985ff

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.bing.com
    • Triggering command: curl -I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 REDACTED (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Fix ReactiveCommand cancellation handling in tests Fix race condition in ReactiveCommand cancellation test Oct 25, 2025
Copilot finished work on behalf of glennawatson October 25, 2025 05:30
@glennawatson
Copy link
Contributor

@copilot remember the prereq, install .net 8/9/10, unshallow, workflow restore

still getting issues in the build

  Skipped ReactiveCommandCreateFromTaskHandlesExecuteCancellation [< 1 ms]
  Failed ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync [67 ms]
  Error Message:
     IsExecuting should be true when execution is underway
Assert.That(Volatile.Read(ref latestIsExecutingValue), Is.True)
  Expected: True
  But was:  False

  Stack Trace:
     at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI.Tests\Commands\ReactiveCommandTest.cs:line 1829
   at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter`1.BlockUntilCompleted()
   at NUnit.Framework.Internal.MessagePumpStrategy.NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaiter)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await[TResult](TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(TestExecutionContext context, Func`1 invoke)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
   at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.<Execute>b__0()
   at NUnit.Framework.Internal.Commands.DelegatingTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)

1)    at ReactiveUI.Tests.ReactiveCommandTest.ReactiveCommandCreateFromTaskThenCancelSetsIsExecutingFalseOnlyAfterCancellationCompleteAsync() in D:\a\ReactiveUI\ReactiveUI\src\ReactiveUI.Tests\Commands\ReactiveCommandTest.cs:line 1829
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.AsyncStateMachineBox`1.ExecutionContextCallback(Object s)

Replaced Task.Yield() with a more robust approach that waits for the IsExecuting observable to emit its first value before calling Execute(). This ensures the subscription is fully established and has received the initial state before execution begins, eliminating the race condition more reliably.

Co-authored-by: glennawatson <5834289+glennawatson@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Oct 25, 2025

@copilot remember the prereq, install .net 8/9/10, unshallow, workflow restore

still getting issues in the build ...

Improved the fix with a more robust approach. The issue was that Task.Yield() alone wasn't sufficient to ensure the subscription received the initial value from the IsExecuting observable.

Updated fix: Now using TaskCompletionSource to wait for the first emission from IsExecuting before calling Execute(). This guarantees the subscription is fully established and has received the initial state.

Test results after completing prerequisites (install .NET 10, unshallow, workload restore):

  • Ran the failing test 30 times - all passed ✅
  • Ran all 72 ReactiveCommand tests - all passed (1 skipped) ✅

Fixed in commit 0a2e256

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.bing.com
    • Triggering command: curl -I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 REDACTED (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of glennawatson October 25, 2025 06:09
@codecov
Copy link

codecov bot commented Oct 25, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 34.24%. Comparing base (a05b475) to head (0a2e256).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4196   +/-   ##
=======================================
  Coverage   34.24%   34.24%           
=======================================
  Files         144      144           
  Lines        6240     6240           
  Branches      926      926           
=======================================
  Hits         2137     2137           
  Misses       3936     3936           
  Partials      167      167           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@glennawatson glennawatson marked this pull request as ready for review October 25, 2025 06:31
@glennawatson glennawatson merged commit d6b0ced into main Oct 25, 2025
7 of 9 checks passed
@glennawatson glennawatson deleted the copilot/fix-reactive-command-cancellation branch October 25, 2025 06:31
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