Skip to content
4 changes: 4 additions & 0 deletions src/Compiler/Facilities/AsyncMemoize.fs
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,8 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T

member this.Get'(key, computation) =

failwith "AsyncMemoize"

let wrappedKey =
{ new ICacheKey<_, _> with
member _.GetKey() = key
Expand All @@ -474,6 +476,8 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T

member _.Get(key: ICacheKey<_, _>, computation) =

failwith "AsyncMemoize"

let key =
{
Label = key.GetLabel()
Expand Down
41 changes: 25 additions & 16 deletions src/Compiler/Facilities/BuildGraph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ type NodeCode<'T> = Node of Async<'T>

let wrapThreadStaticInfo computation =
async {
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode
let phase = DiagnosticsThreadStatics.BuildPhase

try
return! computation
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
}


let reset() =
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- AssertFalseDiagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase

let unwrapNode (Node(computation)) = computation

type Async<'T> with
Expand Down Expand Up @@ -98,7 +103,7 @@ type NodeCodeBuilder() =
member _.Using(value: CompilationGlobalsScope, binder: CompilationGlobalsScope -> NodeCode<'U>) =
Node(
async {
DiagnosticsThreadStatics.DiagnosticsLogger <- value.DiagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- value.DiagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- value.BuildPhase

try
Expand All @@ -125,21 +130,21 @@ type NodeCode private () =
static let cancellationToken = Node(wrapThreadStaticInfo Async.CancellationToken)

static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) =
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode
let phase = DiagnosticsThreadStatics.BuildPhase

try
try
let work =
async {
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
return! computation |> Async.AwaitNodeCode
}

Async.StartImmediateAsTask(work, cancellationToken = ct).Result
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
with :? AggregateException as ex when ex.InnerExceptions.Count = 1 ->
raise (ex.InnerExceptions[0])
Expand All @@ -148,20 +153,20 @@ type NodeCode private () =
NodeCode.RunImmediate(computation, CancellationToken.None)

static member StartAsTask_ForTesting(computation: NodeCode<'T>, ?ct: CancellationToken) =
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger
let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode
let phase = DiagnosticsThreadStatics.BuildPhase

try
let work =
async {
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase
return! computation |> Async.AwaitNodeCode
}

Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None)
finally
DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger
DiagnosticsThreadStatics.DiagnosticsLoggerNodeCode <- diagnosticsLogger
DiagnosticsThreadStatics.BuildPhase <- phase

static member CancellationToken = cancellationToken
Expand Down Expand Up @@ -205,9 +210,12 @@ type NodeCode private () =
let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}")

async {
DiagnosticsThreadStatics.DiagnosticsLogger <- logger
DiagnosticsThreadStatics.BuildPhase <- phase
return! unwrapNode computation
try
DiagnosticsThreadStatics.DiagnosticsLogger <- logger
DiagnosticsThreadStatics.BuildPhase <- phase
return! unwrapNode computation
finally
reset()
}

return!
Expand Down Expand Up @@ -256,6 +264,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption
cachedResultNode
else
node {
use _ = new CompilationGlobalsScope()
Interlocked.Increment(&requestCount) |> ignore

try
Expand Down Expand Up @@ -284,13 +293,13 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption
| ValueSome value -> return value
| _ ->
let tcs = TaskCompletionSource<'T>()
let (Node(p)) = computation

Async.StartWithContinuations(
async {
node {
Thread.CurrentThread.CurrentUICulture <- GraphNode.culture
return! p
},
reset()
return! computation
} |> Async.AwaitNodeCode,
(fun res ->
cachedResult <- ValueSome res
cachedResultNode <- node.Return res
Expand Down
30 changes: 30 additions & 0 deletions src/Compiler/Facilities/DiagnosticsLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,25 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) =
let errors = diagnostics.ToArray()
errors |> Array.iter diagnosticsLogger.DiagnosticSink

let currentDiagnosticsLogger = AsyncLocal<DiagnosticsLogger voption>()

let checkIfSame threadStatic =
match currentDiagnosticsLogger.Value with

// Big hurdle in testing this is the fact that threadstatics leak from one unrelated test to another.
// At least that's what happens in VS test runner. In effect, at the beginning of some computation when we expect
// to see uninitialized threadstatic, we get some value that stayed alive from another test function.
// Especially FinalizeTypeCheckTask shows up here a lot.
// That's why we have to disregard the threadstatics at the beggining of each execution context,
// up to the moment of proper initialization (the first call of UseDiagnosticsLogger usually).
| ValueNone -> threadStatic

| ValueSome asyncLocal ->
match asyncLocal, threadStatic with
| a, t when a = t -> t // Good.
| _ when threadStatic = AssertFalseDiagnosticsLogger -> threadStatic // AsyncLocal is always good on thread switches. ThreadStatic needs to catch up.
| _ -> failwith $"AsyncLocal does not match ThreadStatic. a: {asyncLocal.DebugDisplay()} t: {threadStatic.DebugDisplay()}"

/// Type holds thread-static globals for use by the compiler.
type internal DiagnosticsThreadStatics =
[<ThreadStatic; DefaultValue>]
Expand All @@ -393,6 +412,15 @@ type internal DiagnosticsThreadStatics =
and set v = DiagnosticsThreadStatics.buildPhase <- v

static member DiagnosticsLogger
with get () =
match box DiagnosticsThreadStatics.diagnosticsLogger with
| Null -> AssertFalseDiagnosticsLogger
| _ -> checkIfSame DiagnosticsThreadStatics.diagnosticsLogger
and set v =
currentDiagnosticsLogger.Value <- ValueSome v
DiagnosticsThreadStatics.diagnosticsLogger <- v

static member DiagnosticsLoggerNodeCode
with get () =
match box DiagnosticsThreadStatics.diagnosticsLogger with
| Null -> AssertFalseDiagnosticsLogger
Expand Down Expand Up @@ -533,6 +561,8 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B
let unwindEL = UseDiagnosticsLogger diagnosticsLogger
let unwindBP = UseBuildPhase buildPhase

new() = new CompilationGlobalsScope(DiagnosticsThreadStatics.DiagnosticsLogger, DiagnosticsThreadStatics.BuildPhase)

member _.DiagnosticsLogger = diagnosticsLogger
member _.BuildPhase = buildPhase

Expand Down
4 changes: 4 additions & 0 deletions src/Compiler/Facilities/DiagnosticsLogger.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ type DiagnosticsThreadStatics =

static member DiagnosticsLogger: DiagnosticsLogger with get, set

static member DiagnosticsLoggerNodeCode: DiagnosticsLogger with get, set

[<AutoOpen>]
module DiagnosticsLoggerExtensions =

Expand Down Expand Up @@ -458,6 +460,8 @@ type StackGuard =
type CompilationGlobalsScope =
new: diagnosticsLogger: DiagnosticsLogger * buildPhase: BuildPhase -> CompilationGlobalsScope

new: unit -> CompilationGlobalsScope

interface IDisposable

member DiagnosticsLogger: DiagnosticsLogger
Expand Down
26 changes: 14 additions & 12 deletions src/Compiler/Service/IncrementalBuild.fs
Original file line number Diff line number Diff line change
Expand Up @@ -375,11 +375,13 @@ type BoundModel private (
return diags
} |> GraphNode

let startComputingFullTypeCheck =
node {
let! _ = tcInfoExtras.GetOrComputeValue()
return! diagnostics.GetOrComputeValue()
}
//let startComputingFullTypeCheck() =
// async {
// use _ = InitCompilationGlobalsScope()
// do! tcInfoExtras.GetOrComputeValue() |> Async.AwaitNodeCode |> Async.Ignore
// do! diagnostics.GetOrComputeValue() |> Async.AwaitNodeCode |> Async.Ignore
// }
// |> Async.Start

let tcInfo, tcInfoExtras =
match tcStateOpt with
Expand All @@ -391,7 +393,7 @@ type BoundModel private (
GraphNode.FromResult tcInfo, tcInfoExtras
| _ ->
// start computing extras, so that typeCheckNode can be GC'd quickly
startComputingFullTypeCheck |> Async.AwaitNodeCode |> Async.Catch |> Async.Ignore |> Async.Start
// startComputingFullTypeCheck()
getTcInfo typeCheckNode, tcInfoExtras

member val Diagnostics = diagnostics
Expand Down Expand Up @@ -735,7 +737,7 @@ module IncrementalBuilderHelpers =
}

/// Finish up the typechecking to produce outputs for the rest of the compilation process
let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals partialCheck assemblyName outfile (boundModels: GraphNode<BoundModel> seq) =
let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals _partialCheck assemblyName outfile (boundModels: GraphNode<BoundModel> seq) =
node {
let diagnosticsLogger = CompilationDiagnosticLogger("FinalizeTypeCheckTask", tcConfig.diagnosticsOptions)
use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck)
Expand All @@ -751,13 +753,13 @@ module IncrementalBuilderHelpers =
let! latestImplFiles =
computedBoundModels
|> Seq.map (fun boundModel -> node {
if partialCheck then
return None
else
//if partialCheck then
// return None
//else
let! tcInfoExtras = boundModel.GetOrComputeTcInfoExtras()
return tcInfoExtras.latestImplFile
})
|> NodeCode.Parallel
|> NodeCode.Sequential

let results = [
for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles ->
Expand Down Expand Up @@ -826,7 +828,7 @@ module IncrementalBuilderHelpers =
let! partialDiagnostics =
computedBoundModels
|> Seq.map (fun m -> m.Diagnostics.GetOrComputeValue())
|> NodeCode.Parallel
|> NodeCode.Sequential
let diagnostics = [
diagnosticsLogger.GetDiagnostics()
yield! partialDiagnostics |> Seq.rev
Expand Down