diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index d6ae83ada6e..f2bbeaaae3e 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -463,6 +463,8 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member this.Get'(key, computation) = + failwith "AsyncMemoize" + let wrappedKey = { new ICacheKey<_, _> with member _.GetKey() = key @@ -474,6 +476,8 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member _.Get(key: ICacheKey<_, _>, computation) = + failwith "AsyncMemoize" + let key = { Label = key.GetLabel() diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index b4abe3ad1ed..c6202b0839b 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -15,16 +15,21 @@ type NodeCode<'T> = Node of Async<'T> let wrapThreadStaticInfo computation = async { - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode let phase = DiagnosticsThreadStatics.BuildPhase try return! computation finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- phase } + +let reset() = + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- AssertFalseDiagnosticsLogger + DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase + let unwrapNode (Node(computation)) = computation type Async<'T> with @@ -98,7 +103,7 @@ type NodeCodeBuilder() = member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = Node( async { - DiagnosticsThreadStatics.DiagnosticsLogger <- value.DiagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- value.DiagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- value.BuildPhase try @@ -125,21 +130,21 @@ type NodeCode private () = static let cancellationToken = Node(wrapThreadStaticInfo Async.CancellationToken) static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode let phase = DiagnosticsThreadStatics.BuildPhase try try let work = async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- phase return! computation |> Async.AwaitNodeCode } Async.StartImmediateAsTask(work, cancellationToken = ct).Result finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- phase with :? AggregateException as ex when ex.InnerExceptions.Count = 1 -> raise (ex.InnerExceptions[0]) @@ -148,20 +153,20 @@ type NodeCode private () = NodeCode.RunImmediate(computation, CancellationToken.None) static member StartAsTask_ForTesting(computation: NodeCode<'T>, ?ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode let phase = DiagnosticsThreadStatics.BuildPhase try let work = async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- phase return! computation |> Async.AwaitNodeCode } Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None) finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhase <- phase static member CancellationToken = cancellationToken @@ -205,9 +210,12 @@ type NodeCode private () = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") async { - DiagnosticsThreadStatics.DiagnosticsLogger <- logger - DiagnosticsThreadStatics.BuildPhase <- phase - return! unwrapNode computation + try + DiagnosticsThreadStatics.DiagnosticsLogger <- logger + DiagnosticsThreadStatics.BuildPhase <- phase + return! unwrapNode computation + finally + reset() } return! @@ -256,6 +264,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption cachedResultNode else node { + use _ = new CompilationGlobalsScope() Interlocked.Increment(&requestCount) |> ignore try @@ -284,13 +293,13 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption | ValueSome value -> return value | _ -> let tcs = TaskCompletionSource<'T>() - let (Node(p)) = computation Async.StartWithContinuations( - async { + node { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! p - }, + reset() + return! computation + } |> Async.AwaitNodeCode, (fun res -> cachedResult <- ValueSome res cachedResultNode <- node.Return res diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 75dfaaef39e..4ebf1e494b9 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -375,6 +375,25 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = let errors = diagnostics.ToArray() errors |> Array.iter diagnosticsLogger.DiagnosticSink +let currentDiagnosticsLogger = AsyncLocal() + +let checkIfSame threadStatic = + match currentDiagnosticsLogger.Value with + + // Big hurdle in testing this is the fact that threadstatics leak from one unrelated test to another. + // At least that's what happens in VS test runner. In effect, at the beginning of some computation when we expect + // to see uninitialized threadstatic, we get some value that stayed alive from another test function. + // Especially FinalizeTypeCheckTask shows up here a lot. + // That's why we have to disregard the threadstatics at the beggining of each execution context, + // up to the moment of proper initialization (the first call of UseDiagnosticsLogger usually). + | ValueNone -> threadStatic + + | ValueSome asyncLocal -> + match asyncLocal, threadStatic with + | a, t when a = t -> t // Good. + | _ when threadStatic = AssertFalseDiagnosticsLogger -> threadStatic // AsyncLocal is always good on thread switches. ThreadStatic needs to catch up. + | _ -> failwith $"AsyncLocal does not match ThreadStatic. a: {asyncLocal.DebugDisplay()} t: {threadStatic.DebugDisplay()}" + /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = [] @@ -393,6 +412,15 @@ type internal DiagnosticsThreadStatics = and set v = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLogger + with get () = + match box DiagnosticsThreadStatics.diagnosticsLogger with + | Null -> AssertFalseDiagnosticsLogger + | _ -> checkIfSame DiagnosticsThreadStatics.diagnosticsLogger + and set v = + currentDiagnosticsLogger.Value <- ValueSome v + DiagnosticsThreadStatics.diagnosticsLogger <- v + + static member DiagnosticsLoggerNodeCode with get () = match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger @@ -533,6 +561,8 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B let unwindEL = UseDiagnosticsLogger diagnosticsLogger let unwindBP = UseBuildPhase buildPhase + new() = new CompilationGlobalsScope(DiagnosticsThreadStatics.DiagnosticsLogger, DiagnosticsThreadStatics.BuildPhase) + member _.DiagnosticsLogger = diagnosticsLogger member _.BuildPhase = buildPhase diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index bcbdd197b73..4321407961c 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -236,6 +236,8 @@ type DiagnosticsThreadStatics = static member DiagnosticsLogger: DiagnosticsLogger with get, set + static member DiagnosticsLoggerNodeCode: DiagnosticsLogger with get, set + [] module DiagnosticsLoggerExtensions = @@ -458,6 +460,8 @@ type StackGuard = type CompilationGlobalsScope = new: diagnosticsLogger: DiagnosticsLogger * buildPhase: BuildPhase -> CompilationGlobalsScope + new: unit -> CompilationGlobalsScope + interface IDisposable member DiagnosticsLogger: DiagnosticsLogger diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..a95542d41c0 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -375,11 +375,13 @@ type BoundModel private ( return diags } |> GraphNode - let startComputingFullTypeCheck = - node { - let! _ = tcInfoExtras.GetOrComputeValue() - return! diagnostics.GetOrComputeValue() - } + //let startComputingFullTypeCheck() = + // async { + // use _ = InitCompilationGlobalsScope() + // do! tcInfoExtras.GetOrComputeValue() |> Async.AwaitNodeCode |> Async.Ignore + // do! diagnostics.GetOrComputeValue() |> Async.AwaitNodeCode |> Async.Ignore + // } + // |> Async.Start let tcInfo, tcInfoExtras = match tcStateOpt with @@ -391,7 +393,7 @@ type BoundModel private ( GraphNode.FromResult tcInfo, tcInfoExtras | _ -> // start computing extras, so that typeCheckNode can be GC'd quickly - startComputingFullTypeCheck |> Async.AwaitNodeCode |> Async.Catch |> Async.Ignore |> Async.Start + // startComputingFullTypeCheck() getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -735,7 +737,7 @@ module IncrementalBuilderHelpers = } /// Finish up the typechecking to produce outputs for the rest of the compilation process - let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals partialCheck assemblyName outfile (boundModels: GraphNode seq) = + let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals _partialCheck assemblyName outfile (boundModels: GraphNode seq) = node { let diagnosticsLogger = CompilationDiagnosticLogger("FinalizeTypeCheckTask", tcConfig.diagnosticsOptions) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck) @@ -751,13 +753,13 @@ module IncrementalBuilderHelpers = let! latestImplFiles = computedBoundModels |> Seq.map (fun boundModel -> node { - if partialCheck then - return None - else + //if partialCheck then + // return None + //else let! tcInfoExtras = boundModel.GetOrComputeTcInfoExtras() return tcInfoExtras.latestImplFile }) - |> NodeCode.Parallel + |> NodeCode.Sequential let results = [ for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles -> @@ -826,7 +828,7 @@ module IncrementalBuilderHelpers = let! partialDiagnostics = computedBoundModels |> Seq.map (fun m -> m.Diagnostics.GetOrComputeValue()) - |> NodeCode.Parallel + |> NodeCode.Sequential let diagnostics = [ diagnosticsLogger.GetDiagnostics() yield! partialDiagnostics |> Seq.rev