Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test AsyncLocal diagnostics feasibility, again. #16778

Closed
wants to merge 11 commits into from
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