[release/11.0] Disable file locking by default on iOS and tvOS - #134331
Conversation
## Summary Disables advisory file locking by default on iOS and tvOS (device and simulator), while leaving Mac Catalyst — and every other platform — unchanged. The behavior remains fully configurable: setting `System.IO.DisableFileLocking=false` (or `DOTNET_SYSTEM_IO_DISABLEFILELOCKING=0`) restores locking on those platforms. ## Motivation On iOS and tvOS, RunningBoard terminates a suspended app with exception code `0xdead10cc` when the app still holds a file lock on a file outside its data container. .NET acquires such a lock implicitly: every `FileStream` opened on Unix takes an advisory `flock` to emulate Windows `FileShare` semantics, and a `FileShare.Read` open maps to a shared `LOCK_SH`. A common way to hit this is stack trace symbolication: when the runtime opens a PDB from inside the read-only app bundle to produce line numbers for an exception, the resulting shared lock lives for as long as the handle does. If the user backgrounds the app while that handle is open, the system kills the process. The lock provides no real value here — the app bundle is read-only and the lock is only advisory between .NET processes — but the termination is fatal and hard to diagnose. ## Implementation `SafeFileHandle.DisableFileLocking` on Unix already consults the `System.IO.DisableFileLocking` / `DOTNET_SYSTEM_IO_DISABLEFILELOCKING` config switch; this change only alters the `defaultValue` passed to that lookup, so it becomes `true` on iOS and tvOS: ```csharp defaultValue: (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || OperatingSystem.IsTvOS() ``` Notes on the predicate: - `OperatingSystem.IsIOS()` returns `true` for Mac Catalyst as well, so Mac Catalyst is explicitly excluded — it runs on macOS, is not subject to RunningBoard suspension policy, and keeps its current locking behavior. - iOS and tvOS simulators report as iOS/tvOS, so they follow the device default. That keeps simulator test runs representative of device behavior rather than silently diverging. - Browser and WASI continue to force locking off unconditionally, as before; they are unaffected by this change. Because this goes through `AppContextConfigHelper`, an explicit `false` still wins over the new default, so anyone depending on `FileShare` enforcement on iOS/tvOS can opt back in. The observable consequence is that on iOS and tvOS, `FileShare` is no longer enforced between processes or handles: opening a file with `FileShare.None` no longer prevents a second open, and the `IOException` that previously signalled a sharing violation is no longer thrown. This matches the behavior that Browser and WASI have had for some time. ## Test changes Add a small iOS/tvOS-specific test asserting that file locking is in fact off by default, so a future regression in the predicate is caught directly rather than only as a cascade of sharing-violation test failures. Most lock-dependent tests in `System.IO.FileSystem` were already conditioned on `PlatformDetection.IsFileLockingEnabled`, which reflects the runtime's `DisableFileLocking` value and therefore picks up the new default automatically. The second commit extends that same pattern to the remaining tests that depend on `FileShare` being enforced: | Area | Change | |-------------------------------------|---------------------------------------------------------------------------------------------| | `System.Reflection` | `GetAssemblyName_LockedFile` now uses `ConditionalFact` on `IsFileLockingEnabled` | | `System.IO.IsolatedStorage` | `OpenFile_PassesFileShare` now uses `ConditionalFact` on `IsFileLockingEnabled` | | `System.IO.MemoryMappedFiles` | `FileInUse_CreateFromFile_FailsWithExistingNoShareFile` now uses `ConditionalFact` | | `System.ComponentModel.Composition` | The four `ConstructorN_LockedFileAsCodeBaseArgument_ShouldThrowFileLoad` tests use `ConditionalFact` | In two of these cases the new condition subsumes an existing `[SkipOnPlatform(TestPlatforms.Browser, ...)]` attribute — Browser was skipped precisely because it does not honor file locking — so the attribute is removed in favor of the more general check. ## Validation Built and ran locally on macOS: - `./build.sh clr+libs -rc checked` - `./build.sh clr.corelib+clr.nativecorelib+libs.pretest -rc checked` Test results: | Suite | Total | Failed | |------------------------------------------------|-------|--------| | `System.IO.FileSystem.Tests` | 9,607 | 0 | | `System.IO.FileSystem.Tests` (locking disabled) | 2,095 | 0 | | `System.Reflection` tests | 1,778 | 0 | | `System.IO.IsolatedStorage` tests | 198 | 0 | | `System.IO.MemoryMappedFiles` tests | 937 | 0 | | `System.ComponentModel.Composition` tests | 1,431 | 0 | The "locking disabled" run exercises the new code path by forcing `DisableFileLocking` on, which is the configuration iOS and tvOS will now get by default. Fixes #133697. > [!NOTE] > This pull request description was generated with the assistance of GitHub Copilot. --------- Co-authored-by: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run runtime-extra-platforms |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
Tagging subscribers to this area: @dotnet/area-system-io |
|
None of the observed CI failures are related to PR #134331. The failures occur in unrelated networking, browser-WASM numeric, Android compiler, culture/DNS, Apple test-runner, and runtime-test infrastructure paths. MacCatalyst behavior is explicitly unchanged by the PR. Build Analysis matched several failures to known issues:
Additional failures match or resemble pre-existing reports, including #132059, The remaining failures also have unrelated or infrastructure-specific causes:
The PR should not be blocked or modified because of this CI run. Evidence: |
|
CI analysis:
|
|
/ba-g per previous comment, all failures are unrelated. |
Backport of #134060 to release/11.0
/cc @vitek-karas @rolfbjarne
Customer Impact
Application on iOS which happens to take a lock on a file can be killed by the OS if it goes to background. For details look at the customer reported issue: #133697
Regression
This worked on mono, since mono actually didn't lock files in the file system on iOS (effectively what this PR does)
Testing
Manual validation of the customer scenario. Full CI run on extra platforms.
Risk
Low - there will be one difference from mono behavior. mono implemented locking for files from the same process (and from .NET), with this change CoreCLR doesn't do that. This is considered very unlikely to be relied upon by customers. We will document this change as a breaking change for .NET 11.
IMPORTANT: If this backport is for a servicing release, please verify that:
release/X.0-staging, notrelease/X.0.release/X.0(no-stagingsuffix).Package authoring no longer needed in .NET 9
IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.