[Foundation] Improve potential reentrancy problems in NSUrlSessionHandler.#25183
[Foundation] Improve potential reentrancy problems in NSUrlSessionHandler.#25183rolfbjarne merged 4 commits intomainfrom
Conversation
NET10_0_OR_GREATER is always set now, so remove other code paths.
…dler. C# locks are re-entrant, so try to avoid re-entrancy issues by: * In `RemoveInflightData`: fetch and remove the inflight data in a single `Dictionary.Remove` call. This is also faster than doing two dictionary lookups. * In `Dispose`: collect all tasks to cancel and dispose first (inside the lock), and then we cancel and dispose after the lock. This hopefully fixes #20345 (this solution was tested by a customer and apparently worked). Might fix #20345.
There was a problem hiding this comment.
Pull request overview
This PR updates NSUrlSessionHandler to reduce re-entrancy hazards during inflight request cleanup and handler disposal, aiming to improve stability in long-running/high-volume networking scenarios (potentially addressing #20345).
Changes:
- Simplifies inflight request removal by using
Dictionary.Remove(key, out value)to fetch+remove in one operation. - Refactors
Dispose(bool)to snapshot inflight tasks under lock, clear the dictionary, then cancel/dispose tasks outside the lock to avoid re-entrant lock usage.
| var tasks = new List<NSUrlSessionTask> (); | ||
| lock (inflightRequestsLock) { | ||
| foreach (var pair in inflightRequests) { | ||
| pair.Key?.Cancel (); | ||
| pair.Key?.Dispose (); | ||
| } | ||
|
|
||
| tasks.AddRange (inflightRequests.Keys); | ||
| inflightRequests.Clear (); | ||
| } | ||
| foreach (var task in tasks) { | ||
| task.Cancel (); | ||
| task.Dispose (); | ||
| } |
There was a problem hiding this comment.
In Dispose, inflightRequests.Clear () happens before canceling tasks, so any subsequent DidCompleteWithError / DidReceiveData delegate callbacks won’t be able to retrieve the corresponding InflightData from inflightRequests. Since InflightData.CompletionSource is only completed when GetInflightData finds an entry, this can leave callers awaiting SendAsync permanently blocked if the handler is disposed while requests are in-flight. Consider capturing the InflightData instances (not just the tasks) while holding the lock and explicitly completing/canceling their CompletionSource (and canceling InflightData.CancellationTokenSource / faulting the stream) as part of Dispose, instead of relying on delegate callbacks after the dictionary is cleared.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…-safer-dispose-20345
This comment has been minimized.
This comment has been minimized.
✅ [CI Build #9764620] Build passed (Build packages) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #9764620] Build passed (Detect API changes) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [CI Build #9764620] Build passed (Build macOS tests) ✅Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
🔥 [CI Build #9764620] Test results 🔥Test results❌ Tests failed on VSTS: test results 0 tests crashed, 1 tests failed, 155 tests passed. Failures❌ Tests on macOS Monterey (12) tests1 tests failed, 4 tests passed.Failed tests
Html Report (VSDrops) Download Successes✅ cecil: All 1 tests passed. Html Report (VSDrops) Download macOS tests✅ Tests on macOS Ventura (13): All 5 tests passed. [attempt 2] Html Report (VSDrops) Download Linux Build VerificationPipeline on Agent |
C# locks are re-entrant, so try to avoid re-entrancy issues by:
RemoveInflightData: fetch and remove the inflight data in a singleDictionary.Removecall. This is also faster than doing two dictionarylookups.
Dispose: collect all tasks to cancel and dispose first (inside thelock), and then we cancel and dispose after the lock. This hopefully fixes
Crash in networking stack #20345 (this solution was tested by a
customer and apparently worked).
Might fix #20345.