From 11aead068306ce83a78d7f6bb9cd4b846e718b37 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 13 Feb 2024 15:54:57 +0100 Subject: [PATCH 01/43] starting point for testing AsyncLocal impact --- src/Compiler/Facilities/BuildGraph.fs | 44 ++++----- src/Compiler/Facilities/DiagnosticsLogger.fs | 97 +++++++++++++++++-- src/Compiler/Facilities/DiagnosticsLogger.fsi | 4 + 3 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 71f4d3da991..ed37b46b0aa 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -15,14 +15,14 @@ type NodeCode<'T> = Node of Async<'T> let wrapThreadStaticInfo computation = async { - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC + let phase = DiagnosticsThreadStatics.BuildPhaseNC try return! computation finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase } type Async<'T> with @@ -96,8 +96,8 @@ type NodeCodeBuilder() = member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = Node( async { - DiagnosticsThreadStatics.DiagnosticsLogger <- value.DiagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- value.BuildPhase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- value.DiagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- value.BuildPhase try return! binder value |> Async.AwaitNodeCode @@ -123,22 +123,22 @@ type NodeCode private () = static let cancellationToken = Node(wrapThreadStaticInfo Async.CancellationToken) static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC + let phase = DiagnosticsThreadStatics.BuildPhaseNC try try let work = async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase return! computation |> Async.AwaitNodeCode } Async.StartImmediateAsTask(work, cancellationToken = ct).Result finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase with :? AggregateException as ex when ex.InnerExceptions.Count = 1 -> raise (ex.InnerExceptions[0]) @@ -146,21 +146,21 @@ type NodeCode private () = NodeCode.RunImmediate(computation, CancellationToken.None) static member StartAsTask_ForTesting(computation: NodeCode<'T>, ?ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC + let phase = DiagnosticsThreadStatics.BuildPhaseNC try let work = async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase return! computation |> Async.AwaitNodeCode } Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None) finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase static member CancellationToken = cancellationToken @@ -193,14 +193,14 @@ type NodeCode private () = } static member Parallel(computations: NodeCode<'T> seq) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC + let phase = DiagnosticsThreadStatics.BuildPhaseNC computations |> Seq.map (fun (Node x) -> async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase return! x }) |> Async.Parallel diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 08a46d1a25d..12c86f2a402 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -333,8 +333,7 @@ type DiagnosticsLogger(nameForDebugging: string) = member x.CheckForErrors() = (x.ErrorCount > 0) - member _.DebugDisplay() = - sprintf "DiagnosticsLogger(%s)" nameForDebugging + member _.DebugDisplay() = nameForDebugging let DiscardErrorsLogger = { new DiagnosticsLogger("DiscardErrorsLogger") with @@ -345,8 +344,8 @@ let DiscardErrorsLogger = let AssertFalseDiagnosticsLogger = { new DiagnosticsLogger("AssertFalseDiagnosticsLogger") with // TODO: reenable these asserts in the compiler service - member _.DiagnosticSink(diagnostic, severity) = (* assert false; *) () - member _.ErrorCount = (* assert false; *) 0 + member _.DiagnosticSink(diagnostic, severity) = assert false + member _.ErrorCount = assert false; 0 } type CapturingDiagnosticsLogger(nm, ?eagerFormat) = @@ -376,6 +375,47 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = + + static let buildPhaseAsync = new AsyncLocal() + static let diagnosticsLoggerAsync = new AsyncLocal() + + static let getOrCreate (holder: AsyncLocal<_>) defaultValue = + holder.Value + |> ValueOption.defaultWith (fun () -> + holder.Value <- ValueSome defaultValue + defaultValue) + +#if DEBUG + static let changes = System.Collections.Concurrent.ConcurrentStack() + static let changesAsync = System.Collections.Concurrent.ConcurrentStack() + + static let log prefix name nc = + let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" + let nctag = if nc then "NC" else " " + let str = $"{nctag} {prefix} %-50s{name}\n\n\t{stack}" + if not nc then changesAsync.Push str + changes.Push str + + static let logPhase (bp: BuildPhase) = log "bp:" (if box bp |> isNull then "NULL" else bp.ToString()) + static let logLogger (dl: DiagnosticsLogger) = log "dl:" (if box dl |> isNull then "NULL" else dl.DebugDisplay()) +#else + static let logPhase = ignore + static let logLogger = ignore +#endif + + static let checkForNull v = + if box v |> isNull then failwith $"{v.GetType().Name} set to null." + + static let checkPhaseDiverged v = + let a = getOrCreate buildPhaseAsync BuildPhase.DefaultPhase + if a <> v then + failwith $"BuildPhase diverged. AsyncLocal: <{a}>, ThreadStatic: <{v}>." + + static let checkLoggerDiverged v = + let a = getOrCreate diagnosticsLoggerAsync AssertFalseDiagnosticsLogger + if a <> v then + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{a.DebugDisplay()}>, ThreadStatic: <{v.DebugDisplay()}>." + [] static val mutable private buildPhase: BuildPhase @@ -385,18 +425,56 @@ type internal DiagnosticsThreadStatics = static member BuildPhaseUnchecked = DiagnosticsThreadStatics.buildPhase static member BuildPhase + with get () = + match box DiagnosticsThreadStatics.buildPhase with + | Null -> + checkPhaseDiverged BuildPhase.DefaultPhase + BuildPhase.DefaultPhase + | _ -> + checkPhaseDiverged DiagnosticsThreadStatics.buildPhase + DiagnosticsThreadStatics.buildPhase + and set v = + logPhase v false + buildPhaseAsync.Value <- ValueSome v + DiagnosticsThreadStatics.buildPhase <- v + checkForNull v + + static member DiagnosticsLogger + with get () = + match box DiagnosticsThreadStatics.diagnosticsLogger with + | Null -> + checkLoggerDiverged AssertFalseDiagnosticsLogger + AssertFalseDiagnosticsLogger + | _ -> + checkLoggerDiverged DiagnosticsThreadStatics.diagnosticsLogger + DiagnosticsThreadStatics.diagnosticsLogger + and set v = + logLogger v false + diagnosticsLoggerAsync.Value <- ValueSome v + DiagnosticsThreadStatics.diagnosticsLogger <- v + checkForNull v + + static member BuildPhaseNC with get () = match box DiagnosticsThreadStatics.buildPhase with | Null -> BuildPhase.DefaultPhase | _ -> DiagnosticsThreadStatics.buildPhase - and set v = DiagnosticsThreadStatics.buildPhase <- v + and set v = + DiagnosticsThreadStatics.buildPhase <- v + logPhase v true + checkForNull v + checkPhaseDiverged v - static member DiagnosticsLogger + static member DiagnosticsLoggerNC with get () = match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger | _ -> DiagnosticsThreadStatics.diagnosticsLogger - and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v + and set v = + logLogger v true + DiagnosticsThreadStatics.diagnosticsLogger <- v + checkForNull v + checkLoggerDiverged v [] module DiagnosticsLoggerExtensions = @@ -498,7 +576,10 @@ module DiagnosticsLoggerExtensions = /// NOTE: The change will be undone when the returned "unwind" object disposes let UseBuildPhase (phase: BuildPhase) = - let oldBuildPhase = DiagnosticsThreadStatics.BuildPhaseUnchecked + + // TODO: possibly restore this optimization. BuildPhaseUnchecked can be null so it complicates things for us. + // let oldBuildPhase = DiagnosticsThreadStatics.BuildPhaseUnchecked + let oldBuildPhase = DiagnosticsThreadStatics.BuildPhase DiagnosticsThreadStatics.BuildPhase <- phase { new IDisposable with diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index e9040da36ed..4d2508dd5c9 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -236,6 +236,10 @@ type DiagnosticsThreadStatics = static member DiagnosticsLogger: DiagnosticsLogger with get, set + static member BuildPhaseNC: BuildPhase with get, set + + static member DiagnosticsLoggerNC: DiagnosticsLogger with get, set + [] module DiagnosticsLoggerExtensions = From 35f9a9b7b451ea3d9c809359d9df57c7da7f9067 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 13 Feb 2024 16:21:26 +0100 Subject: [PATCH 02/43] fix Release mode --- src/Compiler/Facilities/DiagnosticsLogger.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 12c86f2a402..446538c529b 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -399,8 +399,8 @@ type internal DiagnosticsThreadStatics = static let logPhase (bp: BuildPhase) = log "bp:" (if box bp |> isNull then "NULL" else bp.ToString()) static let logLogger (dl: DiagnosticsLogger) = log "dl:" (if box dl |> isNull then "NULL" else dl.DebugDisplay()) #else - static let logPhase = ignore - static let logLogger = ignore + static let logPhase _ = ignore + static let logLogger _ = ignore #endif static let checkForNull v = From 2a1cb73aa5a092bb109b8be37c1692998505d7c7 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 13 Feb 2024 17:36:39 +0100 Subject: [PATCH 03/43] log thread id --- src/Compiler/Facilities/DiagnosticsLogger.fs | 10 +++++++++- src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 446538c529b..bb1b6a6d755 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -392,7 +392,8 @@ type internal DiagnosticsThreadStatics = static let log prefix name nc = let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let nctag = if nc then "NC" else " " - let str = $"{nctag} {prefix} %-50s{name}\n\n\t{stack}" + let tid = Thread.CurrentThread.ManagedThreadId + let str = $"t:{tid} {nctag} {prefix} %-50s{name}\n\n\t{stack}" if not nc then changesAsync.Push str changes.Push str @@ -622,6 +623,13 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B unwindBP.Dispose() unwindEL.Dispose() +/// +let PreserveAsyncScope computation = + async { + use _ = new CompilationGlobalsScope(DiagnosticsThreadStatics.DiagnosticsLogger, DiagnosticsThreadStatics.BuildPhase) + return! computation + } + // Global functions are still used by parser and TAST ops. /// Raises an exception with error recovery and returns unit. diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 4d2508dd5c9..c9b176fea70 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -467,3 +467,5 @@ type CompilationGlobalsScope = member DiagnosticsLogger: DiagnosticsLogger member BuildPhase: BuildPhase + +val PreserveAsyncScope: Async<'T> -> Async<'T> From 06380568cf1cbe71a56972f73cad77e55e337747 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 13 Feb 2024 19:55:03 +0100 Subject: [PATCH 04/43] noop NodeCode to see what breaks --- src/Compiler/Facilities/AsyncMemoize.fs | 27 ++---- src/Compiler/Facilities/DiagnosticsLogger.fs | 93 +++++++++----------- 2 files changed, 53 insertions(+), 67 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index b780d91ca74..4edee5fbc45 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -354,15 +354,11 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Restarted, key) Interlocked.Increment &restarted |> ignore System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" - let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger - DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger - - try - let! result = computation |> Async.AwaitNodeCode - post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) - return () - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger + + use _ = UseDiagnosticsLogger cachingLogger + let! result = computation |> Async.AwaitNodeCode + post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) + return () with | TaskCancelled _ -> Interlocked.Increment &cancel_exception_subsequent |> ignore @@ -500,17 +496,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Async.StartAsTask( async { // TODO: Should unify starting and restarting - let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger - DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger - log (Started, key) - try - let! result = computation |> Async.AwaitNodeCode - post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) - return result - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger + use _ = UseDiagnosticsLogger cachingLogger + let! result = computation |> Async.AwaitNodeCode + post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) + return result }, cancellationToken = linkedCtSource.Token ) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index bb1b6a6d755..8a3a8dfe369 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -376,46 +376,47 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = - static let buildPhaseAsync = new AsyncLocal() - static let diagnosticsLoggerAsync = new AsyncLocal() - - static let getOrCreate (holder: AsyncLocal<_>) defaultValue = - holder.Value - |> ValueOption.defaultWith (fun () -> - holder.Value <- ValueSome defaultValue - defaultValue) + static let buildPhaseAsync = new AsyncLocal() + static let diagnosticsLoggerAsync = new AsyncLocal() #if DEBUG static let changes = System.Collections.Concurrent.ConcurrentStack() static let changesAsync = System.Collections.Concurrent.ConcurrentStack() - static let log prefix name nc = + static let dlName (dl: DiagnosticsLogger) = + if box dl |> isNull then "NULL" else dl.DebugDisplay() + + static let log prefix nc = + let al = dlName diagnosticsLoggerAsync.Value + let ts = dlName DiagnosticsThreadStatics.diagnosticsLogger let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let nctag = if nc then "NC" else " " let tid = Thread.CurrentThread.ManagedThreadId - let str = $"t:{tid} {nctag} {prefix} %-50s{name}\n\n\t{stack}" + let str = $"t:{tid} {nctag} {prefix} al: {al}, ts: {ts} \n\n\t{stack}" if not nc then changesAsync.Push str changes.Push str - static let logPhase (bp: BuildPhase) = log "bp:" (if box bp |> isNull then "NULL" else bp.ToString()) - static let logLogger (dl: DiagnosticsLogger) = log "dl:" (if box dl |> isNull then "NULL" else dl.DebugDisplay()) + //static let logPhase _ = ignore + //// static let logPhase (bp: BuildPhase) = log "bp:" (if box bp |> isNull then "NULL" else bp.ToString()) + //static let logLogger (dl: DiagnosticsLogger) = log "dl:" (if box dl |> isNull then "NULL" else dl.DebugDisplay()) #else - static let logPhase _ = ignore - static let logLogger _ = ignore + static let log _ _ = () #endif static let checkForNull v = if box v |> isNull then failwith $"{v.GetType().Name} set to null." - static let checkPhaseDiverged v = - let a = getOrCreate buildPhaseAsync BuildPhase.DefaultPhase - if a <> v then + static let checkPhaseDiverged() = + let a = buildPhaseAsync.Value + let v = DiagnosticsThreadStatics.buildPhase + if box a <> box v then failwith $"BuildPhase diverged. AsyncLocal: <{a}>, ThreadStatic: <{v}>." - static let checkLoggerDiverged v = - let a = getOrCreate diagnosticsLoggerAsync AssertFalseDiagnosticsLogger - if a <> v then - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{a.DebugDisplay()}>, ThreadStatic: <{v.DebugDisplay()}>." + static let checkLoggerDiverged() = + let a = diagnosticsLoggerAsync.Value + let v = DiagnosticsThreadStatics.diagnosticsLogger + if box a <> box v then + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{a.DebugDisplay()}>, ThreadStatic: <{v.DebugDisplay()}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -429,53 +430,47 @@ type internal DiagnosticsThreadStatics = with get () = match box DiagnosticsThreadStatics.buildPhase with | Null -> - checkPhaseDiverged BuildPhase.DefaultPhase BuildPhase.DefaultPhase | _ -> - checkPhaseDiverged DiagnosticsThreadStatics.buildPhase DiagnosticsThreadStatics.buildPhase and set v = - logPhase v false - buildPhaseAsync.Value <- ValueSome v + buildPhaseAsync.Value <- v DiagnosticsThreadStatics.buildPhase <- v - checkForNull v static member DiagnosticsLogger with get () = + log "get" false match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> - checkLoggerDiverged AssertFalseDiagnosticsLogger AssertFalseDiagnosticsLogger | _ -> - checkLoggerDiverged DiagnosticsThreadStatics.diagnosticsLogger DiagnosticsThreadStatics.diagnosticsLogger and set v = - logLogger v false - diagnosticsLoggerAsync.Value <- ValueSome v + log "set" false + diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v - checkForNull v static member BuildPhaseNC - with get () = - match box DiagnosticsThreadStatics.buildPhase with - | Null -> BuildPhase.DefaultPhase - | _ -> DiagnosticsThreadStatics.buildPhase - and set v = - DiagnosticsThreadStatics.buildPhase <- v - logPhase v true - checkForNull v - checkPhaseDiverged v + with get () = BuildPhase.DefaultPhase + //match box DiagnosticsThreadStatics.buildPhase with + //| Null -> BuildPhase.DefaultPhase + //| _ -> DiagnosticsThreadStatics.buildPhase + and set (v: BuildPhase) = ignore v + //DiagnosticsThreadStatics.buildPhase <- v + //logPhase v true + //checkForNull v + //checkPhaseDiverged v static member DiagnosticsLoggerNC - with get () = - match box DiagnosticsThreadStatics.diagnosticsLogger with - | Null -> AssertFalseDiagnosticsLogger - | _ -> DiagnosticsThreadStatics.diagnosticsLogger - and set v = - logLogger v true - DiagnosticsThreadStatics.diagnosticsLogger <- v - checkForNull v - checkLoggerDiverged v + with get () = AssertFalseDiagnosticsLogger + //match box DiagnosticsThreadStatics.diagnosticsLogger with + //| Null -> AssertFalseDiagnosticsLogger + //| _ -> DiagnosticsThreadStatics.diagnosticsLogger + and set (v: DiagnosticsLogger) = ignore v + //logLogger v true + //DiagnosticsThreadStatics.diagnosticsLogger <- v + //checkForNull v + //checkLoggerDiverged v [] module DiagnosticsLoggerExtensions = From 0a722083c8b2d4cb56db48dc57f2c3116941b26f Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 13 Feb 2024 22:37:33 +0100 Subject: [PATCH 05/43] more useful log --- src/Compiler/Facilities/DiagnosticsLogger.fs | 69 +++++++------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 8a3a8dfe369..055ae0c32c0 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -381,43 +381,26 @@ type internal DiagnosticsThreadStatics = #if DEBUG static let changes = System.Collections.Concurrent.ConcurrentStack() - static let changesAsync = System.Collections.Concurrent.ConcurrentStack() + //static let changesAsync = System.Collections.Concurrent.ConcurrentStack() static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() - static let log prefix nc = - let al = dlName diagnosticsLoggerAsync.Value - let ts = dlName DiagnosticsThreadStatics.diagnosticsLogger + static let log prefix = + let al = diagnosticsLoggerAsync.Value + let ts = DiagnosticsThreadStatics.diagnosticsLogger let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" - let nctag = if nc then "NC" else " " let tid = Thread.CurrentThread.ManagedThreadId - let str = $"t:{tid} {nctag} {prefix} al: {al}, ts: {ts} \n\n\t{stack}" - if not nc then changesAsync.Push str + let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" + let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" changes.Push str - //static let logPhase _ = ignore - //// static let logPhase (bp: BuildPhase) = log "bp:" (if box bp |> isNull then "NULL" else bp.ToString()) - //static let logLogger (dl: DiagnosticsLogger) = log "dl:" (if box dl |> isNull then "NULL" else dl.DebugDisplay()) + if box al <> box ts then + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." #else - static let log _ _ = () + static let log _ = () #endif - static let checkForNull v = - if box v |> isNull then failwith $"{v.GetType().Name} set to null." - - static let checkPhaseDiverged() = - let a = buildPhaseAsync.Value - let v = DiagnosticsThreadStatics.buildPhase - if box a <> box v then - failwith $"BuildPhase diverged. AsyncLocal: <{a}>, ThreadStatic: <{v}>." - - static let checkLoggerDiverged() = - let a = diagnosticsLoggerAsync.Value - let v = DiagnosticsThreadStatics.diagnosticsLogger - if box a <> box v then - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{a.DebugDisplay()}>, ThreadStatic: <{v.DebugDisplay()}>, tid: {Thread.CurrentThread.ManagedThreadId}." - [] static val mutable private buildPhase: BuildPhase @@ -439,38 +422,32 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = - log "get" false + log "get" match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set v = - log "set" false + log "set" diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v static member BuildPhaseNC - with get () = BuildPhase.DefaultPhase - //match box DiagnosticsThreadStatics.buildPhase with - //| Null -> BuildPhase.DefaultPhase - //| _ -> DiagnosticsThreadStatics.buildPhase - and set (v: BuildPhase) = ignore v - //DiagnosticsThreadStatics.buildPhase <- v - //logPhase v true - //checkForNull v - //checkPhaseDiverged v + with get () = + match box DiagnosticsThreadStatics.buildPhase with + | Null -> BuildPhase.DefaultPhase + | _ -> DiagnosticsThreadStatics.buildPhase + and set (v: BuildPhase) = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLoggerNC - with get () = AssertFalseDiagnosticsLogger - //match box DiagnosticsThreadStatics.diagnosticsLogger with - //| Null -> AssertFalseDiagnosticsLogger - //| _ -> DiagnosticsThreadStatics.diagnosticsLogger - and set (v: DiagnosticsLogger) = ignore v - //logLogger v true - //DiagnosticsThreadStatics.diagnosticsLogger <- v - //checkForNull v - //checkLoggerDiverged v + with get () = + match box DiagnosticsThreadStatics.diagnosticsLogger with + | Null -> AssertFalseDiagnosticsLogger + | _ -> DiagnosticsThreadStatics.diagnosticsLogger + and set (v: DiagnosticsLogger) = + DiagnosticsThreadStatics.diagnosticsLogger <- v + log "NC set" [] module DiagnosticsLoggerExtensions = From 7b34814aee28ee84a636e2d405c7d1713f16f0ca Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 11:11:44 +0100 Subject: [PATCH 06/43] some attempts --- src/Compiler/Facilities/BuildGraph.fs | 68 +++++++++---------- src/Compiler/Facilities/BuildGraph.fsi | 5 +- src/Compiler/Facilities/DiagnosticsLogger.fs | 33 ++++++--- src/Compiler/Service/BackgroundCompiler.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 7 +- .../BuildGraphTests.fs | 1 + 6 files changed, 61 insertions(+), 55 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index ed37b46b0aa..b9658095ba8 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -93,25 +93,11 @@ type NodeCodeBuilder() = member _.Combine(Node(p1): NodeCode, Node(p2): NodeCode<'T>) : NodeCode<'T> = Node(async.Combine(p1, p2)) [] - member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = + member _.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = Node( async { - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- value.DiagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- value.BuildPhase - - try - return! binder value |> Async.AwaitNodeCode - finally - (value :> IDisposable).Dispose() - } - ) - - [] - member _.Using(value: IDisposable, binder: IDisposable -> NodeCode<'U>) = - Node( - async { - use _ = value - return! binder value |> Async.AwaitNodeCode + use _ = resource + return! binder resource |> Async.AwaitNodeCode } ) @@ -165,7 +151,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(wrapThreadStaticInfo (Cancellable.toAsync computation)) + Node(wrapThreadStaticInfo (Cancellable.toAsync computation |> PreserveAsyncScope)) static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) @@ -175,6 +161,16 @@ type NodeCode private () = static member AwaitTask(task: Task) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) + static member AwaitTaskWithoutWrapping<'T>(task: Task<'T>) = + Node((Async.AwaitTask task)) + + static member AwaitTaskWithoutWrapping(task: Task) = + Node((Async.AwaitTask task)) + + static member AwaitAsyncWithoutWrapping(computation: Async<'T>) = Node(computation) + + static member Unwrap(computation: NodeCode<'T>) = let (Node(wrapped)) = computation in wrapped + static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) @@ -182,30 +178,28 @@ type NodeCode private () = Node(wrapThreadStaticInfo (Async.Sleep(ms))) static member Sequential(computations: NodeCode<'T> seq) = - node { + async { let results = ResizeArray() for computation in computations do - let! res = computation + let! res = computation |> Async.AwaitNodeCode |> PreserveAsyncScope results.Add(res) return results.ToArray() - } + } |> NodeCode.AwaitAsync static member Parallel(computations: NodeCode<'T> seq) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC - let phase = DiagnosticsThreadStatics.BuildPhaseNC - - computations - |> Seq.map (fun (Node x) -> - async { - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! x - }) - |> Async.Parallel - |> wrapThreadStaticInfo + async { + DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + return! + computations + |> Seq.map NodeCode.Unwrap + |> Async.Parallel + } + |> PreserveAsyncScope |> Node + [] module GraphNode = @@ -245,6 +239,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption cachedResultNode else node { + Interlocked.Increment(&requestCount) |> ignore try @@ -267,18 +262,17 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ||| TaskContinuationOptions.NotOnFaulted ||| TaskContinuationOptions.ExecuteSynchronously) ) - |> NodeCode.AwaitTask + |> NodeCode.AwaitTaskWithoutWrapping match cachedResult with | ValueSome value -> return value | _ -> let tcs = TaskCompletionSource<'T>() - let (Node(p)) = computation Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! p + return! computation |> Async.AwaitNodeCode |> PreserveAsyncScope }, (fun res -> cachedResult <- ValueSome res @@ -290,7 +284,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ct ) - return! tcs.Task |> NodeCode.AwaitTask + return! tcs.Task |> NodeCode.AwaitTaskWithoutWrapping finally if taken then semaphore.Release() |> ignore diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index afbf9d2898b..4b31005bee6 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -44,11 +44,8 @@ type NodeCodeBuilder = member Combine: x1: NodeCode * x2: NodeCode<'T> -> NodeCode<'T> - /// A limited form 'use' for establishing the compilation globals. - member Using: CompilationGlobalsScope * (CompilationGlobalsScope -> NodeCode<'T>) -> NodeCode<'T> - /// A generic 'use' that disposes of the IDisposable at the end of the computation. - member Using: IDisposable * (IDisposable -> NodeCode<'T>) -> NodeCode<'T> + member Using: ('T :> IDisposable) * (('T :> IDisposable) -> NodeCode<'U>) -> NodeCode<'U> /// Specifies code that can be run as part of the build graph. val node: NodeCodeBuilder diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 055ae0c32c0..0b1a4c2613d 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -379,27 +379,40 @@ type internal DiagnosticsThreadStatics = static let buildPhaseAsync = new AsyncLocal() static let diagnosticsLoggerAsync = new AsyncLocal() + static let withDefault (holder: AsyncLocal<_>) defaultValue = + match box holder.Value with + | Null -> defaultValue + | _ -> holder.Value + #if DEBUG static let changes = System.Collections.Concurrent.ConcurrentStack() //static let changesAsync = System.Collections.Concurrent.ConcurrentStack() +#endif static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() - static let log prefix = - let al = diagnosticsLoggerAsync.Value + static let logOrCheck prefix = + let al = withDefault diagnosticsLoggerAsync AssertFalseDiagnosticsLogger let ts = DiagnosticsThreadStatics.diagnosticsLogger + +#if DEBUG let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let tid = Thread.CurrentThread.ManagedThreadId let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" changes.Push str - - if box al <> box ts then - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." -#else - static let log _ = () #endif + ignore prefix + + match box al, box ts with + | al, ts when al = ts -> () + // ThreadStatic likes to be null quite often. We ignore this, as long as it doesn't push diagnostics to null logger. + | _, ts when ts = AssertFalseDiagnosticsLogger -> () + | _, Null -> () + | _ -> + // Debugger.Break() + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -422,16 +435,16 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = - log "get" + logOrCheck "get" match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set v = - log "set" diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v + logOrCheck "set" static member BuildPhaseNC with get () = @@ -447,7 +460,7 @@ type internal DiagnosticsThreadStatics = | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set (v: DiagnosticsLogger) = DiagnosticsThreadStatics.diagnosticsLogger <- v - log "NC set" + // logOrCheck "NC set" [] module DiagnosticsLoggerExtensions = diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index f9f952dde70..718a53633f2 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -1666,7 +1666,7 @@ type internal BackgroundCompiler let options = projectSnapshot.ToOptions() self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode + |> Async.AwaitNodeCode |> PreserveAsyncScope member _.ProjectChecked: IEvent = self.ProjectChecked diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..e83a34938d0 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -149,9 +149,9 @@ module IncrementalBuildSyntaxTree = let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) // Return the disposable object that cleans up use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) - use! text = source.GetTextContainer() |> NodeCode.AwaitAsync + use! text = source.GetTextContainer() |> PreserveAsyncScope |> NodeCode.AwaitAsync let input = - match text :?> TextContainer with + match text with | TextContainer.Stream(stream) -> ParseOneInputStream(tcConfig, lexResourceManager, fileName, isLastCompiland, diagnosticsLogger, false, stream) | TextContainer.SourceText(sourceText) -> @@ -391,7 +391,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 |> Async.AwaitNodeCode |> PreserveAsyncScope |> Async.Catch |> Async.Ignore |> Async.RunImmediate getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -1343,6 +1343,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCode + |> PreserveAsyncScope |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 556a9b5bc42..8d462ae55cf 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -268,6 +268,7 @@ module BuildGraphTests = BuildPhase.Interactive |] + // start a bunch of computations in parallel, simulating a typical IDE scenario. let pickRandomPhase _ = phases[random phases.Length] Seq.init 100 pickRandomPhase |> Seq.map (work >> Async.AwaitNodeCode) From c817d61e4ad12d367e977dadbd174392841dc5d2 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 11:11:44 +0100 Subject: [PATCH 07/43] zero thread statics in service entry pointsome attempts --- src/Compiler/Facilities/BuildGraph.fs | 103 +++++++++++------- src/Compiler/Facilities/BuildGraph.fsi | 5 +- src/Compiler/Facilities/DiagnosticsLogger.fs | 33 ++++-- src/Compiler/Service/BackgroundCompiler.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 7 +- src/Compiler/Service/service.fs | 1 + .../BuildGraphTests.fs | 1 + 7 files changed, 95 insertions(+), 57 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index ed37b46b0aa..f0a7c1b18f2 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -13,6 +13,21 @@ open Internal.Utilities.Library [] type NodeCode<'T> = Node of Async<'T> +let restoreThreadStaticInfo() = + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC + let phase = DiagnosticsThreadStatics.BuildPhaseNC + + fun () -> + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- phase + +let initThreadStaticInfo computation = + async { + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase + return! computation + } + let wrapThreadStaticInfo computation = async { let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC @@ -25,11 +40,13 @@ let wrapThreadStaticInfo computation = DiagnosticsThreadStatics.BuildPhaseNC <- phase } +let toAsync (Node(wrapped)) = wrapThreadStaticInfo wrapped + type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = match node with - | Node(computation) -> wrapThreadStaticInfo computation + | Node(computation) -> initThreadStaticInfo computation [] type NodeCodeBuilder() = @@ -93,25 +110,11 @@ type NodeCodeBuilder() = member _.Combine(Node(p1): NodeCode, Node(p2): NodeCode<'T>) : NodeCode<'T> = Node(async.Combine(p1, p2)) [] - member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = - Node( - async { - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- value.DiagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- value.BuildPhase - - try - return! binder value |> Async.AwaitNodeCode - finally - (value :> IDisposable).Dispose() - } - ) - - [] - member _.Using(value: IDisposable, binder: IDisposable -> NodeCode<'U>) = + member _.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = Node( async { - use _ = value - return! binder value |> Async.AwaitNodeCode + use _ = resource + return! binder resource |> toAsync } ) @@ -132,7 +135,7 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! computation |> Async.AwaitNodeCode + return! computation |> toAsync } Async.StartImmediateAsTask(work, cancellationToken = ct).Result @@ -154,7 +157,7 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! computation |> Async.AwaitNodeCode + return! computation |> toAsync } Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None) @@ -165,7 +168,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(wrapThreadStaticInfo (Cancellable.toAsync computation)) + Node(wrapThreadStaticInfo (Cancellable.toAsync computation |> PreserveAsyncScope)) static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) @@ -175,6 +178,18 @@ type NodeCode private () = static member AwaitTask(task: Task) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) + static member AwaitTaskWithoutWrapping<'T>(task: Task<'T>) = + Node((Async.AwaitTask task)) + + static member AwaitTaskWithoutWrapping(task: Task) = + Node((Async.AwaitTask task)) + + static member AwaitAsyncWithoutWrapping(computation: Async<'T>) = Node(computation) + + static member Unwrap(computation: NodeCode<'T>) = let (Node(wrapped)) = computation in wrapped + + static member ToAsync(computation: NodeCode<'T>) = let (Node(wrapped)) = computation in wrapThreadStaticInfo wrapped + static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) @@ -182,30 +197,40 @@ type NodeCode private () = Node(wrapThreadStaticInfo (Async.Sleep(ms))) static member Sequential(computations: NodeCode<'T> seq) = - node { + async { let results = ResizeArray() for computation in computations do - let! res = computation + let! res = computation |> toAsync |> PreserveAsyncScope results.Add(res) return results.ToArray() - } + } |> NodeCode.AwaitAsync static member Parallel(computations: NodeCode<'T> seq) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC - let phase = DiagnosticsThreadStatics.BuildPhaseNC + async { + let restore = restoreThreadStaticInfo() - computations - |> Seq.map (fun (Node x) -> - async { - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! x - }) - |> Async.Parallel - |> wrapThreadStaticInfo + // We don't want parallel computations acting on non-threadsafe loggers. + DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + + let withRestore computation = + async { + try + return! NodeCode.Unwrap computation + finally + restore() + } + + return! + computations + |> Seq.map withRestore + |> Async.Parallel + } + |> PreserveAsyncScope |> Node + [] module GraphNode = @@ -245,6 +270,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption cachedResultNode else node { + Interlocked.Increment(&requestCount) |> ignore try @@ -267,18 +293,17 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ||| TaskContinuationOptions.NotOnFaulted ||| TaskContinuationOptions.ExecuteSynchronously) ) - |> NodeCode.AwaitTask + |> NodeCode.AwaitTaskWithoutWrapping match cachedResult with | ValueSome value -> return value | _ -> let tcs = TaskCompletionSource<'T>() - let (Node(p)) = computation Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! p + return! computation |> toAsync |> PreserveAsyncScope }, (fun res -> cachedResult <- ValueSome res @@ -290,7 +315,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ct ) - return! tcs.Task |> NodeCode.AwaitTask + return! tcs.Task |> NodeCode.AwaitTaskWithoutWrapping finally if taken then semaphore.Release() |> ignore diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index afbf9d2898b..4b31005bee6 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -44,11 +44,8 @@ type NodeCodeBuilder = member Combine: x1: NodeCode * x2: NodeCode<'T> -> NodeCode<'T> - /// A limited form 'use' for establishing the compilation globals. - member Using: CompilationGlobalsScope * (CompilationGlobalsScope -> NodeCode<'T>) -> NodeCode<'T> - /// A generic 'use' that disposes of the IDisposable at the end of the computation. - member Using: IDisposable * (IDisposable -> NodeCode<'T>) -> NodeCode<'T> + member Using: ('T :> IDisposable) * (('T :> IDisposable) -> NodeCode<'U>) -> NodeCode<'U> /// Specifies code that can be run as part of the build graph. val node: NodeCodeBuilder diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 055ae0c32c0..0b1a4c2613d 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -379,27 +379,40 @@ type internal DiagnosticsThreadStatics = static let buildPhaseAsync = new AsyncLocal() static let diagnosticsLoggerAsync = new AsyncLocal() + static let withDefault (holder: AsyncLocal<_>) defaultValue = + match box holder.Value with + | Null -> defaultValue + | _ -> holder.Value + #if DEBUG static let changes = System.Collections.Concurrent.ConcurrentStack() //static let changesAsync = System.Collections.Concurrent.ConcurrentStack() +#endif static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() - static let log prefix = - let al = diagnosticsLoggerAsync.Value + static let logOrCheck prefix = + let al = withDefault diagnosticsLoggerAsync AssertFalseDiagnosticsLogger let ts = DiagnosticsThreadStatics.diagnosticsLogger + +#if DEBUG let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let tid = Thread.CurrentThread.ManagedThreadId let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" changes.Push str - - if box al <> box ts then - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." -#else - static let log _ = () #endif + ignore prefix + + match box al, box ts with + | al, ts when al = ts -> () + // ThreadStatic likes to be null quite often. We ignore this, as long as it doesn't push diagnostics to null logger. + | _, ts when ts = AssertFalseDiagnosticsLogger -> () + | _, Null -> () + | _ -> + // Debugger.Break() + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -422,16 +435,16 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = - log "get" + logOrCheck "get" match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set v = - log "set" diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v + logOrCheck "set" static member BuildPhaseNC with get () = @@ -447,7 +460,7 @@ type internal DiagnosticsThreadStatics = | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set (v: DiagnosticsLogger) = DiagnosticsThreadStatics.diagnosticsLogger <- v - log "NC set" + // logOrCheck "NC set" [] module DiagnosticsLoggerExtensions = diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index f9f952dde70..718a53633f2 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -1666,7 +1666,7 @@ type internal BackgroundCompiler let options = projectSnapshot.ToOptions() self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode + |> Async.AwaitNodeCode |> PreserveAsyncScope member _.ProjectChecked: IEvent = self.ProjectChecked diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..e83a34938d0 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -149,9 +149,9 @@ module IncrementalBuildSyntaxTree = let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) // Return the disposable object that cleans up use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) - use! text = source.GetTextContainer() |> NodeCode.AwaitAsync + use! text = source.GetTextContainer() |> PreserveAsyncScope |> NodeCode.AwaitAsync let input = - match text :?> TextContainer with + match text with | TextContainer.Stream(stream) -> ParseOneInputStream(tcConfig, lexResourceManager, fileName, isLastCompiland, diagnosticsLogger, false, stream) | TextContainer.SourceText(sourceText) -> @@ -391,7 +391,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 |> Async.AwaitNodeCode |> PreserveAsyncScope |> Async.Catch |> Async.Ignore |> Async.RunImmediate getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -1343,6 +1343,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCode + |> PreserveAsyncScope |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 803755ab13f..035df8cdade 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -498,6 +498,7 @@ type FSharpChecker node { let! parseResults = backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName) + |> PreserveAsyncScope |> NodeCode.AwaitAsync if diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 556a9b5bc42..8d462ae55cf 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -268,6 +268,7 @@ module BuildGraphTests = BuildPhase.Interactive |] + // start a bunch of computations in parallel, simulating a typical IDE scenario. let pickRandomPhase _ = phases[random phases.Length] Seq.init 100 pickRandomPhase |> Seq.map (work >> Async.AwaitNodeCode) From 7fbbb3edac427216ceb8b2f21d30b61da06b11c5 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 13:50:30 +0100 Subject: [PATCH 08/43] wip --- src/Compiler/Facilities/BuildGraph.fs | 31 ++++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index f0a7c1b18f2..db6dd83ceb7 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -13,13 +13,13 @@ open Internal.Utilities.Library [] type NodeCode<'T> = Node of Async<'T> -let restoreThreadStaticInfo() = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC - let phase = DiagnosticsThreadStatics.BuildPhaseNC +//let restoreThreadStaticInfo() = +// let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC +// let phase = DiagnosticsThreadStatics.BuildPhaseNC - fun () -> - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- phase +// fun () -> +// DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger +// DiagnosticsThreadStatics.BuildPhaseNC <- phase let initThreadStaticInfo computation = async { @@ -209,24 +209,29 @@ type NodeCode private () = static member Parallel(computations: NodeCode<'T> seq) = async { - let restore = restoreThreadStaticInfo() - - // We don't want parallel computations acting on non-threadsafe loggers. - DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + // let restore = restoreThreadStaticInfo() let withRestore computation = async { try return! NodeCode.Unwrap computation finally - restore() + DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + // restore() } - return! + // We don't want parallel computations acting on non-threadsafe loggers. + let control = ExecutionContext.SuppressFlow() + DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger + let! run = computations |> Seq.map withRestore |> Async.Parallel + |> Async.StartChild + control.Undo() + return! run } |> PreserveAsyncScope |> Node From 3e129fea5303d6cd904d9097b112ceef8e34a0c5 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 17:36:14 +0100 Subject: [PATCH 09/43] wip --- src/Compiler/Facilities/BuildGraph.fs | 41 ++++--------------- src/Compiler/Facilities/DiagnosticsLogger.fs | 8 ++++ src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 + 3 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index db6dd83ceb7..a41fd779f3b 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -17,16 +17,10 @@ type NodeCode<'T> = Node of Async<'T> // let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC // let phase = DiagnosticsThreadStatics.BuildPhaseNC -// fun () -> -// DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger -// DiagnosticsThreadStatics.BuildPhaseNC <- phase - -let initThreadStaticInfo computation = - async { - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger - DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase - return! computation - } +// { new IDisposable with +// member _.Dispose() = +// DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger +// DiagnosticsThreadStatics.BuildPhaseNC <- phase } let wrapThreadStaticInfo computation = async { @@ -46,7 +40,7 @@ type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = match node with - | Node(computation) -> initThreadStaticInfo computation + | Node(computation) -> InitGlobalDiagnostics computation [] type NodeCodeBuilder() = @@ -184,12 +178,6 @@ type NodeCode private () = static member AwaitTaskWithoutWrapping(task: Task) = Node((Async.AwaitTask task)) - static member AwaitAsyncWithoutWrapping(computation: Async<'T>) = Node(computation) - - static member Unwrap(computation: NodeCode<'T>) = let (Node(wrapped)) = computation in wrapped - - static member ToAsync(computation: NodeCode<'T>) = let (Node(wrapped)) = computation in wrapThreadStaticInfo wrapped - static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) @@ -209,31 +197,20 @@ type NodeCode private () = static member Parallel(computations: NodeCode<'T> seq) = async { - // let restore = restoreThreadStaticInfo() - - let withRestore computation = - async { - try - return! NodeCode.Unwrap computation - finally - DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger - // restore() - } // We don't want parallel computations acting on non-threadsafe loggers. let control = ExecutionContext.SuppressFlow() - DiagnosticsThreadStatics.BuildPhaseNC <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLoggerNC <- AssertFalseDiagnosticsLogger let! run = computations - |> Seq.map withRestore + |> Seq.map toAsync |> Async.Parallel |> Async.StartChild control.Undo() return! run } - |> PreserveAsyncScope + |> InitGlobalDiagnostics // do not pass any loggers into parallel computations. + |> wrapThreadStaticInfo // make sure we restore the threadstatic state even if it got mangled by Async.Parallel. + |> PreserveAsyncScope // not really needed? |> Node diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 0b1a4c2613d..09ab19a86ff 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -13,6 +13,7 @@ open System.Reflection open System.Threading open Internal.Utilities.Library open Internal.Utilities.Library.Extras +open DiagnosticsThreadStatics /// Represents the style being used to format errors [] @@ -615,6 +616,13 @@ let PreserveAsyncScope computation = return! computation } +let InitGlobalDiagnostics computation = + async { + DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLogger <- AssertFalseDiagnosticsLogger + return! computation + } + // Global functions are still used by parser and TAST ops. /// Raises an exception with error recovery and returns unit. diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index c9b176fea70..46dd2d0430c 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -469,3 +469,5 @@ type CompilationGlobalsScope = member BuildPhase: BuildPhase val PreserveAsyncScope: Async<'T> -> Async<'T> + +val InitGlobalDiagnostics: Async<'T> -> Async<'T> From d8fb4a7785ff628b032bc2604bf4008d5ec0eb9f Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 17:49:22 +0100 Subject: [PATCH 10/43] do not initialize in getters --- src/Compiler/Facilities/BuildGraph.fs | 2 +- src/Compiler/Facilities/DiagnosticsLogger.fs | 32 +++++++------------ .../BuildGraphTests.fs | 2 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index a41fd779f3b..d713e913bbc 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -209,7 +209,7 @@ type NodeCode private () = return! run } |> InitGlobalDiagnostics // do not pass any loggers into parallel computations. - |> wrapThreadStaticInfo // make sure we restore the threadstatic state even if it got mangled by Async.Parallel. + // |> wrapThreadStaticInfo // this does not work when the computation ends on a different thread. |> PreserveAsyncScope // not really needed? |> Node diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 09ab19a86ff..b8f92b7d8be 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -13,7 +13,6 @@ open System.Reflection open System.Threading open Internal.Utilities.Library open Internal.Utilities.Library.Extras -open DiagnosticsThreadStatics /// Represents the style being used to format errors [] @@ -437,31 +436,19 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = logOrCheck "get" - match box DiagnosticsThreadStatics.diagnosticsLogger with - | Null -> - AssertFalseDiagnosticsLogger - | _ -> - DiagnosticsThreadStatics.diagnosticsLogger + DiagnosticsThreadStatics.diagnosticsLogger and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v logOrCheck "set" static member BuildPhaseNC - with get () = - match box DiagnosticsThreadStatics.buildPhase with - | Null -> BuildPhase.DefaultPhase - | _ -> DiagnosticsThreadStatics.buildPhase - and set (v: BuildPhase) = DiagnosticsThreadStatics.buildPhase <- v + with get () = DiagnosticsThreadStatics.buildPhase + and set v = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLoggerNC - with get () = - match box DiagnosticsThreadStatics.diagnosticsLogger with - | Null -> AssertFalseDiagnosticsLogger - | _ -> DiagnosticsThreadStatics.diagnosticsLogger - and set (v: DiagnosticsLogger) = - DiagnosticsThreadStatics.diagnosticsLogger <- v - // logOrCheck "NC set" + with get () = DiagnosticsThreadStatics.diagnosticsLogger + and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v [] module DiagnosticsLoggerExtensions = @@ -612,8 +599,13 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B /// let PreserveAsyncScope computation = async { - use _ = new CompilationGlobalsScope(DiagnosticsThreadStatics.DiagnosticsLogger, DiagnosticsThreadStatics.BuildPhase) - return! computation + let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let buildPhase = DiagnosticsThreadStatics.BuildPhase + try + return! computation + finally + DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsThreadStatics.BuildPhase <- buildPhase } let InitGlobalDiagnostics computation = diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 8d462ae55cf..6c4c327f86a 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -228,7 +228,7 @@ module BuildGraphTests = |> Seq.iter (fun x -> try x.Wait(1000) |> ignore with | :? TimeoutException -> reraise() | _ -> ()) - [] + [] let ``GraphNode created from an already computed result will return it in tryPeekValue`` () = let graphNode = GraphNode.FromResult 1 From 0cbbd65b06c1df87fd39d90670b2a0485dc38474 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Wed, 14 Feb 2024 18:33:11 +0100 Subject: [PATCH 11/43] test showing a problem with NodeCode --- .../BuildGraphTests.fs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 556a9b5bc42..70776032b00 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -237,23 +237,29 @@ module BuildGraphTests = [] - let internal ``NodeCode preserves DiagnosticsThreadStatics`` () = + let ``NodeCode preserves DiagnosticsThreadStatics`` () = let random = let rng = Random() fun n -> rng.Next n - - let job phase _ = node { - do! random 10 |> Async.Sleep |> NodeCode.AwaitAsync - Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) + + let asyncTask expectedPhase = async { + do! Async.Sleep 10 + Assert.Equal(expectedPhase, DiagnosticsThreadStatics.BuildPhase) } - + + let rec job phase i = node { + for i in 1 .. 10 do + Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) + do! asyncTask phase |> NodeCode.AwaitAsync + } + let work (phase: BuildPhase) = node { - use _ = new CompilationGlobalsScope(DiscardErrorsLogger, phase) + DiagnosticsThreadStatics.BuildPhase <- phase let! _ = Seq.init 8 (job phase) |> NodeCode.Parallel Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) } - + let phases = [| BuildPhase.DefaultPhase BuildPhase.Compile @@ -267,9 +273,10 @@ module BuildGraphTests = BuildPhase.Output BuildPhase.Interactive |] - + let pickRandomPhase _ = phases[random phases.Length] - Seq.init 100 pickRandomPhase + + Seq.init 10 pickRandomPhase |> Seq.map (work >> Async.AwaitNodeCode) |> Async.Parallel |> Async.RunSynchronously From 73faf7eae8e53b80f96496d68468dadac28ab9a0 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 09:12:58 +0100 Subject: [PATCH 12/43] fix test --- tests/FSharp.Compiler.UnitTests/HashIfExpression.fs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs index 9f94b7b1c06..5821a3c162e 100644 --- a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs +++ b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs @@ -46,6 +46,13 @@ type public HashIfExpression() = sb.ToString () + let restoreScope = + let logger = DiagnosticsThreadStatics.DiagnosticsLogger + let phase = DiagnosticsThreadStatics.BuildPhase + fun () -> + DiagnosticsThreadStatics.DiagnosticsLogger <- logger + DiagnosticsThreadStatics.BuildPhase <- phase + let createParser () = let errors = ResizeArray() let warnings = ResizeArray() @@ -79,9 +86,7 @@ type public HashIfExpression() = do // Setup DiagnosticsThreadStatics.BuildPhase <- BuildPhase.Compile interface IDisposable with // Teardown - member _.Dispose() = - DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLogger <- DiagnosticsThreadStatics.DiagnosticsLogger + member _.Dispose() = restoreScope() [] member _.PositiveParserTestCases()= From 32454c56cb8415983d21f953dfc21d275e319845 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 09:13:22 +0100 Subject: [PATCH 13/43] more logging --- src/Compiler/Facilities/DiagnosticsLogger.fs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index b8f92b7d8be..ab9b4ca14c8 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -379,21 +379,25 @@ type internal DiagnosticsThreadStatics = static let buildPhaseAsync = new AsyncLocal() static let diagnosticsLoggerAsync = new AsyncLocal() - static let withDefault (holder: AsyncLocal<_>) defaultValue = - match box holder.Value with - | Null -> defaultValue - | _ -> holder.Value + //static let getOrCreateExecutionContextId() = + // match executionContextId.Value with + // | Null -> executionContextId.Value <- ExecutionContext.Capture().GetHashCode #if DEBUG static let changes = System.Collections.Concurrent.ConcurrentStack() - //static let changesAsync = System.Collections.Concurrent.ConcurrentStack() + static let changesByThreadId = System.Collections.Concurrent.ConcurrentDictionary() + static let changesByContext = System.Collections.Concurrent.ConcurrentDictionary() + + let logByThreadId tid str = + changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore + #endif static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() static let logOrCheck prefix = - let al = withDefault diagnosticsLoggerAsync AssertFalseDiagnosticsLogger + let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger #if DEBUG @@ -402,6 +406,9 @@ type internal DiagnosticsThreadStatics = let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" changes.Push str + changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore + changesByContext.AddOrUpdate(ExecutionContext.Capture().GetHashCode(), [str], (fun _ vs -> str :: vs)) |> ignore + #endif ignore prefix From d405abd7908f853fa4ca1ab30b06c7986b6bd9d4 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 09:13:49 +0100 Subject: [PATCH 14/43] fix some tests --- src/Compiler/Service/service.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 035df8cdade..254529f93d3 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -345,7 +345,7 @@ type FSharpChecker async { let ctok = CompilationThreadToken() return CompileHelpers.compileFromArgs (ctok, argv, legacyReferenceResolver, None, None) - } + } |> InitGlobalDiagnostics /// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation. /// For example, the type provider approvals file may have changed. From 36b89ec0e1dc5165bd8c917279ea577bbc898ed2 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 09:14:05 +0100 Subject: [PATCH 15/43] deparallelize a bit for now --- src/Compiler/Service/FSharpCheckerResults.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 045e0aae7a0..752c945fe47 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -3506,7 +3506,7 @@ type FSharpCheckProjectResults | _ -> [||]) |> Array.toSeq | Choice2Of2 task -> - Async.RunSynchronously( + Async.RunImmediate( async { let! tcSymbolUses = task diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index e83a34938d0..8366efca36d 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -1344,7 +1344,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCode |> PreserveAsyncScope - |> Async.RunSynchronously + |> Async.RunImmediate member builder.NotifyFileChanged(fileName, timeStamp) = node { From fbdbc9c0aceae82e370a0c9d2a8880eb3603ca71 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 09:39:21 +0100 Subject: [PATCH 16/43] a lot of nulls in AsyncMemoize.Get, unfortunately --- src/Compiler/Facilities/DiagnosticsLogger.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index ab9b4ca14c8..cecbe7feec9 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -416,7 +416,7 @@ type internal DiagnosticsThreadStatics = | al, ts when al = ts -> () // ThreadStatic likes to be null quite often. We ignore this, as long as it doesn't push diagnostics to null logger. | _, ts when ts = AssertFalseDiagnosticsLogger -> () - | _, Null -> () + //| _, Null -> () | _ -> // Debugger.Break() failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." From f0e59edf73ad1c7a6f9d9313aee9a60ec58d91c4 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 15:29:40 +0100 Subject: [PATCH 17/43] don't pass non-threadsafe logger to parallel computations --- src/Compiler/Facilities/BuildGraph.fs | 35 ++++++++++++------- src/Compiler/Facilities/DiagnosticsLogger.fs | 15 ++++++++ src/Compiler/Facilities/DiagnosticsLogger.fsi | 7 ++++ .../BuildGraphTests.fs | 20 ++++++++--- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 71f4d3da991..ad467bafd31 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -25,6 +25,8 @@ let wrapThreadStaticInfo computation = DiagnosticsThreadStatics.BuildPhase <- phase } +let unwrapNode (Node(computation)) = computation + type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = @@ -193,19 +195,28 @@ type NodeCode private () = } static member Parallel(computations: NodeCode<'T> seq) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + node { + let concurrentLogging = new CaptureDiagnosticsConcurrently() + let phase = DiagnosticsThreadStatics.BuildPhase + // Why does it return just IDisposable? + use _ = concurrentLogging - computations - |> Seq.map (fun (Node x) -> - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - return! x - }) - |> Async.Parallel - |> wrapThreadStaticInfo - |> Node + let injectLogger i computation = + let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") + + node { + use _ = new CompilationGlobalsScope(logger, phase) + return! computation + } + + return! + computations + |> Seq.mapi injectLogger + |> Seq.map unwrapNode + |> Async.Parallel + |> wrapThreadStaticInfo + |> Node + } [] module GraphNode = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 08a46d1a25d..75dfaaef39e 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -13,6 +13,7 @@ open System.Reflection open System.Threading open Internal.Utilities.Library open Internal.Utilities.Library.Extras +open System.Collections.Concurrent /// Represents the style being used to format errors [] @@ -883,3 +884,17 @@ type StackGuard(maxDepth: int, name: string) = static member GetDepthOption(name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth + +type CaptureDiagnosticsConcurrently() = + let target = DiagnosticsThreadStatics.DiagnosticsLogger + let loggers = ResizeArray() + + member _.GetLoggerForTask(name) : DiagnosticsLogger = + let logger = CapturingDiagnosticsLogger(name) + loggers.Add logger + logger + + interface IDisposable with + member _.Dispose() = + for logger in loggers do + logger.CommitDelayedDiagnostics target diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index e9040da36ed..bcbdd197b73 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -463,3 +463,10 @@ type CompilationGlobalsScope = member DiagnosticsLogger: DiagnosticsLogger member BuildPhase: BuildPhase + +type CaptureDiagnosticsConcurrently = + new: unit -> CaptureDiagnosticsConcurrently + + member GetLoggerForTask: string -> DiagnosticsLogger + + interface IDisposable diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 556a9b5bc42..622f2e91e93 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -235,22 +235,34 @@ module BuildGraphTests = Assert.shouldBeTrue graphNode.HasValue Assert.shouldBe (ValueSome 1) (graphNode.TryPeekValue()) - [] let internal ``NodeCode preserves DiagnosticsThreadStatics`` () = let random = let rng = Random() fun n -> rng.Next n - let job phase _ = node { + let job phase i = node { do! random 10 |> Async.Sleep |> NodeCode.AwaitAsync Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) + DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay() + |> Assert.shouldBe $"DiagnosticsLogger(NodeCode.Parallel {i})" + + errorR (System.Exception($"job {i}")) } let work (phase: BuildPhase) = node { - use _ = new CompilationGlobalsScope(DiscardErrorsLogger, phase) - let! _ = Seq.init 8 (job phase) |> NodeCode.Parallel + let n = 8 + let logger = CapturingDiagnosticsLogger("test NodeCode") + use _ = new CompilationGlobalsScope(logger, phase) + let! _ = Seq.init n (job phase) |> NodeCode.Parallel + + let diags = logger.Diagnostics |> List.map fst + + diags |> List.map _.Phase |> Set |> Assert.shouldBe (Set.singleton phase) + diags |> List.map _.Exception.Message + |> Assert.shouldBe (List.init n <| sprintf "job %d") + Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) } From 61d42f9f91e53db9b18f63bed299496adfe7d5c0 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Thu, 15 Feb 2024 17:04:16 +0100 Subject: [PATCH 18/43] warn on AwaitAsync --- src/Compiler/Facilities/BuildGraph.fs | 6 +++++- src/Compiler/Facilities/DiagnosticsLogger.fs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 07a2085457a..9db934b6e8e 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -166,7 +166,11 @@ type NodeCode private () = static member FromCancellable(computation: Cancellable<'T>) = Node(wrapThreadStaticInfo (Cancellable.toAsync computation |> PreserveAsyncScope)) - static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) + static member AwaitAsync(computation: Async<'T>) = + async { + Trace.TraceWarning("NodeCode.AwaitAsync") + return! computation + } |> wrapThreadStaticInfo |> Node static member AwaitTask(task: Task<'T>) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 8c2f6d25d67..727d41e2bf4 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -408,6 +408,7 @@ type internal DiagnosticsThreadStatics = changes.Push str changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore changesByContext.AddOrUpdate(ExecutionContext.Capture().GetHashCode(), [str], (fun _ vs -> str :: vs)) |> ignore + Trace.WriteLine($"t:{tid} {prefix} {dls}") #endif ignore prefix From 729fa6a25a742d5190d97c455e260b4de4451acd Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 00:20:52 +0100 Subject: [PATCH 19/43] fix using --- src/Compiler/Facilities/BuildGraph.fs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 9db934b6e8e..18fa39e6eee 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -106,13 +106,8 @@ type NodeCodeBuilder() = member _.Combine(Node(p1): NodeCode, Node(p2): NodeCode<'T>) : NodeCode<'T> = Node(async.Combine(p1, p2)) [] - member _.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = - Node( - async { - use _ = resource - return! binder resource |> toAsync - } - ) + member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = + async.Using(resource, binder >> toAsync) |> wrapThreadStaticInfo |> Node let node = NodeCodeBuilder() @@ -164,7 +159,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(wrapThreadStaticInfo (Cancellable.toAsync computation |> PreserveAsyncScope)) + Node(Cancellable.toAsync computation) static member AwaitAsync(computation: Async<'T>) = async { @@ -296,7 +291,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! computation |> toAsync |> PreserveAsyncScope + return! computation |> unwrapNode }, (fun res -> cachedResult <- ValueSome res From 3276021a0bb6be255311f0309477b21187770ffc Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 00:21:25 +0100 Subject: [PATCH 20/43] just trace, callstack is useless --- src/Compiler/Facilities/DiagnosticsLogger.fs | 86 +++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 727d41e2bf4..d3c9d949faa 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -383,15 +383,15 @@ type internal DiagnosticsThreadStatics = // match executionContextId.Value with // | Null -> executionContextId.Value <- ExecutionContext.Capture().GetHashCode -#if DEBUG - static let changes = System.Collections.Concurrent.ConcurrentStack() - static let changesByThreadId = System.Collections.Concurrent.ConcurrentDictionary() - static let changesByContext = System.Collections.Concurrent.ConcurrentDictionary() +//#if DEBUG +// static let changes = System.Collections.Concurrent.ConcurrentStack() +// static let changesByThreadId = System.Collections.Concurrent.ConcurrentDictionary() +// static let changesByContext = System.Collections.Concurrent.ConcurrentDictionary() - let logByThreadId tid str = - changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore +// let logByThreadId tid str = +// changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore -#endif +//#endif static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() @@ -401,26 +401,26 @@ type internal DiagnosticsThreadStatics = let ts = DiagnosticsThreadStatics.diagnosticsLogger #if DEBUG - let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" + // let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let tid = Thread.CurrentThread.ManagedThreadId let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" - let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" - changes.Push str - changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore - changesByContext.AddOrUpdate(ExecutionContext.Capture().GetHashCode(), [str], (fun _ vs -> str :: vs)) |> ignore + //let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" + //changes.Push str + //changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore + //changesByContext.AddOrUpdate(ExecutionContext.Capture().GetHashCode(), [str], (fun _ vs -> str :: vs)) |> ignore Trace.WriteLine($"t:{tid} {prefix} {dls}") #endif ignore prefix - match box al, box ts with - | al, ts when al = ts -> () + match al, ts with // ThreadStatic likes to be null quite often. We ignore this, as long as it doesn't push diagnostics to null logger. | _, ts when ts = AssertFalseDiagnosticsLogger -> () - //| _, Null -> () + | _ when box ts |> isNull -> () + | al, ts when al = ts -> () | _ -> - // Debugger.Break() - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + Debugger.Break() + // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -443,20 +443,24 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = - logOrCheck "get" + logOrCheck "->" DiagnosticsThreadStatics.diagnosticsLogger and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v - logOrCheck "set" + logOrCheck "<-" static member BuildPhaseNC with get () = DiagnosticsThreadStatics.buildPhase and set v = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLoggerNC - with get () = DiagnosticsThreadStatics.diagnosticsLogger - and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v + with get () = + logOrCheck "-> NC" + DiagnosticsThreadStatics.diagnosticsLogger + and set v = + DiagnosticsThreadStatics.diagnosticsLogger <- v + logOrCheck "<- NC" [] module DiagnosticsLoggerExtensions = @@ -604,25 +608,6 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B unwindBP.Dispose() unwindEL.Dispose() -/// -let PreserveAsyncScope computation = - async { - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let buildPhase = DiagnosticsThreadStatics.BuildPhase - try - return! computation - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- buildPhase - } - -let InitGlobalDiagnostics computation = - async { - DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLogger <- AssertFalseDiagnosticsLogger - return! computation - } - // Global functions are still used by parser and TAST ops. /// Raises an exception with error recovery and returns unit. @@ -966,6 +951,27 @@ type StackGuard(maxDepth: int, name: string) = static member GetDepthOption(name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth + +/// +let PreserveAsyncScope computation = computation : Async<'T> + //async { + // let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + // let buildPhase = DiagnosticsThreadStatics.BuildPhase + // try + // return! computation + // finally + // DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + // DiagnosticsThreadStatics.BuildPhase <- buildPhase + //} + +let InitGlobalDiagnostics computation = + async { + DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.DiagnosticsLogger <- AssertFalseDiagnosticsLogger + return! computation + } + + type CaptureDiagnosticsConcurrently() = let target = DiagnosticsThreadStatics.DiagnosticsLogger let loggers = ResizeArray() From 73aea2b48d018a4725ab35d40cfba49e33de2586 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 00:21:53 +0100 Subject: [PATCH 21/43] revert --- src/Compiler/Service/IncrementalBuild.fs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 8366efca36d..2f2f34d3493 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -144,7 +144,6 @@ module IncrementalBuildSyntaxTree = Activity.Tags.fileName, fileName Activity.Tags.buildPhase, BuildPhase.Parse.ToString() |] - try let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) // Return the disposable object that cleans up @@ -165,8 +164,7 @@ module IncrementalBuildSyntaxTree = with exn -> let msg = sprintf "unexpected failure in SyntaxTree.parse\nerror = %s" (exn.ToString()) System.Diagnostics.Debug.Assert(false, msg) - failwith msg - return Unchecked.defaultof<_> + return failwith msg } /// Parse the given file and return the given input. @@ -391,7 +389,7 @@ type BoundModel private ( GraphNode.FromResult tcInfo, tcInfoExtras | _ -> // start computing extras, so that typeCheckNode can be GC'd quickly - startComputingFullTypeCheck |> Async.AwaitNodeCode |> PreserveAsyncScope |> Async.Catch |> Async.Ignore |> Async.RunImmediate + startComputingFullTypeCheck |> Async.AwaitNodeCode |> PreserveAsyncScope |> Async.Catch |> Async.Ignore |> Async.Start getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics From 425a1808aa69ff4c91ead8e3aeb45e66b9a20c4d Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 10:01:07 +0100 Subject: [PATCH 22/43] duplicated --- src/Compiler/Service/IncrementalBuild.fs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 2f2f34d3493..a5fae30f792 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -1511,12 +1511,6 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc // // This operation is done when constructing the builder itself, rather than as an incremental task. let nonFrameworkAssemblyInputs = - // Note we are not calling diagnosticsLogger.GetDiagnostics() anywhere for this task. - // This is ok because not much can actually go wrong here. - let diagnosticsLogger = CompilationDiagnosticLogger("nonFrameworkAssemblyInputs", tcConfig.diagnosticsOptions) - // Return the disposable object that cleans up - use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter) - [ for r in nonFrameworkResolutions do let fileName = r.resolvedPath yield (Choice1Of2 fileName, (fun (cache: TimeStampCache) -> cache.GetFileTimeStamp fileName)) From 3b75c590e49a23e24229808705378ce695118b3d Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 10:01:34 +0100 Subject: [PATCH 23/43] restore --- src/Compiler/Facilities/BuildGraph.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 18fa39e6eee..6d9dd177e66 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -159,7 +159,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(Cancellable.toAsync computation) + Node(Cancellable.toAsync computation |> wrapThreadStaticInfo) static member AwaitAsync(computation: Async<'T>) = async { From 2f78f416891dc5ee105c21e4e31accec3c2a4f44 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 10:01:57 +0100 Subject: [PATCH 24/43] fix some tests hopefuly --- .../CompilerService/AsyncMemoize.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 7c252019e2d..201a18da37e 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -433,6 +433,8 @@ type DummyException(msg) = [] let ``Preserve thread static diagnostics`` () = + async.Zero() |> InitGlobalDiagnostics |> Async.RunSynchronously + let seed = System.Random().Next() let rng = System.Random seed @@ -534,6 +536,7 @@ let ``Preserve thread static diagnostics already completed job`` () = Assert.Equal>(["job 1 error"; "job 1 error"], diagnosticMessages) } + |> InitGlobalDiagnostics |> Async.StartAsTask @@ -571,6 +574,7 @@ let ``We get diagnostics from the job that failed`` () = return diagnosticMessages }) |> Async.Parallel + |> InitGlobalDiagnostics |> Async.StartAsTask |> (fun t -> t.Result) |> Array.toList From 4e6022882c5d69a3479fa3bd6485cebb3fe251c4 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 10:02:33 +0100 Subject: [PATCH 25/43] noop for now --- src/Compiler/Facilities/DiagnosticsLogger.fs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index d3c9d949faa..220360e0af8 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -419,8 +419,8 @@ type internal DiagnosticsThreadStatics = | _ when box ts |> isNull -> () | al, ts when al = ts -> () | _ -> - Debugger.Break() - // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + // Debugger.Break() + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -952,8 +952,7 @@ type StackGuard(maxDepth: int, name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth -/// -let PreserveAsyncScope computation = computation : Async<'T> +let PreserveAsyncScope computation = computation : Async<'T> //async { // let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger // let buildPhase = DiagnosticsThreadStatics.BuildPhase From f8a1d3491b08d7d0aa63e45a4c629a70cef31db6 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 11:08:31 +0100 Subject: [PATCH 26/43] remove --- src/Compiler/Facilities/BuildGraph.fs | 4 ++-- src/Compiler/Facilities/DiagnosticsLogger.fs | 16 ++-------------- src/Compiler/Facilities/DiagnosticsLogger.fsi | 4 ---- src/Compiler/Service/BackgroundCompiler.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 5 ++--- src/Compiler/Service/service.fs | 1 - .../CompilerService/AsyncMemoize.fs | 10 ++++++---- 7 files changed, 13 insertions(+), 29 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 6d9dd177e66..7504b06600c 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -190,11 +190,11 @@ type NodeCode private () = let results = ResizeArray() for computation in computations do - let! res = computation |> toAsync |> PreserveAsyncScope + let! res = computation |> toAsync results.Add(res) return results.ToArray() - } |> NodeCode.AwaitAsync + } |> wrapThreadStaticInfo |> Node static member Parallel(computations: NodeCode<'T> seq) = node { diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 220360e0af8..9ec9eaba5b2 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -419,8 +419,8 @@ type internal DiagnosticsThreadStatics = | _ when box ts |> isNull -> () | al, ts when al = ts -> () | _ -> - // Debugger.Break() - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + Debugger.Break() + // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase @@ -951,18 +951,6 @@ type StackGuard(maxDepth: int, name: string) = static member GetDepthOption(name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth - -let PreserveAsyncScope computation = computation : Async<'T> - //async { - // let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - // let buildPhase = DiagnosticsThreadStatics.BuildPhase - // try - // return! computation - // finally - // DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - // DiagnosticsThreadStatics.BuildPhase <- buildPhase - //} - let InitGlobalDiagnostics computation = async { DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index bdbdbc9d4bf..a9edd038811 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -468,8 +468,6 @@ type CompilationGlobalsScope = member BuildPhase: BuildPhase -val PreserveAsyncScope: Async<'T> -> Async<'T> - val InitGlobalDiagnostics: Async<'T> -> Async<'T> type CaptureDiagnosticsConcurrently = @@ -479,6 +477,4 @@ type CaptureDiagnosticsConcurrently = interface IDisposable -val PreserveAsyncScope: Async<'T> -> Async<'T> - val InitGlobalDiagnostics: Async<'T> -> Async<'T> diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 718a53633f2..f9f952dde70 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -1666,7 +1666,7 @@ type internal BackgroundCompiler let options = projectSnapshot.ToOptions() self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode |> PreserveAsyncScope + |> Async.AwaitNodeCode member _.ProjectChecked: IEvent = self.ProjectChecked diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index a5fae30f792..cedec481321 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -148,7 +148,7 @@ module IncrementalBuildSyntaxTree = let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) // Return the disposable object that cleans up use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) - use! text = source.GetTextContainer() |> PreserveAsyncScope |> NodeCode.AwaitAsync + use! text = source.GetTextContainer() |> NodeCode.AwaitAsync let input = match text with | TextContainer.Stream(stream) -> @@ -389,7 +389,7 @@ type BoundModel private ( GraphNode.FromResult tcInfo, tcInfoExtras | _ -> // start computing extras, so that typeCheckNode can be GC'd quickly - startComputingFullTypeCheck |> Async.AwaitNodeCode |> PreserveAsyncScope |> Async.Catch |> Async.Ignore |> Async.Start + startComputingFullTypeCheck |> Async.AwaitNodeCode |> Async.Catch |> Async.Ignore |> Async.Start getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -1341,7 +1341,6 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCode - |> PreserveAsyncScope |> Async.RunImmediate member builder.NotifyFileChanged(fileName, timeStamp) = diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 254529f93d3..5f5007813f0 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -498,7 +498,6 @@ type FSharpChecker node { let! parseResults = backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName) - |> PreserveAsyncScope |> NodeCode.AwaitAsync if diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 201a18da37e..8abf0b95cc9 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -56,6 +56,9 @@ type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(mem member _.Sequence = events |> Seq.map id +type internal InitTestDiagnostics() = + inherit CompilationGlobalsScope(CapturingDiagnosticsLogger("Asynmemoize tests"), BuildPhase.DefaultPhase) + [] let ``Basics``() = @@ -560,21 +563,20 @@ let ``We get diagnostics from the job that failed`` () = let result = [1; 2] |> Seq.map (fun i -> - async { + node { let diagnosticsLogger = CompilationDiagnosticLogger($"Testing", FSharpDiagnosticOptions.Default) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) try - let! _ = cache.Get(key, job i ) |> Async.AwaitNodeCode + let! _ = cache.Get(key, job i ) () with _ -> () let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList return diagnosticMessages - }) + } |> Async.AwaitNodeCode) |> Async.Parallel - |> InitGlobalDiagnostics |> Async.StartAsTask |> (fun t -> t.Result) |> Array.toList From 3efb96e8cc571d99ea254e7de75db28ea546f322 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 11:25:42 +0100 Subject: [PATCH 27/43] fix --- src/Compiler/Facilities/BuildGraph.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 7504b06600c..4a2553a8b11 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -107,7 +107,7 @@ type NodeCodeBuilder() = [] member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = - async.Using(resource, binder >> toAsync) |> wrapThreadStaticInfo |> Node + async.Using(resource, binder >> toAsync) |> Node let node = NodeCodeBuilder() From 9ec30c38915fef731f711138382b5e7fae54e8ed Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 12:01:54 +0100 Subject: [PATCH 28/43] better Parallel --- src/Compiler/Facilities/BuildGraph.fs | 8 ++++---- src/Compiler/Facilities/DiagnosticsLogger.fs | 3 ++- tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs | 4 +++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 4a2553a8b11..c274807787a 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -206,15 +206,15 @@ type NodeCode private () = let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") - node { - use _ = new CompilationGlobalsScope(logger, phase) - return! computation + async { + DiagnosticsThreadStatics.DiagnosticsLogger <- logger + DiagnosticsThreadStatics.BuildPhase <- phase + return! unwrapNode computation } return! computations |> Seq.mapi injectLogger - |> Seq.map unwrapNode |> Async.Parallel |> wrapThreadStaticInfo |> Node diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 9ec9eaba5b2..b994b1076b5 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -333,7 +333,8 @@ type DiagnosticsLogger(nameForDebugging: string) = member x.CheckForErrors() = (x.ErrorCount > 0) - member _.DebugDisplay() = nameForDebugging + member _.DebugDisplay() = + sprintf "DiagnosticsLogger(%s)" nameForDebugging let DiscardErrorsLogger = { new DiagnosticsLogger("DiscardErrorsLogger") with diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index ed3806bedeb..918a9702114 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -235,6 +235,8 @@ module BuildGraphTests = Assert.shouldBeTrue graphNode.HasValue Assert.shouldBe (ValueSome 1) (graphNode.TryPeekValue()) + type ExampleException(msg) = inherit System.Exception(msg) + [] let ``NodeCode preserves DiagnosticsThreadStatics`` () = let random = @@ -247,7 +249,7 @@ module BuildGraphTests = DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay() |> Assert.shouldBe $"DiagnosticsLogger(NodeCode.Parallel {i})" - errorR (System.Exception($"job {i}")) + errorR (ExampleException($"job {i}")) } let work (phase: BuildPhase) = From 684855970989d7b927aab720a9a862286dabe7e5 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 12:02:19 +0100 Subject: [PATCH 29/43] wip --- src/Compiler/Service/IncrementalBuild.fs | 87 +++++++++++++----------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index cedec481321..ec7b2362340 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -256,49 +256,54 @@ type BoundModel private ( use _ = Activity.start "BoundModel.TypeCheck" [|Activity.Tags.fileName, fileName|] IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked fileName) - let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck") - let diagnosticsLogger = GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger) - use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck) - beforeFileChecked.Trigger fileName + let! wrapper = node { + let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck") + use scope = new CompilationGlobalsScope( + GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger), + BuildPhase.TypeCheck) + + beforeFileChecked.Trigger fileName - ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider) |> ignore - let sink = TcResultsSinkImpl(tcGlobals) - let hadParseErrors = not (Array.isEmpty parseErrors) - let input, moduleNamesDict = DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input - - let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = - CheckOneInput ( - (fun () -> hadParseErrors || diagnosticsLogger.ErrorCount > 0), - tcConfig, tcImports, - tcGlobals, - None, - TcResultsSink.WithSink sink, - prevTcInfo.tcState, input ) - |> NodeCode.FromCancellable - - fileChecked.Trigger fileName - - let newErrors = Array.append parseErrors (capturingDiagnosticsLogger.Diagnostics |> List.toArray) - let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls - - let tcInfo = - { - tcState = tcState - tcEnvAtEndOfFile = tcEnvAtEndOfFile - moduleNamesDict = moduleNamesDict - latestCcuSigForFile = Some ccuSigForFile - tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev - topAttribs = Some topAttribs - tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles - sigNameOpt = - match input with - | ParsedInput.SigFile sigFile -> - Some(sigFile.FileName, sigFile.QualifiedName) - | _ -> - None - } - return tcInfo, sink, implFile, fileName, newErrors + ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider) |> ignore + let sink = TcResultsSinkImpl(tcGlobals) + let hadParseErrors = not (Array.isEmpty parseErrors) + let input, moduleNamesDict = DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input + + let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = + CheckOneInput ( + (fun () -> hadParseErrors || scope.DiagnosticsLogger.ErrorCount > 0), + tcConfig, tcImports, + tcGlobals, + None, + TcResultsSink.WithSink sink, + prevTcInfo.tcState, input ) + |> NodeCode.FromCancellable + + fileChecked.Trigger fileName + + let newErrors = Array.append parseErrors (capturingDiagnosticsLogger.Diagnostics |> List.toArray) + let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls + + let tcInfo = + { + tcState = tcState + tcEnvAtEndOfFile = tcEnvAtEndOfFile + moduleNamesDict = moduleNamesDict + latestCcuSigForFile = Some ccuSigForFile + tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev + topAttribs = Some topAttribs + tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles + sigNameOpt = + match input with + | ParsedInput.SigFile sigFile -> + Some(sigFile.FileName, sigFile.QualifiedName) + | _ -> + None + } + return tcInfo, sink, implFile, fileName, newErrors + } + return wrapper } let skippedImplemetationTypeCheck = From 0697453acf9d0ba57c2cd40dbb85503b4381e14c Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 13:58:30 +0100 Subject: [PATCH 30/43] wip --- src/Compiler/Facilities/BuildGraph.fs | 15 ++++++++++----- src/Compiler/Facilities/DiagnosticsLogger.fs | 6 +++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index c274807787a..d8ecc7c51fa 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -197,9 +197,10 @@ type NodeCode private () = } |> wrapThreadStaticInfo |> Node static member Parallel(computations: NodeCode<'T> seq) = - node { + async { let concurrentLogging = new CaptureDiagnosticsConcurrently() let phase = DiagnosticsThreadStatics.BuildPhase + let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger // Why does it return just IDisposable? use _ = concurrentLogging @@ -209,16 +210,20 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLogger <- logger DiagnosticsThreadStatics.BuildPhase <- phase - return! unwrapNode computation + try + return! unwrapNode computation + finally + // only needed because of NodeCode attaching continuations here + DiagnosticsThreadStatics.DiagnosticsLogger <- ambientLogger + DiagnosticsThreadStatics.BuildPhase <- phase } return! computations |> Seq.mapi injectLogger |> Async.Parallel - |> wrapThreadStaticInfo - |> Node - } + + } |> wrapThreadStaticInfo |> Node [] module GraphNode = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index b994b1076b5..db066355e00 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -404,7 +404,7 @@ type internal DiagnosticsThreadStatics = #if DEBUG // let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let tid = Thread.CurrentThread.ManagedThreadId - let dls = if box al = box ts then dlName al else $"DIVERGED AsyncLocal: {dlName al}, ThreadStatic: {dlName ts}" + let dls = if box al = box ts then dlName al else $"DIVERGED \nAsyncLocal: {dlName al}\nThreadStatic: {dlName ts}" //let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" //changes.Push str //changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore @@ -420,8 +420,8 @@ type internal DiagnosticsThreadStatics = | _ when box ts |> isNull -> () | al, ts when al = ts -> () | _ -> - Debugger.Break() - // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + // Debugger.Break() + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." [] static val mutable private buildPhase: BuildPhase From 3ea30060be3b0de64898819a1a45a3d09a8b1eca Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Fri, 16 Feb 2024 19:41:09 +0100 Subject: [PATCH 31/43] wip --- src/Compiler/Facilities/BuildGraph.fs | 48 +++++++------------ src/Compiler/Facilities/DiagnosticsLogger.fs | 37 ++------------ src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 - src/Compiler/Service/BackgroundCompiler.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 8 ++-- .../BuildGraphTests.fs | 21 ++++---- 6 files changed, 40 insertions(+), 78 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index d8ecc7c51fa..40bf9446568 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -13,15 +13,6 @@ open Internal.Utilities.Library [] type NodeCode<'T> = Node of Async<'T> -//let restoreThreadStaticInfo() = -// let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC -// let phase = DiagnosticsThreadStatics.BuildPhaseNC - -// { new IDisposable with -// member _.Dispose() = -// DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger -// DiagnosticsThreadStatics.BuildPhaseNC <- phase } - let wrapThreadStaticInfo computation = async { let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNC @@ -36,8 +27,6 @@ let wrapThreadStaticInfo computation = let unwrapNode (Node(computation)) = computation -let toAsync (Node(wrapped)) = wrapThreadStaticInfo wrapped - type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = @@ -107,7 +96,12 @@ type NodeCodeBuilder() = [] member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = - async.Using(resource, binder >> toAsync) |> Node + async.Using(resource, binder >> unwrapNode) |> Node + //this.Delay( fun () -> + // Node( + // async.Using(resource, binder >> unwrapNode) + // ) + //) let node = NodeCodeBuilder() @@ -126,7 +120,7 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! computation |> toAsync + return! unwrapNode computation } Async.StartImmediateAsTask(work, cancellationToken = ct).Result @@ -148,7 +142,7 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLoggerNC <- diagnosticsLogger DiagnosticsThreadStatics.BuildPhaseNC <- phase - return! computation |> toAsync + return! unwrapNode computation } Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None) @@ -159,7 +153,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(Cancellable.toAsync computation |> wrapThreadStaticInfo) + Node(computation |> Cancellable.toAsync |> wrapThreadStaticInfo) static member AwaitAsync(computation: Async<'T>) = async { @@ -173,12 +167,6 @@ type NodeCode private () = static member AwaitTask(task: Task) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) - static member AwaitTaskWithoutWrapping<'T>(task: Task<'T>) = - Node((Async.AwaitTask task)) - - static member AwaitTaskWithoutWrapping(task: Task) = - Node((Async.AwaitTask task)) - static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) @@ -186,23 +174,21 @@ type NodeCode private () = Node(wrapThreadStaticInfo (Async.Sleep(ms))) static member Sequential(computations: NodeCode<'T> seq) = - async { + node { let results = ResizeArray() for computation in computations do - let! res = computation |> toAsync + let! res = computation results.Add(res) return results.ToArray() - } |> wrapThreadStaticInfo |> Node + } static member Parallel(computations: NodeCode<'T> seq) = async { - let concurrentLogging = new CaptureDiagnosticsConcurrently() let phase = DiagnosticsThreadStatics.BuildPhase let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger - // Why does it return just IDisposable? - use _ = concurrentLogging + use concurrentLogging = new CaptureDiagnosticsConcurrently() let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") @@ -213,7 +199,6 @@ type NodeCode private () = try return! unwrapNode computation finally - // only needed because of NodeCode attaching continuations here DiagnosticsThreadStatics.DiagnosticsLogger <- ambientLogger DiagnosticsThreadStatics.BuildPhase <- phase } @@ -222,8 +207,9 @@ type NodeCode private () = computations |> Seq.mapi injectLogger |> Async.Parallel + |> wrapThreadStaticInfo - } |> wrapThreadStaticInfo |> Node + } |> Node [] module GraphNode = @@ -286,7 +272,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ||| TaskContinuationOptions.NotOnFaulted ||| TaskContinuationOptions.ExecuteSynchronously) ) - |> NodeCode.AwaitTaskWithoutWrapping + |> NodeCode.AwaitTask match cachedResult with | ValueSome value -> return value @@ -308,7 +294,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ct ) - return! tcs.Task |> NodeCode.AwaitTaskWithoutWrapping + return! tcs.Task |> NodeCode.AwaitTask finally if taken then semaphore.Release() |> ignore diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index db066355e00..a3bbec834c0 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -380,46 +380,19 @@ type internal DiagnosticsThreadStatics = static let buildPhaseAsync = new AsyncLocal() static let diagnosticsLoggerAsync = new AsyncLocal() - //static let getOrCreateExecutionContextId() = - // match executionContextId.Value with - // | Null -> executionContextId.Value <- ExecutionContext.Capture().GetHashCode - -//#if DEBUG -// static let changes = System.Collections.Concurrent.ConcurrentStack() -// static let changesByThreadId = System.Collections.Concurrent.ConcurrentDictionary() -// static let changesByContext = System.Collections.Concurrent.ConcurrentDictionary() - -// let logByThreadId tid str = -// changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore - -//#endif - - static let dlName (dl: DiagnosticsLogger) = - if box dl |> isNull then "NULL" else dl.DebugDisplay() static let logOrCheck prefix = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger - + let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() #if DEBUG - // let stack = StackTrace(2, true).GetFrames() |> Seq.map _.ToString() |> String.concat "\t" let tid = Thread.CurrentThread.ManagedThreadId - let dls = if box al = box ts then dlName al else $"DIVERGED \nAsyncLocal: {dlName al}\nThreadStatic: {dlName ts}" - //let str = $"t:{tid} {prefix} %-300s{dls} \n\n\t{stack}" - //changes.Push str - //changesByThreadId.AddOrUpdate(tid, [str], (fun _ vs -> str :: vs)) |> ignore - //changesByContext.AddOrUpdate(ExecutionContext.Capture().GetHashCode(), [str], (fun _ vs -> str :: vs)) |> ignore + let dls = if box al = box ts then dlName al else $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" Trace.WriteLine($"t:{tid} {prefix} {dls}") - #endif ignore prefix - match al, ts with - // ThreadStatic likes to be null quite often. We ignore this, as long as it doesn't push diagnostics to null logger. - | _, ts when ts = AssertFalseDiagnosticsLogger -> () - | _ when box ts |> isNull -> () - | al, ts when al = ts -> () - | _ -> + if al <> ts then // Debugger.Break() failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." @@ -457,11 +430,11 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = - logOrCheck "-> NC" + //logOrCheck "-> NC" DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v - logOrCheck "<- NC" + //logOrCheck "<- NC" [] module DiagnosticsLoggerExtensions = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index a9edd038811..229c8fc0edf 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -476,5 +476,3 @@ type CaptureDiagnosticsConcurrently = member GetLoggerForTask: string -> DiagnosticsLogger interface IDisposable - -val InitGlobalDiagnostics: Async<'T> -> Async<'T> diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 54bea5584ad..30caa56e400 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -1386,7 +1386,7 @@ type internal BackgroundCompiler return options, (diags @ diagnostics.Diagnostics) } - |> Cancellable.toAsync + |> Cancellable.toAsync |> InitGlobalDiagnostics member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) = use _ = diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index ec7b2362340..529e78d5b93 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -144,10 +144,12 @@ module IncrementalBuildSyntaxTree = Activity.Tags.fileName, fileName Activity.Tags.buildPhase, BuildPhase.Parse.ToString() |] + + let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) + // Return the disposable object that cleans up + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) + try - let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) - // Return the disposable object that cleans up - use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) use! text = source.GetTextContainer() |> NodeCode.AwaitAsync let input = match text with diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 918a9702114..9dbcd00b711 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -246,18 +246,18 @@ module BuildGraphTests = let job phase i = node { do! random 10 |> Async.Sleep |> NodeCode.AwaitAsync Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) - DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay() - |> Assert.shouldBe $"DiagnosticsLogger(NodeCode.Parallel {i})" + //DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay() + //|> Assert.shouldBe $"DiagnosticsLogger(NodeCode.Parallel {i})" errorR (ExampleException($"job {i}")) } - let work (phase: BuildPhase) = + let work method (phase: BuildPhase) = node { let n = 8 let logger = CapturingDiagnosticsLogger("test NodeCode") use _ = new CompilationGlobalsScope(logger, phase) - let! _ = Seq.init n (job phase) |> NodeCode.Parallel + let! _ = Seq.init n (job phase) |> method let diags = logger.Diagnostics |> List.map fst @@ -283,8 +283,11 @@ module BuildGraphTests = |] let pickRandomPhase _ = phases[random phases.Length] - - Seq.init 10 pickRandomPhase - |> Seq.map (work >> Async.AwaitNodeCode) - |> Async.Parallel - |> Async.RunSynchronously + + let run method = + Seq.init 10 pickRandomPhase + |> Seq.map (work method >> Async.AwaitNodeCode) + |> Async.Parallel + + [ NodeCode.Parallel; NodeCode.Sequential ] |> List.map run |> Async.Parallel |> Async.RunImmediate + From 2931c57db40783fab7baee5f15538f0d15f53c63 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sat, 17 Feb 2024 11:13:28 +0100 Subject: [PATCH 32/43] check only in getter --- src/Compiler/Facilities/DiagnosticsLogger.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index a3bbec834c0..1f5d5c210bf 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -422,7 +422,7 @@ type internal DiagnosticsThreadStatics = and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v - logOrCheck "<-" + // logOrCheck "<-" static member BuildPhaseNC with get () = DiagnosticsThreadStatics.buildPhase From 2524df60ce73262977610d2b3abb52f5edfcfe84 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sat, 17 Feb 2024 11:39:12 +0100 Subject: [PATCH 33/43] init logger for parse --- src/Compiler/Service/BackgroundCompiler.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 30caa56e400..0caa41b12e5 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -566,7 +566,7 @@ type internal BackgroundCompiler flatErrors: bool, userOpName: string ) = - async { + node { use _ = Activity.start "BackgroundCompiler.ParseFile" @@ -583,7 +583,7 @@ type internal BackgroundCompiler | Some res -> return res | None -> Interlocked.Increment(&actualParseFileCount) |> ignore - let! ct = Async.CancellationToken + let! ct = NodeCode.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -603,7 +603,7 @@ type internal BackgroundCompiler parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (fileName, hash, options), res)) return res else - let! ct = Async.CancellationToken + let! ct = NodeCode.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -1666,7 +1666,7 @@ type internal BackgroundCompiler flatErrors: bool, userOpName: string ) = - self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) + self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) |> Async.AwaitNodeCode member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) = let options = projectSnapshot.ToOptions() From 3802de6acc6e5bfe63f824fe5eca0ec765f6d35e Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sat, 17 Feb 2024 11:39:12 +0100 Subject: [PATCH 34/43] wip --- src/Compiler/Driver/ParseAndCheckInputs.fs | 31 ++++++++++++------- src/Compiler/Facilities/BuildGraph.fs | 7 ++++- src/Compiler/Facilities/DiagnosticsLogger.fs | 27 ++++++++-------- src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 -- src/Compiler/Service/BackgroundCompiler.fs | 10 +++--- src/Compiler/Service/service.fs | 5 ++- .../CompilerService/AsyncMemoize.fs | 3 -- 7 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 5a23c95ca7b..bed6c6a4028 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -42,6 +42,7 @@ open FSharp.Compiler.TypedTree open FSharp.Compiler.TypedTreeOps open FSharp.Compiler.TypedTreeBasics open FSharp.Compiler.TcGlobals +open FSharp.Compiler.BuildGraph let CanonicalizeFilename fileName = let basic = FileSystemUtils.fileNameOfPath fileName @@ -811,23 +812,31 @@ let UseMultipleDiagnosticLoggers (inputs, diagnosticsLogger, eagerFormat) f = logger.CommitDelayedDiagnostics diagnosticsLogger let ParseInputFilesInParallel (tcConfig: TcConfig, lexResourceManager, sourceFiles, delayLogger: DiagnosticsLogger, retryLocked) = + node { + use _ = UseDiagnosticsLogger delayLogger - let isLastCompiland, isExe = sourceFiles |> tcConfig.ComputeCanContainEntryPoint + let isLastCompiland, isExe = sourceFiles |> tcConfig.ComputeCanContainEntryPoint - for fileName in sourceFiles do - checkInputFile tcConfig fileName + for fileName in sourceFiles do + checkInputFile tcConfig fileName - let sourceFiles = List.zip sourceFiles isLastCompiland + let sourceFiles = List.zip sourceFiles isLastCompiland - UseMultipleDiagnosticLoggers (sourceFiles, delayLogger, None) (fun sourceFilesWithDelayLoggers -> - sourceFilesWithDelayLoggers - |> ListParallel.map (fun ((fileName, isLastCompiland), delayLogger) -> - let directoryName = Path.GetDirectoryName fileName + let! result = + sourceFiles + |> Seq.map (fun (fileName, isLastCompiland) -> node { + let directoryName = Path.GetDirectoryName fileName - let input = - parseInputFileAux (tcConfig, lexResourceManager, fileName, (isLastCompiland, isExe), delayLogger, retryLocked) + let input = + parseInputFileAux (tcConfig, lexResourceManager, fileName, (isLastCompiland, isExe), DiagnosticsThreadStatics.DiagnosticsLogger, retryLocked) - (input, directoryName))) + return (input, directoryName) + }) + |> NodeCode.Parallel + return result |> Array.toList + } + |> Async.AwaitNodeCode + |> Async.RunImmediate let ParseInputFilesSequential (tcConfig: TcConfig, lexResourceManager, sourceFiles, diagnosticsLogger: DiagnosticsLogger, retryLocked) = let isLastCompiland, isExe = sourceFiles |> tcConfig.ComputeCanContainEntryPoint diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 40bf9446568..fb30cad9100 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -31,7 +31,12 @@ type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = match node with - | Node(computation) -> InitGlobalDiagnostics computation + | Node(computation) -> + async { + SetThreadDiagnosticsLoggerNoUnwind AssertFalseDiagnosticsLogger + SetThreadBuildPhaseNoUnwind BuildPhase.DefaultPhase + return! computation + } [] type NodeCodeBuilder() = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 1f5d5c210bf..7c10e89ec3e 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -392,9 +392,12 @@ type internal DiagnosticsThreadStatics = #endif ignore prefix - if al <> ts then - // Debugger.Break() + if al <> ts then + #if DEBUG + Debugger.Break() + #else failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + #endif [] static val mutable private buildPhase: BuildPhase @@ -422,7 +425,9 @@ type internal DiagnosticsThreadStatics = and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v - // logOrCheck "<-" + #if DEBUG + logOrCheck "<-" + #endif static member BuildPhaseNC with get () = DiagnosticsThreadStatics.buildPhase @@ -430,11 +435,15 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = - //logOrCheck "-> NC" + #if DEBUG + logOrCheck "-> NC" + #endif DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v - //logOrCheck "<- NC" + #if DEBUG + logOrCheck "<- NC" + #endif [] module DiagnosticsLoggerExtensions = @@ -925,14 +934,6 @@ type StackGuard(maxDepth: int, name: string) = static member GetDepthOption(name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth -let InitGlobalDiagnostics computation = - async { - DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLogger <- AssertFalseDiagnosticsLogger - return! computation - } - - type CaptureDiagnosticsConcurrently() = let target = DiagnosticsThreadStatics.DiagnosticsLogger let loggers = ResizeArray() diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 229c8fc0edf..399b94d69c0 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -468,8 +468,6 @@ type CompilationGlobalsScope = member BuildPhase: BuildPhase -val InitGlobalDiagnostics: Async<'T> -> Async<'T> - type CaptureDiagnosticsConcurrently = new: unit -> CaptureDiagnosticsConcurrently diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index 30caa56e400..a87e275e430 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -566,7 +566,7 @@ type internal BackgroundCompiler flatErrors: bool, userOpName: string ) = - async { + node { use _ = Activity.start "BackgroundCompiler.ParseFile" @@ -583,7 +583,7 @@ type internal BackgroundCompiler | Some res -> return res | None -> Interlocked.Increment(&actualParseFileCount) |> ignore - let! ct = Async.CancellationToken + let! ct = NodeCode.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -603,7 +603,7 @@ type internal BackgroundCompiler parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (fileName, hash, options), res)) return res else - let! ct = Async.CancellationToken + let! ct = NodeCode.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -1386,7 +1386,7 @@ type internal BackgroundCompiler return options, (diags @ diagnostics.Diagnostics) } - |> Cancellable.toAsync |> InitGlobalDiagnostics + |> Cancellable.toAsync member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) = use _ = @@ -1666,7 +1666,7 @@ type internal BackgroundCompiler flatErrors: bool, userOpName: string ) = - self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) + self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) |> Async.AwaitNodeCode member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) = let options = projectSnapshot.ToOptions() diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index cde7e299d7c..4276d914f83 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -245,6 +245,9 @@ type FSharpChecker let parallelReferenceResolution = inferParallelReferenceResolution parallelReferenceResolution + SetThreadDiagnosticsLoggerNoUnwind AssertFalseDiagnosticsLogger + SetThreadBuildPhaseNoUnwind BuildPhase.DefaultPhase + FSharpChecker( legacyReferenceResolver, projectCacheSizeReal, @@ -345,7 +348,7 @@ type FSharpChecker async { let ctok = CompilationThreadToken() return CompileHelpers.compileFromArgs (ctok, argv, legacyReferenceResolver, None, None) - } |> InitGlobalDiagnostics + } /// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation. /// For example, the type provider approvals file may have changed. diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 8abf0b95cc9..8a2d3839061 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -436,8 +436,6 @@ type DummyException(msg) = [] let ``Preserve thread static diagnostics`` () = - async.Zero() |> InitGlobalDiagnostics |> Async.RunSynchronously - let seed = System.Random().Next() let rng = System.Random seed @@ -539,7 +537,6 @@ let ``Preserve thread static diagnostics already completed job`` () = Assert.Equal>(["job 1 error"; "job 1 error"], diagnosticMessages) } - |> InitGlobalDiagnostics |> Async.StartAsTask From 9ad968964a7c295050236d12b79ef15405e330ff Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sat, 17 Feb 2024 21:52:53 +0100 Subject: [PATCH 35/43] wip --- src/Compiler/Facilities/BuildGraph.fs | 12 +-- src/Compiler/Facilities/DiagnosticsLogger.fs | 51 +++++++---- src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 + src/Compiler/Service/FSharpCheckerResults.fs | 2 +- src/Compiler/Service/IncrementalBuild.fs | 91 +++++++++---------- src/Compiler/Service/service.fs | 3 - .../xunit.runner.json | 6 +- 7 files changed, 88 insertions(+), 79 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index fb30cad9100..b3481bb602f 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -31,10 +31,9 @@ type Async<'T> with static member AwaitNodeCode(node: NodeCode<'T>) = match node with - | Node(computation) -> + | Node(computation) -> async { - SetThreadDiagnosticsLoggerNoUnwind AssertFalseDiagnosticsLogger - SetThreadBuildPhaseNoUnwind BuildPhase.DefaultPhase + DiagnosticsThreadStatics.InitGlobals() return! computation } @@ -102,11 +101,6 @@ type NodeCodeBuilder() = [] member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = async.Using(resource, binder >> unwrapNode) |> Node - //this.Delay( fun () -> - // Node( - // async.Using(resource, binder >> unwrapNode) - // ) - //) let node = NodeCodeBuilder() @@ -187,7 +181,7 @@ type NodeCode private () = results.Add(res) return results.ToArray() - } + } static member Parallel(computations: NodeCode<'T> seq) = async { diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 7c10e89ec3e..4af2ebf6aea 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -377,27 +377,35 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = - static let buildPhaseAsync = new AsyncLocal() - static let diagnosticsLoggerAsync = new AsyncLocal() + static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() + static let buildPhaseAsync = new AsyncLocal() + static let diagnosticsLoggerAsync = new AsyncLocal(fun args -> + if args.ThreadContextChanged then + Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} prev: {dlName args.PreviousValue}, current: {dlName args.CurrentValue}, THREAD CONTEXT CHANGED")) - static let logOrCheck prefix = + static let check() = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger - let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() + match box al, box ts with + | Null, _ -> () // New context started. + | _, Null -> () // ?? + | a, t when not <| a.Equals(t) -> // Not good. + #if DEBUG + Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") + #else + Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") + // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + #endif + | _ -> () #if DEBUG + static let log prefix = + let al = diagnosticsLoggerAsync.Value + let ts = DiagnosticsThreadStatics.diagnosticsLogger let tid = Thread.CurrentThread.ManagedThreadId let dls = if box al = box ts then dlName al else $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" Trace.WriteLine($"t:{tid} {prefix} {dls}") #endif - ignore prefix - - if al <> ts then - #if DEBUG - Debugger.Break() - #else - failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." - #endif [] static val mutable private buildPhase: BuildPhase @@ -420,13 +428,16 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = - logOrCheck "->" + #if DEBUG + log "->" + #endif + check() DiagnosticsThreadStatics.diagnosticsLogger and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v #if DEBUG - logOrCheck "<-" + log "<-" #endif static member BuildPhaseNC @@ -436,18 +447,24 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = #if DEBUG - logOrCheck "-> NC" + log "-> NC" #endif DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v #if DEBUG - logOrCheck "<- NC" + log "<- NC" #endif + static member InitGlobals() = + Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} INIT GLOBAL DIAGNOSTICS") + buildPhaseAsync.Value <- BuildPhase.DefaultPhase + diagnosticsLoggerAsync.Value <- DiscardErrorsLogger + DiagnosticsThreadStatics.buildPhase <- BuildPhase.DefaultPhase + DiagnosticsThreadStatics.diagnosticsLogger <- DiscardErrorsLogger + [] module DiagnosticsLoggerExtensions = - // Dev15.0 shipped with a bug in diasymreader in the portable pdb symbol reader which causes an AV // This uses a simple heuristic to detect it (the vsversion is < 16.0) let tryAndDetectDev15 = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 399b94d69c0..8ec3cdaaeca 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -240,6 +240,8 @@ type DiagnosticsThreadStatics = static member DiagnosticsLoggerNC: DiagnosticsLogger with get, set + static member InitGlobals: unit -> unit + [] module DiagnosticsLoggerExtensions = diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 752c945fe47..045e0aae7a0 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -3506,7 +3506,7 @@ type FSharpCheckProjectResults | _ -> [||]) |> Array.toSeq | Choice2Of2 task -> - Async.RunImmediate( + Async.RunSynchronously( async { let! tcSymbolUses = task diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 529e78d5b93..f2096ed2d41 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -259,53 +259,52 @@ type BoundModel private ( IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked fileName) - let! wrapper = node { - let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck") - use scope = new CompilationGlobalsScope( - GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger), - BuildPhase.TypeCheck) - beforeFileChecked.Trigger fileName + let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck") + use scope = new CompilationGlobalsScope( + GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger), + BuildPhase.TypeCheck) + + beforeFileChecked.Trigger fileName - ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider) |> ignore - let sink = TcResultsSinkImpl(tcGlobals) - let hadParseErrors = not (Array.isEmpty parseErrors) - let input, moduleNamesDict = DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input - - let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = - CheckOneInput ( - (fun () -> hadParseErrors || scope.DiagnosticsLogger.ErrorCount > 0), - tcConfig, tcImports, - tcGlobals, - None, - TcResultsSink.WithSink sink, - prevTcInfo.tcState, input ) - |> NodeCode.FromCancellable - - fileChecked.Trigger fileName - - let newErrors = Array.append parseErrors (capturingDiagnosticsLogger.Diagnostics |> List.toArray) - let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls - - let tcInfo = - { - tcState = tcState - tcEnvAtEndOfFile = tcEnvAtEndOfFile - moduleNamesDict = moduleNamesDict - latestCcuSigForFile = Some ccuSigForFile - tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev - topAttribs = Some topAttribs - tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles - sigNameOpt = - match input with - | ParsedInput.SigFile sigFile -> - Some(sigFile.FileName, sigFile.QualifiedName) - | _ -> - None - } - return tcInfo, sink, implFile, fileName, newErrors - } - return wrapper + ApplyMetaCommandsFromInputToTcConfig (tcConfig, input, Path.GetDirectoryName fileName, tcImports.DependencyProvider) |> ignore + let sink = TcResultsSinkImpl(tcGlobals) + let hadParseErrors = not (Array.isEmpty parseErrors) + let input, moduleNamesDict = DeduplicateParsedInputModuleName prevTcInfo.moduleNamesDict input + + let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = + CheckOneInput ( + (fun () -> hadParseErrors || scope.DiagnosticsLogger.ErrorCount > 0), + tcConfig, tcImports, + tcGlobals, + None, + TcResultsSink.WithSink sink, + prevTcInfo.tcState, input ) + |> NodeCode.FromCancellable + + fileChecked.Trigger fileName + + let newErrors = Array.append parseErrors (capturingDiagnosticsLogger.Diagnostics |> List.toArray) + let tcEnvAtEndOfFile = if keepAllBackgroundResolutions then tcEnvAtEndOfFile else tcState.TcEnvFromImpls + + let tcInfo = + { + tcState = tcState + tcEnvAtEndOfFile = tcEnvAtEndOfFile + moduleNamesDict = moduleNamesDict + latestCcuSigForFile = Some ccuSigForFile + tcDiagnosticsRev = newErrors :: prevTcInfo.tcDiagnosticsRev + topAttribs = Some topAttribs + tcDependencyFiles = fileName :: prevTcInfo.tcDependencyFiles + sigNameOpt = + match input with + | ParsedInput.SigFile sigFile -> + Some(sigFile.FileName, sigFile.QualifiedName) + | _ -> + None + } + return tcInfo, sink, implFile, fileName, newErrors + } let skippedImplemetationTypeCheck = @@ -1348,7 +1347,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCode - |> Async.RunImmediate + |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = node { diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 4276d914f83..82b43827e1f 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -245,9 +245,6 @@ type FSharpChecker let parallelReferenceResolution = inferParallelReferenceResolution parallelReferenceResolution - SetThreadDiagnosticsLoggerNoUnwind AssertFalseDiagnosticsLogger - SetThreadBuildPhaseNoUnwind BuildPhase.DefaultPhase - FSharpChecker( legacyReferenceResolver, projectCacheSizeReal, diff --git a/tests/FSharp.Compiler.UnitTests/xunit.runner.json b/tests/FSharp.Compiler.UnitTests/xunit.runner.json index 743febb7028..de930b44321 100644 --- a/tests/FSharp.Compiler.UnitTests/xunit.runner.json +++ b/tests/FSharp.Compiler.UnitTests/xunit.runner.json @@ -1,5 +1,5 @@ { - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "appDomain": "ifAvailable", - "shadowCopy": false + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "appDomain": "ifAvailable", + "shadowCopy": false } From a8a62bf1746e87807a657e57ead39094a194fdeb Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sun, 18 Feb 2024 10:14:50 +0100 Subject: [PATCH 36/43] f --- src/Compiler/Driver/ParseAndCheckInputs.fs | 13 +++- src/Compiler/Facilities/BuildGraph.fs | 22 +++---- src/Compiler/Facilities/DiagnosticsLogger.fs | 69 ++++++++++++-------- src/Compiler/Service/BackgroundCompiler.fs | 3 +- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index bed6c6a4028..875cd7b7cc3 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -824,15 +824,24 @@ let ParseInputFilesInParallel (tcConfig: TcConfig, lexResourceManager, sourceFil let! result = sourceFiles - |> Seq.map (fun (fileName, isLastCompiland) -> node { + |> Seq.map (fun (fileName, isLastCompiland) -> + node { let directoryName = Path.GetDirectoryName fileName let input = - parseInputFileAux (tcConfig, lexResourceManager, fileName, (isLastCompiland, isExe), DiagnosticsThreadStatics.DiagnosticsLogger, retryLocked) + parseInputFileAux ( + tcConfig, + lexResourceManager, + fileName, + (isLastCompiland, isExe), + DiagnosticsThreadStatics.DiagnosticsLogger, + retryLocked + ) return (input, directoryName) }) |> NodeCode.Parallel + return result |> Array.toList } |> Async.AwaitNodeCode diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index b3481bb602f..3328fb4f070 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -154,11 +154,13 @@ type NodeCode private () = static member FromCancellable(computation: Cancellable<'T>) = Node(computation |> Cancellable.toAsync |> wrapThreadStaticInfo) - static member AwaitAsync(computation: Async<'T>) = + static member AwaitAsync(computation: Async<'T>) = async { Trace.TraceWarning("NodeCode.AwaitAsync") return! computation - } |> wrapThreadStaticInfo |> Node + } + |> wrapThreadStaticInfo + |> Node static member AwaitTask(task: Task<'T>) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) @@ -181,7 +183,7 @@ type NodeCode private () = results.Add(res) return results.ToArray() - } + } static member Parallel(computations: NodeCode<'T> seq) = async { @@ -195,20 +197,18 @@ type NodeCode private () = async { DiagnosticsThreadStatics.DiagnosticsLogger <- logger DiagnosticsThreadStatics.BuildPhase <- phase - try + + try return! unwrapNode computation finally DiagnosticsThreadStatics.DiagnosticsLogger <- ambientLogger - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsThreadStatics.BuildPhase <- phase } - return! - computations - |> Seq.mapi injectLogger - |> Async.Parallel - |> wrapThreadStaticInfo + return! computations |> Seq.mapi injectLogger |> Async.Parallel |> wrapThreadStaticInfo - } |> Node + } + |> Node [] module GraphNode = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 4af2ebf6aea..b65b2c2dca5 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -346,7 +346,10 @@ let AssertFalseDiagnosticsLogger = { new DiagnosticsLogger("AssertFalseDiagnosticsLogger") with // TODO: reenable these asserts in the compiler service member _.DiagnosticSink(diagnostic, severity) = assert false - member _.ErrorCount = assert false; 0 + + member _.ErrorCount = + assert false + 0 } type CapturingDiagnosticsLogger(nm, ?eagerFormat) = @@ -377,33 +380,49 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = - static let dlName (dl: DiagnosticsLogger) = if box dl |> isNull then "NULL" else dl.DebugDisplay() + static let dlName (dl: DiagnosticsLogger) = + if box dl |> isNull then "NULL" else dl.DebugDisplay() static let buildPhaseAsync = new AsyncLocal() - static let diagnosticsLoggerAsync = new AsyncLocal(fun args -> - if args.ThreadContextChanged then - Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} prev: {dlName args.PreviousValue}, current: {dlName args.CurrentValue}, THREAD CONTEXT CHANGED")) - static let check() = + static let diagnosticsLoggerAsync = + new AsyncLocal(fun args -> + if args.ThreadContextChanged then + Trace.WriteLine( + $"t:{Thread.CurrentThread.ManagedThreadId} prev: {dlName args.PreviousValue}, current: {dlName args.CurrentValue}, THREAD CONTEXT CHANGED" + )) + + static let check () = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger + match box al, box ts with | Null, _ -> () // New context started. | _, Null -> () // ?? | a, t when not <| a.Equals(t) -> // Not good. - #if DEBUG - Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") - #else - Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") - // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." - #endif +#if DEBUG + Debug.Assert( + false, + $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + ) +#else + () + // Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") + // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." +#endif | _ -> () #if DEBUG static let log prefix = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger let tid = Thread.CurrentThread.ManagedThreadId - let dls = if box al = box ts then dlName al else $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" + + let dls = + if box al = box ts then + dlName al + else + $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" + Trace.WriteLine($"t:{tid} {prefix} {dls}") #endif @@ -418,27 +437,25 @@ type internal DiagnosticsThreadStatics = static member BuildPhase with get () = match box DiagnosticsThreadStatics.buildPhase with - | Null -> - BuildPhase.DefaultPhase - | _ -> - DiagnosticsThreadStatics.buildPhase + | Null -> BuildPhase.DefaultPhase + | _ -> DiagnosticsThreadStatics.buildPhase and set v = buildPhaseAsync.Value <- v DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLogger with get () = - #if DEBUG +#if DEBUG log "->" - #endif - check() +#endif + check () DiagnosticsThreadStatics.diagnosticsLogger and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v - #if DEBUG +#if DEBUG log "<-" - #endif +#endif static member BuildPhaseNC with get () = DiagnosticsThreadStatics.buildPhase @@ -446,15 +463,15 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = - #if DEBUG +#if DEBUG log "-> NC" - #endif +#endif DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v - #if DEBUG +#if DEBUG log "<- NC" - #endif +#endif static member InitGlobals() = Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} INIT GLOBAL DIAGNOSTICS") diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index a87e275e430..dd718a308c6 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -1666,7 +1666,8 @@ type internal BackgroundCompiler flatErrors: bool, userOpName: string ) = - self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) |> Async.AwaitNodeCode + self.ParseFile(fileName, sourceText, options, cache, flatErrors, userOpName) + |> Async.AwaitNodeCode member _.ParseFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) = let options = projectSnapshot.ToOptions() From 90aa5b4b1bc443822efebc5190c464c0f0e623da Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sun, 18 Feb 2024 12:24:28 +0100 Subject: [PATCH 37/43] check --- src/Compiler/Facilities/AsyncMemoize.fs | 4 +-- src/Compiler/Facilities/BuildGraph.fs | 20 ++++++------- src/Compiler/Facilities/BuildGraph.fsi | 2 ++ src/Compiler/Facilities/DiagnosticsLogger.fs | 6 ++-- .../CompilerService/AsyncMemoize.fs | 30 +++++++++---------- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 4edee5fbc45..dae0020f9f8 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -356,7 +356,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCode + let! result = computation |> Async.AwaitNodeCodeAsyncMemoize post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return () with @@ -499,7 +499,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Started, key) use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCode + let! result = computation |> Async.AwaitNodeCodeAsyncMemoize post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return result }, diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 3328fb4f070..343bfc6da4a 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -9,6 +9,7 @@ open System.Diagnostics open System.Globalization open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library +open Internal.Utilities.Library.Cancellable [] type NodeCode<'T> = Node of Async<'T> @@ -37,6 +38,8 @@ type Async<'T> with return! computation } + static member AwaitNodeCodeAsyncMemoize(node: NodeCode<'T>) = unwrapNode node + [] type NodeCodeBuilder() = @@ -186,29 +189,24 @@ type NodeCode private () = } static member Parallel(computations: NodeCode<'T> seq) = - async { + node { let phase = DiagnosticsThreadStatics.BuildPhase - let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger + //let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger use concurrentLogging = new CaptureDiagnosticsConcurrently() let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") async { - DiagnosticsThreadStatics.DiagnosticsLogger <- logger - DiagnosticsThreadStatics.BuildPhase <- phase + SetThreadDiagnosticsLoggerNoUnwind logger + SetThreadBuildPhaseNoUnwind phase - try - return! unwrapNode computation - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- ambientLogger - DiagnosticsThreadStatics.BuildPhase <- phase + return! computation |> unwrapNode } - return! computations |> Seq.mapi injectLogger |> Async.Parallel |> wrapThreadStaticInfo + return! computations |> Seq.mapi injectLogger |> Async.Parallel |> Node } - |> Node [] module GraphNode = diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index 4b31005bee6..7e1fd542b00 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -22,6 +22,8 @@ type Async<'T> with /// Asynchronously await code in the build graph static member AwaitNodeCode: node: NodeCode<'T> -> Async<'T> + static member AwaitNodeCodeAsyncMemoize: node: NodeCode<'T> -> Async<'T> + /// A standard builder for node code. [] type NodeCodeBuilder = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index b65b2c2dca5..d478c7bc96a 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -401,10 +401,8 @@ type internal DiagnosticsThreadStatics = | _, Null -> () // ?? | a, t when not <| a.Equals(t) -> // Not good. #if DEBUG - Debug.Assert( - false, - $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." - ) + if Debugger.IsAttached then Debugger.Break() else () + // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." #else () // Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 8a2d3839061..574ef7a4d75 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -56,10 +56,6 @@ type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(mem member _.Sequence = events |> Seq.map id -type internal InitTestDiagnostics() = - inherit CompilationGlobalsScope(CapturingDiagnosticsLogger("Asynmemoize tests"), BuildPhase.DefaultPhase) - - [] let ``Basics``() = @@ -434,7 +430,7 @@ type DummyException(msg) = inherit Exception(msg) [] -let ``Preserve thread static diagnostics`` () = +let ``Preserve thread static diagnostics`` () = let seed = System.Random().Next() @@ -472,7 +468,7 @@ let ``Preserve thread static diagnostics`` () = let tasks = seq { for i in 1 .. 100 do - task { + node { let diagnosticsLogger = CompilationDiagnosticLogger($"Testing task {i}", FSharpDiagnosticOptions.Default) @@ -486,7 +482,7 @@ let ``Preserve thread static diagnostics`` () = member _.GetVersion() = rng.Next(1, 10) member _.GetLabel() = "job2" } - let! result = job2Cache.Get(key, job2 (i % 10)) |> Async.AwaitNodeCode + let! result = job2Cache.Get(key, job2 (i % 10)) let diagnostics = diagnosticsLogger.GetDiagnostics() @@ -496,7 +492,7 @@ let ``Preserve thread static diagnostics`` () = } } - let results = (Task.WhenAll tasks).Result + let results = tasks |> NodeCode.Parallel |> Async.AwaitNodeCode |> Async.RunSynchronously let _diagnosticCounts = results |> Seq.map snd |> Seq.map Array.length |> Seq.groupBy id |> Seq.map (fun (k, v) -> k, v |> Seq.length) |> Seq.sortBy fst |> Seq.toList @@ -510,6 +506,8 @@ let ``Preserve thread static diagnostics`` () = [] let ``Preserve thread static diagnostics already completed job`` () = + DiagnosticsThreadStatics.InitGlobals() + let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with @@ -523,26 +521,28 @@ let ``Preserve thread static diagnostics already completed job`` () = return Ok input } - async { + node { let diagnosticsLogger = CompilationDiagnosticLogger($"Testing", FSharpDiagnosticOptions.Default) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) - let! _ = cache.Get(key, job "1" ) |> Async.AwaitNodeCode - let! _ = cache.Get(key, job "2" ) |> Async.AwaitNodeCode + let! _ = cache.Get(key, job "1" ) + let! _ = cache.Get(key, job "2" ) let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList Assert.Equal>(["job 1 error"; "job 1 error"], diagnosticMessages) } - |> Async.StartAsTask + |> NodeCode.StartAsTask_ForTesting [] let ``We get diagnostics from the job that failed`` () = + DiagnosticsThreadStatics.InitGlobals() + let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with @@ -572,9 +572,9 @@ let ``We get diagnostics from the job that failed`` () = let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList return diagnosticMessages - } |> Async.AwaitNodeCode) - |> Async.Parallel - |> Async.StartAsTask + }) + |> NodeCode.Parallel + |> NodeCode.StartAsTask_ForTesting |> (fun t -> t.Result) |> Array.toList From 114f6826370cb716e5d9ce1c292de97db195d3c4 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Sun, 18 Feb 2024 12:24:28 +0100 Subject: [PATCH 38/43] restore assertfalse --- src/Compiler/Facilities/AsyncMemoize.fs | 4 +-- src/Compiler/Facilities/BuildGraph.fs | 20 ++++++------- src/Compiler/Facilities/BuildGraph.fsi | 2 ++ src/Compiler/Facilities/DiagnosticsLogger.fs | 14 ++++----- .../CompilerService/AsyncMemoize.fs | 30 +++++++++---------- 5 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 4edee5fbc45..dae0020f9f8 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -356,7 +356,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCode + let! result = computation |> Async.AwaitNodeCodeAsyncMemoize post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return () with @@ -499,7 +499,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Started, key) use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCode + let! result = computation |> Async.AwaitNodeCodeAsyncMemoize post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return result }, diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 3328fb4f070..343bfc6da4a 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -9,6 +9,7 @@ open System.Diagnostics open System.Globalization open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library +open Internal.Utilities.Library.Cancellable [] type NodeCode<'T> = Node of Async<'T> @@ -37,6 +38,8 @@ type Async<'T> with return! computation } + static member AwaitNodeCodeAsyncMemoize(node: NodeCode<'T>) = unwrapNode node + [] type NodeCodeBuilder() = @@ -186,29 +189,24 @@ type NodeCode private () = } static member Parallel(computations: NodeCode<'T> seq) = - async { + node { let phase = DiagnosticsThreadStatics.BuildPhase - let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger + //let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger use concurrentLogging = new CaptureDiagnosticsConcurrently() let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") async { - DiagnosticsThreadStatics.DiagnosticsLogger <- logger - DiagnosticsThreadStatics.BuildPhase <- phase + SetThreadDiagnosticsLoggerNoUnwind logger + SetThreadBuildPhaseNoUnwind phase - try - return! unwrapNode computation - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- ambientLogger - DiagnosticsThreadStatics.BuildPhase <- phase + return! computation |> unwrapNode } - return! computations |> Seq.mapi injectLogger |> Async.Parallel |> wrapThreadStaticInfo + return! computations |> Seq.mapi injectLogger |> Async.Parallel |> Node } - |> Node [] module GraphNode = diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index 4b31005bee6..7e1fd542b00 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -22,6 +22,8 @@ type Async<'T> with /// Asynchronously await code in the build graph static member AwaitNodeCode: node: NodeCode<'T> -> Async<'T> + static member AwaitNodeCodeAsyncMemoize: node: NodeCode<'T> -> Async<'T> + /// A standard builder for node code. [] type NodeCodeBuilder = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index b65b2c2dca5..b99e8bb9a3f 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -399,16 +399,12 @@ type internal DiagnosticsThreadStatics = match box al, box ts with | Null, _ -> () // New context started. | _, Null -> () // ?? + | _ when al.DebugDisplay().Contains("NodeCode.Parallel") -> () // Threadstatic needs to catch up. | a, t when not <| a.Equals(t) -> // Not good. #if DEBUG - Debug.Assert( - false, - $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." - ) + if Debugger.IsAttached then Debugger.Break() #else - () - // Debug.Assert(false, $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}.") - // failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." + failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." #endif | _ -> () #if DEBUG @@ -476,9 +472,9 @@ type internal DiagnosticsThreadStatics = static member InitGlobals() = Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} INIT GLOBAL DIAGNOSTICS") buildPhaseAsync.Value <- BuildPhase.DefaultPhase - diagnosticsLoggerAsync.Value <- DiscardErrorsLogger + diagnosticsLoggerAsync.Value <- AssertFalseDiagnosticsLogger DiagnosticsThreadStatics.buildPhase <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.diagnosticsLogger <- DiscardErrorsLogger + DiagnosticsThreadStatics.diagnosticsLogger <- AssertFalseDiagnosticsLogger [] module DiagnosticsLoggerExtensions = diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 8a2d3839061..574ef7a4d75 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -56,10 +56,6 @@ type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(mem member _.Sequence = events |> Seq.map id -type internal InitTestDiagnostics() = - inherit CompilationGlobalsScope(CapturingDiagnosticsLogger("Asynmemoize tests"), BuildPhase.DefaultPhase) - - [] let ``Basics``() = @@ -434,7 +430,7 @@ type DummyException(msg) = inherit Exception(msg) [] -let ``Preserve thread static diagnostics`` () = +let ``Preserve thread static diagnostics`` () = let seed = System.Random().Next() @@ -472,7 +468,7 @@ let ``Preserve thread static diagnostics`` () = let tasks = seq { for i in 1 .. 100 do - task { + node { let diagnosticsLogger = CompilationDiagnosticLogger($"Testing task {i}", FSharpDiagnosticOptions.Default) @@ -486,7 +482,7 @@ let ``Preserve thread static diagnostics`` () = member _.GetVersion() = rng.Next(1, 10) member _.GetLabel() = "job2" } - let! result = job2Cache.Get(key, job2 (i % 10)) |> Async.AwaitNodeCode + let! result = job2Cache.Get(key, job2 (i % 10)) let diagnostics = diagnosticsLogger.GetDiagnostics() @@ -496,7 +492,7 @@ let ``Preserve thread static diagnostics`` () = } } - let results = (Task.WhenAll tasks).Result + let results = tasks |> NodeCode.Parallel |> Async.AwaitNodeCode |> Async.RunSynchronously let _diagnosticCounts = results |> Seq.map snd |> Seq.map Array.length |> Seq.groupBy id |> Seq.map (fun (k, v) -> k, v |> Seq.length) |> Seq.sortBy fst |> Seq.toList @@ -510,6 +506,8 @@ let ``Preserve thread static diagnostics`` () = [] let ``Preserve thread static diagnostics already completed job`` () = + DiagnosticsThreadStatics.InitGlobals() + let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with @@ -523,26 +521,28 @@ let ``Preserve thread static diagnostics already completed job`` () = return Ok input } - async { + node { let diagnosticsLogger = CompilationDiagnosticLogger($"Testing", FSharpDiagnosticOptions.Default) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) - let! _ = cache.Get(key, job "1" ) |> Async.AwaitNodeCode - let! _ = cache.Get(key, job "2" ) |> Async.AwaitNodeCode + let! _ = cache.Get(key, job "1" ) + let! _ = cache.Get(key, job "2" ) let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList Assert.Equal>(["job 1 error"; "job 1 error"], diagnosticMessages) } - |> Async.StartAsTask + |> NodeCode.StartAsTask_ForTesting [] let ``We get diagnostics from the job that failed`` () = + DiagnosticsThreadStatics.InitGlobals() + let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with @@ -572,9 +572,9 @@ let ``We get diagnostics from the job that failed`` () = let diagnosticMessages = diagnosticsLogger.GetDiagnostics() |> Array.map (fun (d, _) -> d.Exception.Message) |> Array.toList return diagnosticMessages - } |> Async.AwaitNodeCode) - |> Async.Parallel - |> Async.StartAsTask + }) + |> NodeCode.Parallel + |> NodeCode.StartAsTask_ForTesting |> (fun t -> t.Result) |> Array.toList From a9359f0973b04ab6a753edf14dcb430aa3a85461 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Mon, 19 Feb 2024 14:24:18 +0100 Subject: [PATCH 39/43] trace UseDiagnosticsLogger --- src/Compiler/Facilities/AsyncMemoize.fs | 4 ++-- src/Compiler/Facilities/BuildGraph.fs | 2 +- src/Compiler/Facilities/BuildGraph.fsi | 2 +- src/Compiler/Facilities/DiagnosticsLogger.fs | 18 ++++++++++++------ src/Compiler/Service/IncrementalBuild.fs | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index dae0020f9f8..2947b65e03a 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -356,7 +356,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCodeAsyncMemoize + let! result = computation |> Async.AwaitNodeCodeNoInit post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return () with @@ -499,7 +499,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Started, key) use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCodeAsyncMemoize + let! result = computation |> Async.AwaitNodeCodeNoInit post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return result }, diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 343bfc6da4a..c4e5c80ed44 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -38,7 +38,7 @@ type Async<'T> with return! computation } - static member AwaitNodeCodeAsyncMemoize(node: NodeCode<'T>) = unwrapNode node + static member AwaitNodeCodeNoInit(node: NodeCode<'T>) = unwrapNode node [] type NodeCodeBuilder() = diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index 7e1fd542b00..b39dceede97 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -22,7 +22,7 @@ type Async<'T> with /// Asynchronously await code in the build graph static member AwaitNodeCode: node: NodeCode<'T> -> Async<'T> - static member AwaitNodeCodeAsyncMemoize: node: NodeCode<'T> -> Async<'T> + static member AwaitNodeCodeNoInit: node: NodeCode<'T> -> Async<'T> /// A standard builder for node code. [] diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index b99e8bb9a3f..3dbe7c352d0 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -389,7 +389,7 @@ type internal DiagnosticsThreadStatics = new AsyncLocal(fun args -> if args.ThreadContextChanged then Trace.WriteLine( - $"t:{Thread.CurrentThread.ManagedThreadId} prev: {dlName args.PreviousValue}, current: {dlName args.CurrentValue}, THREAD CONTEXT CHANGED" + "" //$"\nt:{Thread.CurrentThread.ManagedThreadId} ASYNCLOCAL context change\n\t\tprev: {dlName args.PreviousValue}\n\t\tcurrent: {dlName args.CurrentValue}\n" )) static let check () = @@ -442,7 +442,7 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLogger with get () = #if DEBUG - log "->" + //log "->" #endif check () DiagnosticsThreadStatics.diagnosticsLogger @@ -450,7 +450,7 @@ type internal DiagnosticsThreadStatics = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v #if DEBUG - log "<-" + //log "<-" #endif static member BuildPhaseNC @@ -460,13 +460,13 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = #if DEBUG - log "-> NC" + //log "-> NC" #endif DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v #if DEBUG - log "<- NC" + //log "<- NC" #endif static member InitGlobals() = @@ -589,10 +589,15 @@ let UseBuildPhase (phase: BuildPhase) = /// NOTE: The change will be undone when the returned "unwind" object disposes let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #DiagnosticsLogger) = let oldLogger = DiagnosticsThreadStatics.DiagnosticsLogger - DiagnosticsThreadStatics.DiagnosticsLogger <- transformer oldLogger + let newLogger = transformer oldLogger + DiagnosticsThreadStatics.DiagnosticsLogger <- newLogger + Trace.IndentLevel <- Trace.IndentLevel + 1 + Trace.WriteLine $"using: {DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay()}" { new IDisposable with member _.Dispose() = + Trace.WriteLine $"releasing: {newLogger.DebugDisplay()}, restoring: {oldLogger.DebugDisplay()}" + Trace.IndentLevel <- Trace.IndentLevel - 1 DiagnosticsThreadStatics.DiagnosticsLogger <- oldLogger } @@ -625,6 +630,7 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B /// Raises an exception with error recovery and returns unit. let errorR exn = + Trace.WriteLine $"t:{Thread.CurrentThread.ManagedThreadId} pushing ERROR" DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR exn /// Raises a warning with error recovery and returns unit. diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f2096ed2d41..a02e95606a0 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -395,7 +395,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 |> Async.AwaitNodeCodeNoInit |> Async.Catch |> Async.Ignore |> Async.Start getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics From efa7207fcd22781af3db576059705184d60080f1 Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Mon, 19 Feb 2024 15:48:16 +0100 Subject: [PATCH 40/43] wip --- src/Compiler/Facilities/DiagnosticsLogger.fs | 4 ++-- src/Compiler/Service/IncrementalBuild.fs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 3dbe7c352d0..ac519596258 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -592,7 +592,7 @@ let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #Diagnost let newLogger = transformer oldLogger DiagnosticsThreadStatics.DiagnosticsLogger <- newLogger Trace.IndentLevel <- Trace.IndentLevel + 1 - Trace.WriteLine $"using: {DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay()}" + Trace.WriteLine $"using: {DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay()}, old logger: {oldLogger.DebugDisplay()}" { new IDisposable with member _.Dispose() = @@ -630,7 +630,7 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B /// Raises an exception with error recovery and returns unit. let errorR exn = - Trace.WriteLine $"t:{Thread.CurrentThread.ManagedThreadId} pushing ERROR" + Trace.WriteLine $"t:{Thread.CurrentThread.ManagedThreadId} pushing ERROR to {DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay()}" DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR exn /// Raises a warning with error recovery and returns unit. diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index a02e95606a0..906d94c5356 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -145,11 +145,11 @@ module IncrementalBuildSyntaxTree = Activity.Tags.buildPhase, BuildPhase.Parse.ToString() |] - let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) - // Return the disposable object that cleans up - use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) - try + let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) + // Return the disposable object that cleans up + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) + use! text = source.GetTextContainer() |> NodeCode.AwaitAsync let input = match text with @@ -395,7 +395,7 @@ type BoundModel private ( GraphNode.FromResult tcInfo, tcInfoExtras | _ -> // start computing extras, so that typeCheckNode can be GC'd quickly - startComputingFullTypeCheck |> Async.AwaitNodeCodeNoInit |> Async.Catch |> Async.Ignore |> Async.Start + startComputingFullTypeCheck |> Async.AwaitNodeCode |> Async.Catch |> Async.Ignore |> Async.Start getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -1346,8 +1346,8 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let slotOfFile = builder.GetSlotOfFileName fileName let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() - |> Async.AwaitNodeCode - |> Async.RunSynchronously + |> Async.AwaitNodeCodeNoInit + |> Async.RunImmediate member builder.NotifyFileChanged(fileName, timeStamp) = node { From 785bf2d10df066b437b1d61ef7c9df7052f1f6ac Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Mon, 19 Feb 2024 16:42:25 +0100 Subject: [PATCH 41/43] fix CompilerImports behavor --- src/Compiler/Facilities/BuildGraph.fs | 31 ++++++++++---------- src/Compiler/Facilities/DiagnosticsLogger.fs | 13 ++++---- src/Compiler/Service/IncrementalBuild.fs | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index c4e5c80ed44..09e7d406053 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -103,7 +103,7 @@ type NodeCodeBuilder() = [] member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = - async.Using(resource, binder >> unwrapNode) |> Node + async.Using(resource, binder >> unwrapNode >> wrapThreadStaticInfo) |> Node let node = NodeCodeBuilder() @@ -155,15 +155,9 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(computation |> Cancellable.toAsync |> wrapThreadStaticInfo) + Node(wrapThreadStaticInfo (Cancellable.toAsync computation)) - static member AwaitAsync(computation: Async<'T>) = - async { - Trace.TraceWarning("NodeCode.AwaitAsync") - return! computation - } - |> wrapThreadStaticInfo - |> Node + static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) static member AwaitTask(task: Task<'T>) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) @@ -179,19 +173,26 @@ type NodeCode private () = static member Sequential(computations: NodeCode<'T> seq) = node { - let results = ResizeArray() + use concurrentLogging = new CaptureDiagnosticsConcurrently() + + let sequential = node { + use _ = UseDiagnosticsLogger (concurrentLogging.GetLoggerForTask "NodeCode.Sequential") - for computation in computations do - let! res = computation - results.Add(res) + let results = ResizeArray() + + for computation in computations do + let! res = computation + results.Add(res) + + return results.ToArray() + } - return results.ToArray() + return! sequential } static member Parallel(computations: NodeCode<'T> seq) = node { let phase = DiagnosticsThreadStatics.BuildPhase - //let ambientLogger = DiagnosticsThreadStatics.DiagnosticsLogger use concurrentLogging = new CaptureDiagnosticsConcurrently() let injectLogger i computation = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index ac519596258..c2c4a115833 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -396,13 +396,12 @@ type internal DiagnosticsThreadStatics = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger - match box al, box ts with + match box al, ts with | Null, _ -> () // New context started. - | _, Null -> () // ?? - | _ when al.DebugDisplay().Contains("NodeCode.Parallel") -> () // Threadstatic needs to catch up. - | a, t when not <| a.Equals(t) -> // Not good. + | _ when al <> ts && al.DebugDisplay().Contains("NodeCode") -> () // Threadstatic needs to catch up. + | _ when al <> ts -> // Not good. #if DEBUG - if Debugger.IsAttached then Debugger.Break() + Debugger.Break() #else failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." #endif @@ -596,9 +595,9 @@ let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #Diagnost { new IDisposable with member _.Dispose() = - Trace.WriteLine $"releasing: {newLogger.DebugDisplay()}, restoring: {oldLogger.DebugDisplay()}" - Trace.IndentLevel <- Trace.IndentLevel - 1 DiagnosticsThreadStatics.DiagnosticsLogger <- oldLogger + Trace.WriteLine $"released: {newLogger.DebugDisplay()}, restored: {oldLogger.DebugDisplay()}" + Trace.IndentLevel <- Trace.IndentLevel - 1 } let UseDiagnosticsLogger newLogger = diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 906d94c5356..2c6a97448de 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -1347,7 +1347,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() |> Async.AwaitNodeCodeNoInit - |> Async.RunImmediate + |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = node { From 8ee075f0325362e88936cfd2cc3180d1b1f5f46e Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 20 Feb 2024 09:33:26 +0100 Subject: [PATCH 42/43] min diff --- src/Compiler/Facilities/AsyncMemoize.fs | 4 +- src/Compiler/Facilities/BuildGraph.fs | 54 ++++++----- src/Compiler/Facilities/BuildGraph.fsi | 8 +- src/Compiler/Facilities/DiagnosticsLogger.fs | 94 ++++++++++--------- src/Compiler/Facilities/DiagnosticsLogger.fsi | 2 - src/Compiler/Service/IncrementalBuild.fs | 26 ++--- .../CompilerService/AsyncMemoize.fs | 4 - 7 files changed, 100 insertions(+), 92 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 2947b65e03a..4edee5fbc45 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -356,7 +356,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCodeNoInit + let! result = computation |> Async.AwaitNodeCode post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return () with @@ -499,7 +499,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Started, key) use _ = UseDiagnosticsLogger cachingLogger - let! result = computation |> Async.AwaitNodeCodeNoInit + let! result = computation |> Async.AwaitNodeCode post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return result }, diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 09e7d406053..12162f71199 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -30,15 +30,7 @@ let unwrapNode (Node(computation)) = computation type Async<'T> with - static member AwaitNodeCode(node: NodeCode<'T>) = - match node with - | Node(computation) -> - async { - DiagnosticsThreadStatics.InitGlobals() - return! computation - } - - static member AwaitNodeCodeNoInit(node: NodeCode<'T>) = unwrapNode node + static member AwaitNodeCode(node: NodeCode<'T>) = unwrapNode node [] type NodeCodeBuilder() = @@ -102,8 +94,27 @@ type NodeCodeBuilder() = member _.Combine(Node(p1): NodeCode, Node(p2): NodeCode<'T>) : NodeCode<'T> = Node(async.Combine(p1, p2)) [] - member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = - async.Using(resource, binder >> unwrapNode >> wrapThreadStaticInfo) |> Node + member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = + Node( + async { + DiagnosticsThreadStatics.DiagnosticsLogger <- value.DiagnosticsLogger + DiagnosticsThreadStatics.BuildPhase <- value.BuildPhase + + try + return! binder value |> Async.AwaitNodeCode + finally + (value :> IDisposable).Dispose() + } + ) + + [] + member _.Using(value: IDisposable, binder: IDisposable -> NodeCode<'U>) = + Node( + async { + use _ = value + return! binder value |> Async.AwaitNodeCode + } + ) let node = NodeCodeBuilder() @@ -173,27 +184,20 @@ type NodeCode private () = static member Sequential(computations: NodeCode<'T> seq) = node { - use concurrentLogging = new CaptureDiagnosticsConcurrently() - - let sequential = node { - use _ = UseDiagnosticsLogger (concurrentLogging.GetLoggerForTask "NodeCode.Sequential") - - let results = ResizeArray() + let results = ResizeArray() - for computation in computations do - let! res = computation - results.Add(res) - - return results.ToArray() - } + for computation in computations do + let! res = computation + results.Add(res) - return! sequential + return results.ToArray() } static member Parallel(computations: NodeCode<'T> seq) = node { let phase = DiagnosticsThreadStatics.BuildPhase - use concurrentLogging = new CaptureDiagnosticsConcurrently() + let concurrentLogging = new CaptureDiagnosticsConcurrently() + use _ = concurrentLogging let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index b39dceede97..c967649b2f8 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -22,8 +22,6 @@ type Async<'T> with /// Asynchronously await code in the build graph static member AwaitNodeCode: node: NodeCode<'T> -> Async<'T> - static member AwaitNodeCodeNoInit: node: NodeCode<'T> -> Async<'T> - /// A standard builder for node code. [] type NodeCodeBuilder = @@ -46,8 +44,12 @@ type NodeCodeBuilder = member Combine: x1: NodeCode * x2: NodeCode<'T> -> NodeCode<'T> + /// A limited form 'use' for establishing the compilation globals. + member Using: CompilationGlobalsScope * (CompilationGlobalsScope -> NodeCode<'T>) -> NodeCode<'T> + /// A generic 'use' that disposes of the IDisposable at the end of the computation. - member Using: ('T :> IDisposable) * (('T :> IDisposable) -> NodeCode<'U>) -> NodeCode<'U> + member Using: IDisposable * (IDisposable -> NodeCode<'T>) -> NodeCode<'T> + /// Specifies code that can be run as part of the build graph. val node: NodeCodeBuilder diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index c2c4a115833..f0763ace52f 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -345,11 +345,9 @@ let DiscardErrorsLogger = let AssertFalseDiagnosticsLogger = { new DiagnosticsLogger("AssertFalseDiagnosticsLogger") with // TODO: reenable these asserts in the compiler service - member _.DiagnosticSink(diagnostic, severity) = assert false + member _.DiagnosticSink(diagnostic, severity) = () //assert false - member _.ErrorCount = - assert false - 0 + member _.ErrorCount = 0 //assert false } type CapturingDiagnosticsLogger(nm, ?eagerFormat) = @@ -377,30 +375,39 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = let errors = diagnostics.ToArray() errors |> Array.iter diagnosticsLogger.DiagnosticSink +[] +module Tracing = + let dlName (dl: DiagnosticsLogger) = + if box dl |> isNull then "NULL" else dl.DebugDisplay() + + let tid () = Thread.CurrentThread.ManagedThreadId + /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = - static let dlName (dl: DiagnosticsLogger) = - if box dl |> isNull then "NULL" else dl.DebugDisplay() - static let buildPhaseAsync = new AsyncLocal() static let diagnosticsLoggerAsync = - new AsyncLocal(fun args -> - if args.ThreadContextChanged then - Trace.WriteLine( - "" //$"\nt:{Thread.CurrentThread.ManagedThreadId} ASYNCLOCAL context change\n\t\tprev: {dlName args.PreviousValue}\n\t\tcurrent: {dlName args.CurrentValue}\n" - )) + new AsyncLocal() + //fun args -> + // if args.ThreadContextChanged then + // Trace.WriteLine( + // "" //$"\nt:{Thread.CurrentThread.ManagedThreadId} ASYNCLOCAL context change\n\t\tprev: {dlName args.PreviousValue}\n\t\tcurrent: {dlName args.CurrentValue}\n" + // )) static let check () = let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger - match box al, ts with - | Null, _ -> () // New context started. - | _ when al <> ts && al.DebugDisplay().Contains("NodeCode") -> () // Threadstatic needs to catch up. - | _ when al <> ts -> // Not good. + match box al, box ts with + | a, t when a = t -> () + | Null, _ -> () // New context. + | _ when ts = AssertFalseDiagnosticsLogger -> () // Threadstatic not yet initialized. + | _ when al.DebugDisplay().Contains("Caching") || ts.DebugDisplay().Contains("Caching") -> () // AsyncMemoize, disregard until fixed. + | _ when al.DebugDisplay().Contains("NodeCode") || ts.DebugDisplay().Contains("NodeCode") -> () // Threadstatic needs to catch up. + | _ when al.DebugDisplay() <> ts.DebugDisplay() -> // Not good. #if DEBUG + Trace.WriteLine $"t:{tid()}, AL: {dlName al} TS: {dlName ts}" Debugger.Break() #else failwith $"DiagnosticsLogger diverged. AsyncLocal: <{dlName al}>, ThreadStatic: <{dlName ts}>, tid: {Thread.CurrentThread.ManagedThreadId}." @@ -408,17 +415,16 @@ type internal DiagnosticsThreadStatics = | _ -> () #if DEBUG static let log prefix = - let al = diagnosticsLoggerAsync.Value + //let al = diagnosticsLoggerAsync.Value let ts = DiagnosticsThreadStatics.diagnosticsLogger - let tid = Thread.CurrentThread.ManagedThreadId - let dls = - if box al = box ts then - dlName al - else - $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" + //let dls = + // if box al = box ts then + // dlName al + // else + // $"\nDIVERGED \n\tAsyncLocal: {dlName al}\n\tThreadStatic: {dlName ts}\n" - Trace.WriteLine($"t:{tid} {prefix} {dls}") + Trace.WriteLine($"t:{tid()} {prefix} {dlName ts}") #endif [] @@ -434,39 +440,37 @@ type internal DiagnosticsThreadStatics = match box DiagnosticsThreadStatics.buildPhase with | Null -> BuildPhase.DefaultPhase | _ -> DiagnosticsThreadStatics.buildPhase - and set v = - buildPhaseAsync.Value <- v - DiagnosticsThreadStatics.buildPhase <- v + and set v = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLogger with get () = -#if DEBUG - //log "->" -#endif - check () - DiagnosticsThreadStatics.diagnosticsLogger + + match box DiagnosticsThreadStatics.diagnosticsLogger with + | Null -> AssertFalseDiagnosticsLogger + | _ -> + + check() + DiagnosticsThreadStatics.diagnosticsLogger and set v = diagnosticsLoggerAsync.Value <- v DiagnosticsThreadStatics.diagnosticsLogger <- v -#if DEBUG - //log "<-" -#endif static member BuildPhaseNC - with get () = DiagnosticsThreadStatics.buildPhase + with get () = + match box DiagnosticsThreadStatics.buildPhase with + | Null -> BuildPhase.DefaultPhase + | _ -> DiagnosticsThreadStatics.buildPhase and set v = DiagnosticsThreadStatics.buildPhase <- v static member DiagnosticsLoggerNC with get () = -#if DEBUG - //log "-> NC" -#endif - DiagnosticsThreadStatics.diagnosticsLogger + // log "NC ->" + match box DiagnosticsThreadStatics.diagnosticsLogger with + | Null -> AssertFalseDiagnosticsLogger + | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v -#if DEBUG - //log "<- NC" -#endif + // log "NC <-" static member InitGlobals() = Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} INIT GLOBAL DIAGNOSTICS") @@ -500,6 +504,8 @@ module DiagnosticsLoggerExtensions = Debug.Assert(false, "Could not preserve stack trace for watson exception.") () + do DiagnosticsThreadStatics.InitGlobals() + type DiagnosticsLogger with member x.EmitDiagnostic(exn, severity) = @@ -591,7 +597,7 @@ let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #Diagnost let newLogger = transformer oldLogger DiagnosticsThreadStatics.DiagnosticsLogger <- newLogger Trace.IndentLevel <- Trace.IndentLevel + 1 - Trace.WriteLine $"using: {DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay()}, old logger: {oldLogger.DebugDisplay()}" + Trace.WriteLine $"t:{tid()} use : {dlName DiagnosticsThreadStatics.DiagnosticsLogger}" { new IDisposable with member _.Dispose() = diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 8ec3cdaaeca..399b94d69c0 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -240,8 +240,6 @@ type DiagnosticsThreadStatics = static member DiagnosticsLoggerNC: DiagnosticsLogger with get, set - static member InitGlobals: unit -> unit - [] module DiagnosticsLoggerExtensions = diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index 2c6a97448de..f59a1e9b6a5 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -148,11 +148,10 @@ module IncrementalBuildSyntaxTree = try let diagnosticsLogger = CompilationDiagnosticLogger("Parse", tcConfig.diagnosticsOptions) // Return the disposable object that cleans up - use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) - + use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) use! text = source.GetTextContainer() |> NodeCode.AwaitAsync let input = - match text with + match text :?> TextContainer with | TextContainer.Stream(stream) -> ParseOneInputStream(tcConfig, lexResourceManager, fileName, isLastCompiland, diagnosticsLogger, false, stream) | TextContainer.SourceText(sourceText) -> @@ -166,7 +165,8 @@ module IncrementalBuildSyntaxTree = with exn -> let msg = sprintf "unexpected failure in SyntaxTree.parse\nerror = %s" (exn.ToString()) System.Diagnostics.Debug.Assert(false, msg) - return failwith msg + failwith msg + return Unchecked.defaultof<_> } /// Parse the given file and return the given input. @@ -258,12 +258,9 @@ type BoundModel private ( use _ = Activity.start "BoundModel.TypeCheck" [|Activity.Tags.fileName, fileName|] IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked fileName) - - let capturingDiagnosticsLogger = CapturingDiagnosticsLogger("TypeCheck") - use scope = new CompilationGlobalsScope( - GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger), - BuildPhase.TypeCheck) + let diagnosticsLogger = GetDiagnosticsLoggerFilteringByScopedPragmas(false, input.ScopedPragmas, tcConfig.diagnosticsOptions, capturingDiagnosticsLogger) + use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck) beforeFileChecked.Trigger fileName @@ -274,7 +271,7 @@ type BoundModel private ( let! (tcEnvAtEndOfFile, topAttribs, implFile, ccuSigForFile), tcState = CheckOneInput ( - (fun () -> hadParseErrors || scope.DiagnosticsLogger.ErrorCount > 0), + (fun () -> hadParseErrors || diagnosticsLogger.ErrorCount > 0), tcConfig, tcImports, tcGlobals, None, @@ -304,7 +301,6 @@ type BoundModel private ( None } return tcInfo, sink, implFile, fileName, newErrors - } let skippedImplemetationTypeCheck = @@ -1346,7 +1342,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let slotOfFile = builder.GetSlotOfFileName fileName let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() - |> Async.AwaitNodeCodeNoInit + |> Async.AwaitNodeCode |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = @@ -1516,6 +1512,12 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc // // This operation is done when constructing the builder itself, rather than as an incremental task. let nonFrameworkAssemblyInputs = + // Note we are not calling diagnosticsLogger.GetDiagnostics() anywhere for this task. + // This is ok because not much can actually go wrong here. + let diagnosticsLogger = CompilationDiagnosticLogger("nonFrameworkAssemblyInputs", tcConfig.diagnosticsOptions) + // Return the disposable object that cleans up + use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter) + [ for r in nonFrameworkResolutions do let fileName = r.resolvedPath yield (Choice1Of2 fileName, (fun (cache: TimeStampCache) -> cache.GetFileTimeStamp fileName)) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 574ef7a4d75..f9a0450f7d3 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -506,8 +506,6 @@ let ``Preserve thread static diagnostics`` () = [] let ``Preserve thread static diagnostics already completed job`` () = - DiagnosticsThreadStatics.InitGlobals() - let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with @@ -541,8 +539,6 @@ let ``Preserve thread static diagnostics already completed job`` () = [] let ``We get diagnostics from the job that failed`` () = - DiagnosticsThreadStatics.InitGlobals() - let cache = AsyncMemoize() let key = { new ICacheKey<_, _> with From 7336e4680b72d930abf924cdacde5acb434d50ee Mon Sep 17 00:00:00 2001 From: Jakub Majocha Date: Tue, 20 Feb 2024 10:06:18 +0100 Subject: [PATCH 43/43] wrapThreadStaticInfo --- src/Compiler/Facilities/BuildGraph.fs | 44 +++++++------------- src/Compiler/Facilities/BuildGraph.fsi | 6 +-- src/Compiler/Facilities/DiagnosticsLogger.fs | 11 +++-- src/Compiler/Service/IncrementalBuild.fs | 2 +- 4 files changed, 25 insertions(+), 38 deletions(-) diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 12162f71199..b7e32ed156c 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -30,7 +30,7 @@ let unwrapNode (Node(computation)) = computation type Async<'T> with - static member AwaitNodeCode(node: NodeCode<'T>) = unwrapNode node + static member AwaitNodeCode(node: NodeCode<'T>) = unwrapNode node [] type NodeCodeBuilder() = @@ -94,27 +94,8 @@ type NodeCodeBuilder() = member _.Combine(Node(p1): NodeCode, Node(p2): NodeCode<'T>) : NodeCode<'T> = Node(async.Combine(p1, p2)) [] - member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) = - Node( - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- value.DiagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- value.BuildPhase - - try - return! binder value |> Async.AwaitNodeCode - finally - (value :> IDisposable).Dispose() - } - ) - - [] - member _.Using(value: IDisposable, binder: IDisposable -> NodeCode<'U>) = - Node( - async { - use _ = value - return! binder value |> Async.AwaitNodeCode - } - ) + member this.Using(resource: ('T :> IDisposable), binder: ('T :> IDisposable) -> NodeCode<'U>) = + async.Using(resource, binder >> unwrapNode) |> Node let node = NodeCodeBuilder() @@ -166,7 +147,7 @@ type NodeCode private () = static member CancellationToken = cancellationToken static member FromCancellable(computation: Cancellable<'T>) = - Node(wrapThreadStaticInfo (Cancellable.toAsync computation)) + Node(Cancellable.toAsync computation) static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) @@ -175,6 +156,12 @@ type NodeCode private () = static member AwaitTask(task: Task) = Node(wrapThreadStaticInfo (Async.AwaitTask task)) + + static member AwaitTaskWithoutWrapping(task: Task<'T>) = + Node((Async.AwaitTask task)) + + static member AwaitTaskWithoutWrapping(task: Task) = + Node((Async.AwaitTask task)) static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) @@ -196,8 +183,7 @@ type NodeCode private () = static member Parallel(computations: NodeCode<'T> seq) = node { let phase = DiagnosticsThreadStatics.BuildPhase - let concurrentLogging = new CaptureDiagnosticsConcurrently() - use _ = concurrentLogging + use concurrentLogging = new CaptureDiagnosticsConcurrently() let injectLogger i computation = let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") @@ -209,7 +195,7 @@ type NodeCode private () = return! computation |> unwrapNode } - return! computations |> Seq.mapi injectLogger |> Async.Parallel |> Node + return! computations |> Seq.mapi injectLogger |> Async.Parallel |> wrapThreadStaticInfo |> Node } @@ -274,7 +260,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ||| TaskContinuationOptions.NotOnFaulted ||| TaskContinuationOptions.ExecuteSynchronously) ) - |> NodeCode.AwaitTask + |> NodeCode.AwaitTaskWithoutWrapping match cachedResult with | ValueSome value -> return value @@ -284,7 +270,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! computation |> unwrapNode + return! computation |> unwrapNode |> wrapThreadStaticInfo }, (fun res -> cachedResult <- ValueSome res @@ -296,7 +282,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ct ) - return! tcs.Task |> NodeCode.AwaitTask + return! tcs.Task |> NodeCode.AwaitTaskWithoutWrapping finally if taken then semaphore.Release() |> ignore diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index c967649b2f8..a340b8c5b1d 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -44,11 +44,7 @@ type NodeCodeBuilder = member Combine: x1: NodeCode * x2: NodeCode<'T> -> NodeCode<'T> - /// A limited form 'use' for establishing the compilation globals. - member Using: CompilationGlobalsScope * (CompilationGlobalsScope -> NodeCode<'T>) -> NodeCode<'T> - - /// A generic 'use' that disposes of the IDisposable at the end of the computation. - member Using: IDisposable * (IDisposable -> NodeCode<'T>) -> NodeCode<'T> + member Using: ('T :> IDisposable) * (('T :> IDisposable) -> NodeCode<'U>) -> NodeCode<'U> /// Specifies code that can be run as part of the build graph. diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index f0763ace52f..c4b03287879 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -464,13 +464,18 @@ type internal DiagnosticsThreadStatics = static member DiagnosticsLoggerNC with get () = - // log "NC ->" + #if DEBUG + log "NC ->" + #endif match box DiagnosticsThreadStatics.diagnosticsLogger with | Null -> AssertFalseDiagnosticsLogger | _ -> DiagnosticsThreadStatics.diagnosticsLogger and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v - // log "NC <-" + #if DEBUG + log "NC <-" + #endif + static member InitGlobals() = Trace.WriteLine($"t:{Thread.CurrentThread.ManagedThreadId} INIT GLOBAL DIAGNOSTICS") @@ -602,7 +607,7 @@ let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #Diagnost { new IDisposable with member _.Dispose() = DiagnosticsThreadStatics.DiagnosticsLogger <- oldLogger - Trace.WriteLine $"released: {newLogger.DebugDisplay()}, restored: {oldLogger.DebugDisplay()}" + Trace.WriteLine $"t:{tid()} disp: {newLogger.DebugDisplay()}, restored: {oldLogger.DebugDisplay()}" Trace.IndentLevel <- Trace.IndentLevel - 1 } diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..0861d79817e 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -151,7 +151,7 @@ module IncrementalBuildSyntaxTree = use _holder = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parse) use! text = source.GetTextContainer() |> NodeCode.AwaitAsync let input = - match text :?> TextContainer with + match text with | TextContainer.Stream(stream) -> ParseOneInputStream(tcConfig, lexResourceManager, fileName, isLastCompiland, diagnosticsLogger, false, stream) | TextContainer.SourceText(sourceText) ->