AsyncLocal diagnostics, clean slate
We used node CE, a customized async CE variant to ensure DiagnosticsThreadStatics flow from thread to thread along with the execution of asynchronous compiler tasks.
AsyncLocal<'T> achieves this without the need for a specialized CE.
AsyncLocal<T>Class Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.
This allows us to just remove NodeCode and replace node expressions with async.
Tests show AsyncLocal works as expected in scenarios involving task, async, Async.SwitchToNewThread() SwitchToThreadPool(), Thread.Start() as well as TPL parallel execution.
- [x] Added concurrent diagnostics logging for running many computations:
-
MultipleDiagnosticsLoggers.Parallelgenerally for use in place ofNodeCode.Parallel. Replays diagnostics onto caller's logger preserving the given order of computations. -
MultipleDiagnosticsLoggers.SequentialreplacesNodeCode.Parallel. UnlikeAsync.Sequentialit starts immediately on the same thread so It automatically uses caller's diagnostics context.
- [x] Added tests for
AsyncLocaldiagnostics flow in async and sync contexts.
Previous attempts: #16602, #16645
:heavy_exclamation_mark: Release notes required
:white_check_mark: Found changes and release notes in following paths:
Change path Release notes path Description src/Compilerdocs/release-notes/.FSharp.Compiler.Service/8.0.400.md vsintegration/srcdocs/release-notes/.VisualStudio/17.11.md
Might shed some light on what's going on here with the deadlocks: https://github.com/dotnet/fsharp/pull/12638
Also: https://devblogs.microsoft.com/premier-developer/the-danger-of-taskcompletionsourcet-class/
And maybe informative, since both GraphNode and AsyncMemoize that we have are variations on AsyncLazy:
https://github.com/dotnet/roslyn/blob/01b7c233fdda946c1a5628d4692ed827a07e33dd/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/AsyncLazy%601.cs#L525-L530
// We want to always run continuations asynchronously. Running them synchronously could result in deadlocks:
// if we're looping through a bunch of Requests and completing them one by one, and the continuation for the
// first Request was then blocking waiting for a later Request, we would hang. It also could cause performance
// issues. If the first request then consumes a lot of CPU time, we're not letting other Requests complete that
// could use another CPU core at the same time.
public Request() : base(TaskCreationOptions.RunContinuationsAsynchronously)
Looking at the source of Async.AwaitTask, It's also not clear to me, whether we respect TaskCreationOptions.RunContinuationsAsynchronously flag. It seems to work, but is it deliberate or by accident?
Still a draft, or ready for review?
This was kinda put on hold as a risky change IIRC. I can try to revive it but it would require a really careful look.
/run fantomas
Failed to run fantomas: https://github.com/dotnet/fsharp/actions/runs/8894616544
I added one more test to make sure it works with ListParallel. (In use with graph-based type checking).
/run fantomas
Failed to run fantomas: https://github.com/dotnet/fsharp/actions/runs/8924428488
I think this is good for a review now.
@majocha i saw reports from @ForNeVeR that some of the changes result in the deadlock
ah, ok I'm seeing the issue now.