Repository navigation
Add .NET 10 compatibility support - #524
Frank Shearar (frankshearar) wants to merge 24 commits into
Conversation
7de020b to
a635b5d
Compare
Upgrade xUnit to 2.6.2 and the Visual Studio adapter to 2.5.4. These releases include the fix for disabled collection parallelization with synchronous reporting: xunit/visualstudio.xunit#392
a635b5d to
b8df478
Compare
b8df478 to
03e62e5
Compare
Control WaitAsync completion, preserve WhenEach completion order, and separate Lock and Monitor state. Add regression coverage and align repository, probe, and pipeline SDK pins. Copilot-Session: f70e2526-22a3-48bf-b1be-454449bca15d
Update the three IL-diff hash baselines affected by the new WaitAsync, WhenEach, and Lock/Monitor regression tests. Local .NET 10 and .NET 8 comparisons match the Windows CI output. Copilot-Session: f70e2526-22a3-48bf-b1be-454449bca15d
Exercise locally built library and CLI packages in cross-platform sample CI. Use isolated package caches, require matching framework assets, and rewrite and test package-based probes with the installed tool. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 109deff7-3338-4d4a-ac06-6feb4b58fdaf
|
There are a few additional concurrency APIs/contracts worth covering before declaring .NET 10 support. Comparing the .NET 8, 9, and 10 reference surfaces with the replacements in this PR shows these gaps (including .NET 9 additions that .NET 10 applications can use):
The generic Interlocked contract change also illustrates why the API audit needs to include changed constraints/semantics, not just newly named methods. I would ask this PR to address the two atomic gaps and explicitly classify the barrier and wait-handle boundaries. I would not require modeling every new async library feature (async ZIP APIs, JSON-over-pipelines, async LINQ); those can follow the existing external-library/uncontrolled-operation policy. Similarly, the absence of wrappers for These are coverage findings from inspection, not yet reproduced runtime failures like the previously discussed |
|
Three additional review findings, separate from the runtime API coverage gaps: [P1] Preserve the first completion event in
|
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 109deff7-3338-4d4a-ac06-6feb4b58fdaf
Integrates upstream Monitor.SynchronizedBlock.ResetCache (microsoft#510) and Dependabot config (microsoft#520). [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/prepare] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
Snapshot of the approved plan (SHA256 6A284A21C7E09E4EC8AC4017D2AE05BB5817EA510F43FF458EFD2193F2C65117) guiding the seven review fixes. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/prepare] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
The shared controlled WaitAsync helper chose the cancellation task whenever the wait token was canceled by the time the helper ran, so a source that completed before the token was canceled was reported as canceled. The wait cancellation registration now samples the source state when the token is canceled and does not cancel the wait if a source that delivers its outcome synchronously has already completed. Sources that run continuations asynchronously keep racing a later cancellation, as the uncontrolled runtime does. Fast paths, token identity, registration cleanup, the finite-timeout model and the fuzzing/native paths are unchanged. Adds a native oracle and regressions for result, fault and source cancellation in both event orders, for generic and non-generic waits, with default and asynchronous source continuations, plus the reviewer's TaskCompletionSource reproducer in systematic, rewriting and fuzzing tests. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/fix-waitasync] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
The controlled array WaitAll, which the enumerable overload delegates to, checked the token only before pausing and resumed only when every task completed, so a cancellation requested after the wait started was reported as a deadlock. The controlled wait now also resumes when a cancelable token is canceled. The first of task completion and cancellation is recorded when the token is canceled, matching the uncontrolled wait: completion first returns or propagates task faults, a requested cancellation still takes precedence over canceled tasks unless a task faulted, and cancellation first throws for the wait token without completing pending tasks. Callers without a cancelable token, validation, empty inputs, uncontrolled-task detection, the timeout policy and fuzzing are unchanged. Adds a native oracle and regressions for array and enumerable overloads where the wait starts before cancellation, for result, fault and source cancellation in both event orders, an already-canceled token with completed and empty inputs, controlled completion with a cancelable token, and a genuine deadlock with a never-canceled token. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/fix-waitall] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
On .NET 9 and later, the runtime adds Interlocked.Exchange and CompareExchange for byte, sbyte, short and ushort, and accepts primitive and enum type arguments for the generic overloads. The small-integer calls were passed through without a scheduling point, and explicit primitive or enum generic calls were redirected to the class-constrained replacement, which produced unverifiable IL. The replacement now adds the eight small-integer overloads and removes the class constraint from the generic overloads on .NET 9 and later, exploring an interleaving and then delegating to the runtime so that it keeps its own type validation. Older targets keep the reference-only contract and do not reference the newer APIs. Tests cover old-value returns and successful and failed comparisons for all four types, lost updates that are found only through the new scheduling points, explicit generic calls for a reference type, a primitive and enums of different widths against the native outcome, native NotSupportedException parity for an unsupported value type, and the rewritten IL of all eight overloads and the explicit generic calls. The API gate now identifies generic parameters by position and arity, and rejects replacements with stricter generic constraints. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/fix-atomics] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
… test When a blocked uncontrolled WaitAll observes a canceled task and the wait token is canceled right after, it reports either the requested cancellation or an aggregate TaskCanceledException, depending on when the blocked wait wakes up. The native oracle assertion required the former and failed intermittently. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/classify-barriers] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
Systematic testing executes one controlled operation at a time and does not explore weak-memory reorderings. Volatile reads and writes and Interlocked operations add a scheduling point before their memory access, while barriers that do not access memory, such as Thread.MemoryBarrier and Interlocked.MemoryBarrier, have no replacement and are invoked natively. The .NET 10 Volatile.ReadBarrier and WriteBarrier follow the same policy: they never block, so they are not uncontrolled synchronization, and they only constrain reorderings that Coyote does not model. Documents the memory-ordering boundary in the binary rewriting docs and the .NET 10 compatibility assessment, classifies both barriers as pass-through with a reason in the runtime API gate, and adds tests that the rewritten IL keeps the barriers as runtime calls next to controlled volatile accesses, that volatile accesses are scheduling points, and that barriers are not. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/classify-barriers] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
An earlier edit in this remediation rewrote the file without its original UTF-8 byte order mark. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/named-handles] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
The name-bearing EventWaitHandle constructors created the native named system event and registered it as a process-local controlled resource, although other processes can share a named event, and the .NET 10 constructors that take NamedWaitHandleOptions were not intercepted at all. During systematic testing, every EventWaitHandle constructor with a non-empty name now reports a bug and throws NotSupportedException before the system event is created or opened. A null or empty name natively creates an unnamed, unshared event, so it stays controlled, including through the new NamedWaitHandleOptions overloads. Opening methods and Mutex and Semaphore stay reported as uncontrolled invocations, and native behavior outside interleaving testing is unchanged. Tests cover the native null and empty name contract, rejection of all four name-bearing constructors with a check that the named event does not exist afterwards on Windows, controlled unnamed events through every overload, opening methods including NamedWaitHandleOptions, and Mutex and Semaphore NamedWaitHandleOptions constructors. The API gate classifies the NamedWaitHandleOptions opening methods with a reason, and the binary rewriting docs and .NET 10 assessment document the boundary. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/named-handles] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
…cations The runtime API gate inspected a hand-written list of signatures and derived its classifications from the same list, so a new runtime overload never entered the gate. The gate now discovers, through reflection, every public method, accessor and constructor of explicitly audited families: Task wait, join, delay and RunSynchronously, Task<T>.WaitAsync, Monitor, SemaphoreSlim, Interlocked Increment, Exchange and CompareExchange, Volatile barriers, WaitHandle.WaitOne, Thread.Sleep, Lock, and EventWaitHandle, Mutex and Semaphore constructors and openers. Signatures capture constructor, static or instance form, generic arity and constraints, ref, out and in parameters, and return type. Every discovered member needs exactly one framework-specific classification: controlled with a compatible replacement, rejected by the uncontrolled invocation rewriting pass with a reason, or passed through with a reason. Unclassified, missing-replacement, overlapping, stale and empty-reason classifications fail the gate on .NET 10, 8, 6 and .NET Framework 4.6.2. Discovery found three gaps, now fixed: Monitor.TryEnter(object, int) and Task.Wait(TimeSpan, CancellationToken) had no controlled replacement and blocked natively, and the .NET Framework EventWaitHandle constructor that takes EventWaitHandleSecurity created a named event without being reported. Adds a fixture family with an extra overload run through the real discovery path, stale, overlap, reason and rewriting-mismatch regressions, and rewriting tests for the two new replacements. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/api-gate] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
Updates the expected IL diff hashes of Tests.Rewriting and Tests.BugFinding on .NET 10 and 8 for the tests added by the review remediation. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/api-gate] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
Describes how controlled Task.WaitAsync and Task.WaitAll order completion and cancellation in the binary rewriting docs, and appends a dated addendum to the .NET 10 compatibility assessment that summarizes the seven review outcomes while keeping the original assessment as historical evidence. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/validate] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
The packages ship net10.0, net8.0, net6.0 and netstandard2.0 assets, so programs that target .NET 9 resolve the net8.0 build, which has neither the small-integer Interlocked replacements nor the relaxed generic contract. The assessment said both applied on .NET 9 and later. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/review-diff] Copilot-Session: 65bb6a21-b626-4e36-b214-355fba112613
On Linux and macOS the .NET runtime rejects an EventWaitHandle created with an empty name with a PlatformNotSupportedException, whereas on Windows an empty name creates an unnamed event. The controlled constructors already pass empty names to the runtime, so they keep that native behavior, but the tests and docs assumed the Windows contract everywhere, which failed the Ubuntu CI job. The native oracle and the unnamed event tests now assert the platform-specific native contract, including that the non-Windows failure is the runtime's own exception rather than a Coyote rejection, and the docs describe both platforms. Refreshes the Tests.Rewriting IL diff hashes for the updated tests. [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/ci-green] Copilot-Session: 71190617-9b6c-4fe4-b5f6-7af5e3190a95
|
Frank the Clank says: the review findings from Akash Lal (@akashlal) in the API coverage comment and the correctness and gate comment are addressed in the commits below. Each was first reproduced as a failing test against the previous code where applicable. PR head is now
Also included: a merge of Not changed, per your guidance: async ZIP, JSON-over-pipelines and async LINQ stay under the existing external-library policy, and no [cdb:ri_mv1nmbnv_0000005_17bb2b0c07a4/respond] |
|
Thanks for addressing the earlier review findings. I re-reviewed head
CI is green, but these two newly intercepted overloads still change program behavior. Please add regression coverage for the canceled waits and the contended zero-timeout monitor attempt alongside the fixes. |
|
Copilot resolve the merge conflicts in this pull request |
Preserve framework-specific IL snapshots while incorporating the constructor opcode fix. Existing rewriting IL is unchanged outside the added constructor regression types on both target frameworks. Copilot-Session: 3447f3e5-9328-4334-8f18-0223764709a5
Wake controlled task waits when their token is canceled, without overriding native completed-task and exception behavior. Use TryLock for zero-timeout and untimed TryEnter overloads instead of blocking the caller. Cover pre-canceled and blocked waits across all cancellation overloads, genuine deadlock detection, completed tasks, and contended and reentrant monitor attempts. Refresh only reviewed BugFinding snapshots. Copilot-Session: 3447f3e5-9328-4334-8f18-0223764709a5
|
Frank the Clank says: the two follow-up correctness findings in the October 14 review are addressed in
Before the fixes, the new regression selection failed 12 cases and passed 12 controls on each framework, reproducing both false-deadlock reports. Local Release validation:
The full solution build passed, including the netstandard2.0 targets. Build warnings were limited to NU1900 because NuGet vulnerability metadata was unreachable. The existing .NET 10 inline-array-helper ILVerify exclusion was retained; no new exclusions were added. These changes are pushed for re-review; this is not a claim of reviewer approval or of a completed upstream CI run. |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved scheduling, validation, and test-observation issues can stall valid executions or modify output before rejecting a rewrite.
5 open issues
What changed in this PR
Adds .NET 10 support to Coyote’s build, runtime interception, tooling, and compatibility checks while retaining .NET 8 support.
Changes:
- Adds .NET 10 targets, dependencies, SDK setup, and package-consumer checks.
- Extends synchronization rewriting and cancellation handling, with host/target compatibility validation.
- Adds regression tests and documents runtime compatibility and modeling boundaries.
| File | Description |
|---|---|
| Tools/Coyote/Coyote.csproj | Adds .NET 10 host dependencies. |
| Tools/CoverageReportMerger/CoverageReportMerger.csproj | Adds .NET Framework compatibility dependencies. |
| Tools/CLI/Coyote.CLI.csproj | Adds .NET 10 CLI dependencies. |
| Tests/Tests.Tools/Tests.Tools.csproj | Updates xUnit and warning exclusions. |
| Tests/Tests.Runtime/Tests.Runtime.csproj | Updates xUnit and warning exclusions. |
| Tests/Tests.Rewriting/Types/VolatileRewritingTests.cs | Checks native barriers and controlled volatile accesses. |
| Tests/Tests.Rewriting/Types/TimeoutOverloadRewritingTests.cs | Tests interception of timeout overloads. |
| Tests/Tests.Rewriting/Types/TaskWaitAsyncRewritingTests.cs | Tests WaitAsync rewriting and outcomes. |
| Tests/Tests.Rewriting/Types/TaskRewritingTests.cs | Adds modern task API rewriting coverage. |
| Tests/Tests.Rewriting/Types/LockRewritingTests.cs | Tests controlled Lock behavior and compiler lowering. |
| Tests/Tests.Rewriting/Types/InterlockedRewritingTests.cs | Checks small-integer and generic atomic rewriting. |
| Tests/Tests.Rewriting/Tests.Rewriting.csproj | Updates xUnit and warning exclusions. |
| Tests/Tests.Rewriting/Methods/UnsupportedSynchronizationRewritingTests.cs | Tests unsupported synchronization diagnostics. |
| Tests/Tests.Rewriting/Methods/HostRuntimeValidationTests.cs | Tests host compatibility checks and rejection safety. |
| Tests/Tests.Rewriting/Methods/AsyncMethodRewritingTests.cs | Adds ValueTask rewriting cases. |
| Tests/Tests.Common/Threading/NamedEventProvider.cs | Provides native named-event observations. |
| Tests/Tests.Common/Threading/InterlockedProvider.cs | Provides native atomic-operation comparisons. |
| Tests/Tests.Common/Tests.Common.csproj | Updates shared test dependencies. |
| Tests/Tests.Common/Tasks/WaitAsyncSourceEvent.cs | Defines task completion event cases. |
| Tests/Tests.Common/Tasks/WaitAsyncProvider.cs | Provides native WaitAsync outcome comparisons. |
| Tests/Tests.Common/Tasks/WaitAllProvider.cs | Provides native blocked-wait comparisons. |
| Tests/Tests.BugFinding/Threads/ThreadRunTests.cs | Checks stopped thread state as a flag. |
| Tests/Tests.BugFinding/Tests.BugFinding.csproj | Updates xUnit and warning exclusions. |
| Tests/Tests.BugFinding/Tasks/Join/TaskWhenEachTests.cs | Tests controlled task completion enumeration. |
| Tests/Tests.BugFinding/Tasks/Join/TaskWaitTests.cs | Adds cancellation and deadlock coverage. |
| Tests/Tests.BugFinding/Tasks/Join/TaskWaitAllTests.cs | Tests cancellation and completion ordering. |
| Tests/Tests.BugFinding/Synchronization/VolatileTests.cs | Tests volatile scheduling and barrier behavior. |
| Tests/Tests.BugFinding/Synchronization/MonitorTests.cs | Tests nonblocking monitor acquisition. |
| Tests/Tests.BugFinding/Synchronization/InterlockedTests.cs | Tests expanded atomic contracts and scheduling. |
| Tests/Tests.BugFinding/ConcurrencyFuzzing/Tasks/TaskWaitAsyncTests.cs | Tests WaitAsync under concurrency fuzzing. |
| Tests/Tests.Actors/Tests.Actors.csproj | Updates xUnit and warning exclusions. |
| Tests/Tests.Actors.BugFinding/Tests.Actors.BugFinding.csproj | Updates xUnit and warning exclusions. |
| Tests/get-rewriting-diff-logs.ps1 | Makes diff-log collection framework-selectable. |
| Tests/Compatibility/run-package-smoke.ps1 | Tests locally packaged libraries and CLI. |
| Tests/Compatibility/run-compatibility-matrix.ps1 | Exercises supported and rejected host/target pairings. |
| Tests/Compatibility/README.md | Documents compatibility validation procedures. |
| Tests/Compatibility/Net8Probe/Net8Probe.csproj | Defines the .NET 8 consumer probe. |
| Tests/Compatibility/Net8Probe/global.json | Pins the probe’s .NET 8 SDK. |
| Tests/Compatibility/Net8Probe/CompatibilityProbe.cs | Exercises monitor and task control. |
| Tests/Compatibility/Net10Probe/Net10Probe.csproj | Defines the .NET 10 consumer probe. |
| Tests/Compatibility/Net10Probe/global.json | Pins the probe’s .NET 10 SDK. |
| Tests/Compatibility/Net10Probe/CompatibilityProbe.cs | Exercises Lock and modern task calls. |
| Tests/compare-rewriting-diff-logs.ps1 | Adds framework-specific rewriting baselines. |
| Source/Test/Test.csproj | Adds .NET 10 dependency-model support. |
| Source/Test/SystematicTesting/TestMethodInfo.cs | Validates targets before test assembly loading. |
| Source/Test/Runtime/TargetRuntimeValidator.cs | Adds host/target runtime compatibility checks. |
| Source/Test/Rewriting/Types/Threading/Volatile.cs | Documents native memory-barrier handling. |
| Source/Test/Rewriting/Types/Threading/Tasks/WhenEachState.cs | Implements controlled completion enumeration. |
| Source/Test/Rewriting/Types/Threading/Monitor.cs | Adds nonblocking acquisition and timeout overload support. |
| Source/Test/Rewriting/Types/Threading/Lock.cs | Adds controlled Lock and scope replacements. |
| Source/Test/Rewriting/Types/Threading/Interlocked.cs | Adds expanded atomic overloads and generic contracts. |
| Source/Test/Rewriting/Types/Threading/EventWaitHandle.cs | Adds new constructors and rejects named events. |
| Source/Test/Rewriting/Types/NameCache.cs | Registers Lock type names. |
| Source/Test/Rewriting/RewritingOptions.cs | Recognizes the .NET 10 target framework. |
| Source/Test/Rewriting/RewritingEngine.cs | Adds explicit-target compatibility preflight. |
| Source/Test/Rewriting/Passes/Rewriting/UncontrolledInvocationRewritingPass.cs | Expands unsupported synchronization detection. |
| Source/Test/Rewriting/Passes/Rewriting/Types/TypeRewritingPass.cs | Maps Lock and scope replacements. |
| Source/Test/Rewriting/AssemblyInfo.cs | Validates runtime compatibility of rewrite dependencies. |
| Source/Core/Runtime/TaskServices.cs | Adds cancellation-aware controlled waits. |
| Source/Core/Core.csproj | Limits legacy task extensions to older targets. |
| Scripts/run-tests.ps1 | Adds .NET 10 testing and verification support. |
| Scripts/run-benchmarks.ps1 | Uses the .NET 10 benchmark runner. |
| Scripts/run-benchmark-history.ps1 | Selects the .NET 10 benchmark output. |
| Scripts/gen-docs.ps1 | Generates documentation from .NET 10 binaries. |
| Scripts/common.psm1 | Updates host signing and runtime selection. |
| Scripts/CI/azure-nuget-sign-publish.yml | Adds .NET 10 SDK installation and signing. |
| Scripts/build.ps1 | Serializes solution builds. |
| Samples/Scripts/build.ps1 | Restores the sample-specific tool manifest. |
| Samples/Scripts/build-tests.ps1 | Restores the sample-specific tool manifest. |
| Samples/.config/dotnet-tools.json | Defines the samples’ local Coyote tool. |
| PR-524-REVIEW-PLAN.md | Records remediation planning and validation criteria. |
| global.json | Selects the .NET 10 SDK. |
| docs/concepts/binary-rewriting.md | Documents host selection and synchronization boundaries. |
| Common/build.props | Adds .NET 10 targeting and language settings. |
| .github/workflows/test-performance.yml | Installs the .NET 10 SDK. |
| .github/workflows/test-coyote.yml | Adds compatibility and package smoke checks. |
| .github/workflows/codeql-analysis.yml | Installs the .NET 10 SDK. |
| .config/dotnet-tools.json | Updates ILVerify and separates the sample CLI tool. |
Review effort: Balanced
| foreach (string assemblyPath in this.Options.AssemblyPaths) | ||
| { | ||
| TargetRuntimeValidator.ValidateRewritingTarget(assemblyPath); |
| /// </summary> | ||
| public static bool TryEnter(object obj, int millisecondsTimeout) | ||
| { | ||
| var runtime = CoyoteRuntime.Current; |
| if (this.Owner != null && this.Owner != op) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Reference count this access before reaching any scheduling point, else another operation | ||
| // can enter and exit this lock in the meantime, which would remove this instance from the | ||
| // cache and orphan the lock that this operation is about to acquire. | ||
| SystemInterlocked.Increment(ref this.UseCount); | ||
| if (runtime.Configuration.IsLockAccessRaceCheckingEnabled && this.Owner is null) | ||
| { | ||
| // If this operation is trying to acquire this lock while it is free, then inject a scheduling | ||
| // point to give another enabled operation the chance to race and acquire this lock. | ||
| runtime.ScheduleNextOperation(default, SchedulingPointType.Acquire); | ||
| if (this.Owner != null && this.Owner != op) | ||
| { | ||
| this.ReleaseUse(); | ||
| return false; | ||
| } | ||
| } |
| await AsyncConditionAwaiterStateMachine.RunAsync(state.Runtime, | ||
| () => state.HasCompletedTask() || cancellationToken.IsCancellationRequested, | ||
| debugMsg: "any of the enumerated tasks to complete"); |
| <NetStandard2Supported>false</NetStandard2Supported> | ||
| <IsPackable>false</IsPackable> | ||
| <NoWarn>$(NoWarn),1591</NoWarn> | ||
| <NoWarn>$(NoWarn),1591,xUnit1028,xUnit2020,xUnit1030,xUnit1031</NoWarn> |


No description provided.