Concurrent shared compilations can fail releasing the client mutex on Linux
- Dominant language
- C#
- Stars
- 20.7k
- Forks
- 4.3k
- PR merge metrics
- PR metrics pending
Description
## Description
After `Csc` and `Vbc` opted into MSBuild multithreaded task execution in #84701, a `dotnet/sdk` file-based-app test can intermittently fail when `-mt` builds two project references in the same MSBuild process with shared compilation enabled:
```text
Microsoft.CSharp.Core.targets(97,5): error MSB3883: Unexpected exception:
Microsoft.CSharp.Core.targets(97,5): error : Cannot release a lock that is not owned by the current thread.
```
The failure is tracked by [dotnet/sdk#56238](https://github.com/dotnet/sdk/issues/56238). It occurred on Ubuntu 22.04 x64 with .NET 11 SDK `11.0.100-ci`; test history reported a 0.52% failure rate. The [failing Helix log](https://helixr1107v0xdeko0k025g8.blob.core.windows.net/dotnet-sdk-refs-pull-56185-merge-f93c200a568c4267b5/dotnet.Tests.dll.20/1/console.a04b3a4d.log?helixlogtype=result) shows `LibraryB` completing while another compiler invocation fails.
## Distinguishing trigger
The failing test creates a file-based app with two `#:project` references and runs:
```text
dotnet build Program.cs -mt
```
It also sets `MSBUILDUSESERVER=0`, so the MSBuild server is disabled while Roslyn shared compilation remains enabled. In the same 108-test work item, the ordinary file-based builds passed; only `MultiThreadedArgument_BuildsProjectReferencesInProcess(0, -mt)` failed. Its `-mt:false` row passed immediately afterward.
[dotnet/sdk#56239](https://github.com/dotnet/sdk/pull/56239) proposes `UseSharedCompilation=false` for this one test to isolate its MSBuild in-process-node assertion. That is useful test isolation, but it is a mitigation rather than a fix for this failure.
## Relevant implementation and history
`BuildServerConnection.RunServerBuildRequestAsync` serializes server startup with a named client mutex. Its `tryConnectToServerAsync` local function is deliberately non-async so `WaitOne` and `ReleaseMutex` execute on the same thread. However, Roslyn's current tests exercise requests sequentially and do not appear to cover concurrent calls sharing the same pipe/client mutex from one process.
This resembles:
- #63863
- [dotnet/runtime#53420](https://github.com/dotnet/runtime/issues/53420), closed in July after the failure had not been observed following the .NET 11 wait-subsystem transition
- [dotnet/dotnet#4088](https://github.com/dotnet/dotnet/issues/4088), where the same raw exception came from `ServerNamedMutex.Dispose`
The runtime synchronization correction associated with dotnet/dotnet#4088 is present in the runtime used by this failure, so this appears to be a recurrence exposed by the new concurrent compiler-task execution rather than simply a missing backflow.
The current failure also lacks Roslyn's `WaitOne Id` / `Release Id` diagnostic. `tryConnectToServerAsync` catches `ApplicationException` around mutex disposal, while the .NET 11 Unix implementation surfaced this ownership failure as `InvalidOperationException` in dotnet/dotnet#4088.
## Requested investigation
1. Add a Linux regression/stress test that invokes `RunServerBuildRequestAsync` concurrently with the same pipe name, or otherwise runs concurrent shared-compilation `Csc` tasks.
2. Determine whether Roslyn is violating the mutex lifetime contract or whether this is a .NET 11 Unix named-mutex regression; transfer or split the runtime portion if needed.
3. Preserve the acquisition/release thread IDs when this exception occurs on .NET 11 so a future occurrence is actionable.
> [!NOTE]
> This report was prepared with GitHub Copilot assistance from the linked failure logs and source investigation.
Contributor guide
Research direction
Start with BuildServerConnection.RunServerBuildRequestAsync and its tryConnectToServerAsync local function, then reproduce the failure with concurrent calls using the same pipe name or concurrent shared-compilation Csc tasks on Linux. Add a regression or stress test, determine whether the issue belongs to Roslyn or the .NET 11 Unix named-mutex implementation, and preserve acquisition and release thread IDs when the exception occurs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, linux
- Domain
- compilers, operating-systems, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100