diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md index 672209d2ed1..bb1692a47cc 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md @@ -29,3 +29,4 @@ * Reduce allocations in compiler checking via `ValueOption` usage ([PR #16323](https://github.com/dotnet/fsharp/pull/16323), [PR #16567](https://github.com/dotnet/fsharp/pull/16567)) * Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536)) * Refactored parenthesization API. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461)) +* Replaced `ThreadStatic` diagnostics globals with `AsyncLocal` for better stability of Transparent Compiler. Removed `NodeCode` in favor of unflavored Async ([PR #16645](https://github.com/dotnet/fsharp/pull/16645)) \ No newline at end of file diff --git a/docs/release-notes/.VisualStudio/17.10.md b/docs/release-notes/.VisualStudio/17.10.md index a4b0a8a9f0d..05bcf918794 100644 --- a/docs/release-notes/.VisualStudio/17.10.md +++ b/docs/release-notes/.VisualStudio/17.10.md @@ -6,3 +6,4 @@ ### Changed * Use refactored parenthesization API in unnecessary parentheses code fix. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461)) +* Removed `NodeCode` in favor of unflavored Async with AsyncLocal state ([PR #16645](https://github.com/dotnet/fsharp/pull/16645)) \ No newline at end of file diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index 32298c0cf05..92ab18ab7d8 100644 --- a/src/Compiler/Driver/CompilerConfig.fs +++ b/src/Compiler/Driver/CompilerConfig.fs @@ -252,7 +252,7 @@ and IProjectReference = abstract FileName: string /// Evaluate raw contents of the assembly file generated by the project - abstract EvaluateRawContents: unit -> NodeCode + abstract EvaluateRawContents: unit -> Async /// Get the logical timestamp that would be the timestamp of the assembly file generated by the project /// diff --git a/src/Compiler/Driver/CompilerConfig.fsi b/src/Compiler/Driver/CompilerConfig.fsi index f59950f9e28..282e167bd1f 100644 --- a/src/Compiler/Driver/CompilerConfig.fsi +++ b/src/Compiler/Driver/CompilerConfig.fsi @@ -86,7 +86,7 @@ and IProjectReference = /// Evaluate raw contents of the assembly file generated by the project. /// 'None' may be returned if an in-memory view of the contents is, for some reason, /// not available. In this case the on-disk view of the contents will be preferred. - abstract EvaluateRawContents: unit -> NodeCode + abstract EvaluateRawContents: unit -> Async /// Get the logical timestamp that would be the timestamp of the assembly file generated by the project. /// diff --git a/src/Compiler/Driver/CompilerImports.fs b/src/Compiler/Driver/CompilerImports.fs index d8d9ccd9866..77c957f1cee 100644 --- a/src/Compiler/Driver/CompilerImports.fs +++ b/src/Compiler/Driver/CompilerImports.fs @@ -1735,7 +1735,7 @@ and [] TcImports m ) = - let startingErrorCount = DiagnosticsThreadStatics.DiagnosticsLogger.ErrorCount + let startingErrorCount = DiagnosticsAsyncState.DiagnosticsLogger.ErrorCount // Find assembly level TypeProviderAssemblyAttributes. These will point to the assemblies that // have class which implement ITypeProvider and which have TypeProviderAttribute on them. @@ -1936,7 +1936,7 @@ and [] TcImports with RecoverableException e -> errorRecovery e m - if startingErrorCount < DiagnosticsThreadStatics.DiagnosticsLogger.ErrorCount then + if startingErrorCount < DiagnosticsAsyncState.DiagnosticsLogger.ErrorCount then error (Error(FSComp.SR.etOneOrMoreErrorsSeenDuringExtensionTypeSetting (), m)) providers @@ -2158,14 +2158,14 @@ and [] TcImports ( ctok, r: AssemblyResolution - ) : NodeCode<(_ * (unit -> AvailableImportedAssembly list)) option> = - node { + ) : Async<(_ * (unit -> AvailableImportedAssembly list)) option> = + async { CheckDisposed() let m = r.originalReference.Range let fileName = r.resolvedPath let! contentsOpt = - node { + async { match r.ProjectReference with | Some ilb -> return! ilb.EvaluateRawContents() | None -> return ProjectAssemblyDataResult.Unavailable true @@ -2228,27 +2228,29 @@ and [] TcImports // NOTE: When used in the Language Service this can cause the transitive checking of projects. Hence it must be cancellable. member tcImports.RegisterAndImportReferencedAssemblies(ctok, nms: AssemblyResolution list) = - node { + async { CheckDisposed() let tcConfig = tcConfigP.Get ctok let runMethod = match tcConfig.parallelReferenceResolution with - | ParallelReferenceResolution.On -> NodeCode.Parallel - | ParallelReferenceResolution.Off -> NodeCode.Sequential - - let! results = - nms - |> List.map (fun nm -> - node { - try - return! tcImports.TryRegisterAndPrepareToImportReferencedDll(ctok, nm) - with e -> - errorR (Error(FSComp.SR.buildProblemReadingAssembly (nm.resolvedPath, e.Message), nm.originalReference.Range)) - return None - }) - |> runMethod + | ParallelReferenceResolution.On -> Async.Parallel + | ParallelReferenceResolution.Off -> Async.SequentialImmediate + + let! results = async { + use captureTasks = new CaptureDiagnosticsConcurrently(DiagnosticsAsyncState.DiagnosticsLogger) + return! nms |> List.map (fun nm -> + async { + use _ = UseDiagnosticsLogger captureTasks.LoggerForTask + try + return! tcImports.TryRegisterAndPrepareToImportReferencedDll(ctok, nm) + with e -> + errorR (Error(FSComp.SR.buildProblemReadingAssembly (nm.resolvedPath, e.Message), nm.originalReference.Range)) + return None + }) + |> runMethod + } let _dllinfos, phase2s = results |> Array.choose id |> List.ofArray |> List.unzip fixupOrphanCcus () @@ -2282,7 +2284,7 @@ and [] TcImports ReportWarnings warns tcImports.RegisterAndImportReferencedAssemblies(ctok, res) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation |> ignore true @@ -2383,7 +2385,7 @@ and [] TcImports // we dispose TcImports is because we need to dispose type providers, and type providers are never included in the framework DLL set. // If a framework set ever includes type providers, you will not have to worry about explicitly calling Dispose as the Finalizer will handle it. static member BuildFrameworkTcImports(tcConfigP: TcConfigProvider, frameworkDLLs, nonFrameworkDLLs) = - node { + async { let ctok = CompilationThreadToken() let tcConfig = tcConfigP.Get ctok @@ -2460,7 +2462,7 @@ and [] TcImports resolvedAssemblies |> List.choose tryFindEquivPrimaryAssembly let! fslibCcu, fsharpCoreAssemblyScopeRef = - node { + async { if tcConfig.compilingFSharpCore then // When compiling FSharp.Core.dll, the fslibCcu reference to FSharp.Core.dll is a delayed ccu thunk fixed up during type checking return CcuThunk.CreateDelayed getFSharpCoreLibraryName, ILScopeRef.Local @@ -2553,7 +2555,7 @@ and [] TcImports dependencyProvider ) = - node { + async { let ctok = CompilationThreadToken() let tcConfig = tcConfigP.Get ctok @@ -2571,7 +2573,7 @@ and [] TcImports } static member BuildTcImports(tcConfigP: TcConfigProvider, dependencyProvider) = - node { + async { let ctok = CompilationThreadToken() let tcConfig = tcConfigP.Get ctok @@ -2603,7 +2605,7 @@ let RequireReferences (ctok, tcImports: TcImports, tcEnv, thisAssemblyName, reso let ccuinfos = tcImports.RegisterAndImportReferencedAssemblies(ctok, resolutions) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation let asms = ccuinfos diff --git a/src/Compiler/Driver/CompilerImports.fsi b/src/Compiler/Driver/CompilerImports.fsi index ac06a25c2dc..9697e18968d 100644 --- a/src/Compiler/Driver/CompilerImports.fsi +++ b/src/Compiler/Driver/CompilerImports.fsi @@ -199,14 +199,14 @@ type TcImports = member internal Base: TcImports option static member BuildFrameworkTcImports: - TcConfigProvider * AssemblyResolution list * AssemblyResolution list -> NodeCode + TcConfigProvider * AssemblyResolution list * AssemblyResolution list -> Async static member BuildNonFrameworkTcImports: TcConfigProvider * TcImports * AssemblyResolution list * UnresolvedAssemblyReference list * DependencyProvider -> - NodeCode + Async static member BuildTcImports: - tcConfigP: TcConfigProvider * dependencyProvider: DependencyProvider -> NodeCode + tcConfigP: TcConfigProvider * dependencyProvider: DependencyProvider -> Async /// Process a group of #r in F# Interactive. /// Adds the reference to the tcImports and add the ccu to the type checking environment. diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 5a23c95ca7b..c6464caa522 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -1828,7 +1828,7 @@ let CheckMultipleInputsUsingGraphMode |> Graph.writeMermaidToFile graphFile) let _ = ctok // TODO Use it - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let diagnosticsLogger = DiagnosticsAsyncState.DiagnosticsLogger // In the first linear part of parallel checking, we use a 'checkForErrors' that checks either for errors // somewhere in the files processed prior to each one, or in the processing of this particular file. diff --git a/src/Compiler/Driver/fsc.fs b/src/Compiler/Driver/fsc.fs index 014ed840222..a09e748dff8 100644 --- a/src/Compiler/Driver/fsc.fs +++ b/src/Compiler/Driver/fsc.fs @@ -614,7 +614,7 @@ let main1 // Import basic assemblies let tcGlobals, frameworkTcImports = TcImports.BuildFrameworkTcImports(foundationalTcConfigP, sysRes, otherRes) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation let ilSourceDocs = [ @@ -663,7 +663,7 @@ let main1 let tcImports = TcImports.BuildNonFrameworkTcImports(tcConfigP, frameworkTcImports, otherRes, knownUnresolved, dependencyProvider) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation // register tcImports to be disposed in future disposables.Register tcImports diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index b780d91ca74..e4db56ba663 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -50,11 +50,11 @@ type internal MemoizeReply<'TValue> = | New of CancellationToken | Existing of Task<'TValue> -type internal MemoizeRequest<'TValue> = GetOrCompute of NodeCode<'TValue> * CancellationToken +type internal MemoizeRequest<'TValue> = GetOrCompute of Async<'TValue> * CancellationToken [] type internal Job<'TValue> = - | Running of TaskCompletionSource<'TValue> * CancellationTokenSource * NodeCode<'TValue> * DateTime * ResizeArray + | Running of TaskCompletionSource<'TValue> * CancellationTokenSource * Async<'TValue> * DateTime * ResizeArray | Completed of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list | Canceled of DateTime | Failed of DateTime * exn // TODO: probably we don't need to keep this @@ -354,15 +354,12 @@ 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 + post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) + return () + with | TaskCancelled _ -> Interlocked.Increment &cancel_exception_subsequent |> ignore @@ -481,14 +478,22 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Version = key.GetVersion() } - node { - let! ct = NodeCode.CancellationToken + let callerDiagnosticLogger = + if DiagnosticsAsyncState.DiagnosticsLogger = UninitializedDiagnosticsLogger then + // TODO: Telemetry? + DiagnosticsAsyncState.DiagnosticsLogger <- DiscardErrorsLogger + + DiagnosticsAsyncState.DiagnosticsLogger + + // Preserve outer diagnostics scope in case the computation doesn't. + let computation = computation |> Async.CompilationScope - let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger + async { + let! ct = Async.CancellationToken match! processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger - |> NodeCode.AwaitTask + |> Async.AwaitTask with | New internalCt -> @@ -500,21 +505,17 @@ 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 + use _ = UseDiagnosticsLogger cachingLogger log (Started, key) - try - let! result = computation |> Async.AwaitNodeCode - post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) - return result - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger + let! result = computation + post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) + return result }, cancellationToken = linkedCtSource.Token ) - |> NodeCode.AwaitTask + |> Async.AwaitTask with | TaskCancelled ex -> // TODO: do we need to do anything else here? Presumably it should be done by the registration on @@ -530,7 +531,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics))) return raise ex - | Existing job -> return! job |> NodeCode.AwaitTask + | Existing job -> return! job |> Async.AwaitTask } diff --git a/src/Compiler/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi index a34588e7af8..6ffc787d148 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fsi +++ b/src/Compiler/Facilities/AsyncMemoize.fsi @@ -65,9 +65,9 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member Clear: predicate: ('TKey -> bool) -> unit - member Get: key: ICacheKey<'TKey, 'TVersion> * computation: NodeCode<'TValue> -> NodeCode<'TValue> + member Get: key: ICacheKey<'TKey, 'TVersion> * computation: Async<'TValue> -> Async<'TValue> - member Get': key: 'TKey * computation: NodeCode<'TValue> -> NodeCode<'TValue> + member Get': key: 'TKey * computation: Async<'TValue> -> Async<'TValue> member Event: IEvent diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index 71f4d3da991..abb2fd0c52e 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -5,208 +5,46 @@ module FSharp.Compiler.BuildGraph open System open System.Threading open System.Threading.Tasks -open System.Diagnostics open System.Globalization -open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library - -[] -type NodeCode<'T> = Node of Async<'T> - -let wrapThreadStaticInfo computation = - async { - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase - - try - return! computation - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - } - -type Async<'T> with - - static member AwaitNodeCode(node: NodeCode<'T>) = - match node with - | Node(computation) -> wrapThreadStaticInfo computation - -[] -type NodeCodeBuilder() = - - static let zero = Node(async.Zero()) - - [] - member _.Zero() : NodeCode = zero - - [] - member _.Delay(f: unit -> NodeCode<'T>) = - Node( - async.Delay(fun () -> - match f () with - | Node(p) -> p) - ) - - [] - member _.Return value = Node(async.Return(value)) - - [] - member _.ReturnFrom(computation: NodeCode<_>) = computation - - [] - member _.Bind(Node(p): NodeCode<'a>, binder: 'a -> NodeCode<'b>) : NodeCode<'b> = - Node( - async.Bind( - p, - fun x -> - match binder x with - | Node p -> p - ) - ) - - [] - member _.TryWith(Node(p): NodeCode<'T>, binder: exn -> NodeCode<'T>) : NodeCode<'T> = - Node( - async.TryWith( - p, - fun ex -> - match binder ex with - | Node p -> p - ) - ) - - [] - member _.TryFinally(Node(p): NodeCode<'T>, binder: unit -> unit) : NodeCode<'T> = Node(async.TryFinally(p, binder)) - - [] - member _.For(xs: 'T seq, binder: 'T -> NodeCode) : NodeCode = - Node( - async.For( - xs, - fun x -> - match binder x with - | Node p -> p - ) - ) - - [] - 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 - } - ) - -let node = NodeCodeBuilder() +open FSharp.Compiler.DiagnosticsLogger [] -type NodeCode private () = - - static let cancellationToken = Node(wrapThreadStaticInfo Async.CancellationToken) +type Async = + static member CompilationScope(computation: Async<'T>) = + async { + use _ = + new CompilationGlobalsScope(DiagnosticsAsyncState.DiagnosticsLogger, DiagnosticsAsyncState.BuildPhase) - static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase + return! computation + } + static member RunImmediateWithoutCancellation(computation) = try - try - let work = - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - return! computation |> Async.AwaitNodeCode - } + Async + .StartImmediateAsTask(computation |> Async.CompilationScope, cancellationToken = CancellationToken.None) + .Result - Async.StartImmediateAsTask(work, cancellationToken = ct).Result - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase with :? AggregateException as ex when ex.InnerExceptions.Count = 1 -> raise (ex.InnerExceptions[0]) - static member RunImmediateWithoutCancellation(computation: NodeCode<'T>) = - NodeCode.RunImmediate(computation, CancellationToken.None) - - static member StartAsTask_ForTesting(computation: NodeCode<'T>, ?ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase - - try - let work = - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - return! computation |> Async.AwaitNodeCode - } - - Async.StartAsTask(work, cancellationToken = defaultArg ct CancellationToken.None) - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - - static member CancellationToken = cancellationToken - - static member FromCancellable(computation: Cancellable<'T>) = - Node(wrapThreadStaticInfo (Cancellable.toAsync computation)) - - static member AwaitAsync(computation: Async<'T>) = Node(wrapThreadStaticInfo computation) - - static member AwaitTask(task: Task<'T>) = - Node(wrapThreadStaticInfo (Async.AwaitTask task)) + static member FromCancellableWithScope(computation: Cancellable<'T>) = + computation |> Cancellable.toAsync |> Async.CompilationScope - static member AwaitTask(task: Task) = - Node(wrapThreadStaticInfo (Async.AwaitTask task)) + static member StartAsTask_ForTesting(computation: Async<'T>, ?ct: CancellationToken) = + Async.StartAsTask(computation |> Async.CompilationScope, cancellationToken = defaultArg ct CancellationToken.None) - static member AwaitWaitHandle_ForTesting(waitHandle: WaitHandle) = - Node(wrapThreadStaticInfo (Async.AwaitWaitHandle(waitHandle))) - - static member Sleep(ms: int) = - Node(wrapThreadStaticInfo (Async.Sleep(ms))) - - static member Sequential(computations: NodeCode<'T> seq) = - node { + static member SequentialImmediate(computations: Async<'T> seq) = + async { let results = ResizeArray() for computation in computations do - let! res = computation - results.Add(res) + let! result = computation + results.Add result return results.ToArray() } - static member Parallel(computations: NodeCode<'T> seq) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase - - computations - |> Seq.map (fun (Node x) -> - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - return! x - }) - |> Async.Parallel - |> wrapThreadStaticInfo - |> Node - [] module GraphNode = @@ -226,13 +64,13 @@ module GraphNode = | None -> () [] -type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption<'T>, cachedResultNode: NodeCode<'T>) = +type GraphNode<'T> private (computation: Async<'T>, cachedResult: ValueOption<'T>, cachedResultNode: Async<'T>) = let mutable computation = computation let mutable requestCount = 0 let mutable cachedResult = cachedResult - let mutable cachedResultNode: NodeCode<'T> = cachedResultNode + let mutable cachedResultNode: Async<'T> = cachedResultNode let isCachedResultNodeNotNull () = not (obj.ReferenceEquals(cachedResultNode, null)) @@ -244,11 +82,11 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption if isCachedResultNodeNotNull () then cachedResultNode else - node { + async { Interlocked.Increment(&requestCount) |> ignore try - let! ct = NodeCode.CancellationToken + let! ct = Async.CancellationToken // We must set 'taken' before any implicit cancellation checks // occur, making sure we are under the protection of the 'try'. @@ -267,22 +105,22 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ||| TaskContinuationOptions.NotOnFaulted ||| TaskContinuationOptions.ExecuteSynchronously) ) - |> NodeCode.AwaitTask + |> Async.AwaitTask match cachedResult with | ValueSome value -> return value | _ -> let tcs = TaskCompletionSource<'T>() - let (Node(p)) = computation + let p = computation Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! p + return! p |> Async.CompilationScope }, (fun res -> cachedResult <- ValueSome res - cachedResultNode <- node.Return res + cachedResultNode <- async.Return res computation <- Unchecked.defaultof<_> tcs.SetResult(res)), (fun ex -> tcs.SetException(ex)), @@ -290,7 +128,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption ct ) - return! tcs.Task |> NodeCode.AwaitTask + return! tcs.Task |> Async.AwaitTask finally if taken then semaphore.Release() |> ignore @@ -305,7 +143,7 @@ type GraphNode<'T> private (computation: NodeCode<'T>, cachedResult: ValueOption member _.IsComputing = requestCount > 0 static member FromResult(result: 'T) = - let nodeResult = node.Return result + let nodeResult = async.Return result GraphNode(nodeResult, ValueSome result, nodeResult) new(computation) = GraphNode(computation, ValueNone, Unchecked.defaultof<_>) diff --git a/src/Compiler/Facilities/BuildGraph.fsi b/src/Compiler/Facilities/BuildGraph.fsi index afbf9d2898b..ef05b61c122 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -2,89 +2,29 @@ module internal FSharp.Compiler.BuildGraph -open System -open System.Diagnostics open System.Threading open System.Threading.Tasks -open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library -/// Represents code that can be run as part of the build graph. -/// -/// This is essentially cancellable async code where the only asynchronous waits are on nodes. -/// When a node is evaluated the evaluation is run synchronously on the thread of the -/// first requestor. -[] -type NodeCode<'T> - -type Async<'T> with - - /// Asynchronously await code in the build graph - static member AwaitNodeCode: node: NodeCode<'T> -> Async<'T> - -/// A standard builder for node code. -[] -type NodeCodeBuilder = - - member Bind: NodeCode<'T> * ('T -> NodeCode<'U>) -> NodeCode<'U> - - member Zero: unit -> NodeCode - - member Delay: (unit -> NodeCode<'T>) -> NodeCode<'T> - - member Return: 'T -> NodeCode<'T> - - member ReturnFrom: NodeCode<'T> -> NodeCode<'T> - - member TryWith: NodeCode<'T> * (exn -> NodeCode<'T>) -> NodeCode<'T> - - member TryFinally: NodeCode<'T> * (unit -> unit) -> NodeCode<'T> - - member For: xs: 'T seq * binder: ('T -> NodeCode) -> NodeCode - - 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> - -/// Specifies code that can be run as part of the build graph. -val node: NodeCodeBuilder - /// Contains helpers to specify code that can be run as part of the build graph. [] -type NodeCode = - - /// Only used for testing, do not use - static member RunImmediate: computation: NodeCode<'T> * ct: CancellationToken -> 'T +type Async = /// Used in places where we don't care about cancellation, e.g. the command line compiler /// and F# Interactive - static member RunImmediateWithoutCancellation: computation: NodeCode<'T> -> 'T - - static member CancellationToken: NodeCode - - static member Sequential: computations: NodeCode<'T> seq -> NodeCode<'T[]> - - static member Parallel: computations: (NodeCode<'T> seq) -> NodeCode<'T[]> - - static member AwaitAsync: computation: Async<'T> -> NodeCode<'T> - - static member AwaitTask: task: Task<'T> -> NodeCode<'T> - - static member AwaitTask: task: Task -> NodeCode + static member RunImmediateWithoutCancellation: computation: Async<'T> -> 'T /// Execute the cancellable computation synchronously using the ambient cancellation token of /// the NodeCode. - static member FromCancellable: computation: Cancellable<'T> -> NodeCode<'T> + static member FromCancellableWithScope: computation: Cancellable<'T> -> Async<'T> - /// Only used for testing, do not use - static member StartAsTask_ForTesting: computation: NodeCode<'T> * ?ct: CancellationToken -> Task<'T> + /// Wrap the computation with CompilationGlobalsScope to restore current DiagnosticsAsyncState after it returns. + static member CompilationScope: computation: Async<'T> -> Async<'T> /// Only used for testing, do not use - static member AwaitWaitHandle_ForTesting: waitHandle: WaitHandle -> NodeCode + static member StartAsTask_ForTesting: computation: Async<'T> * ?ct: CancellationToken -> Task<'T> + + static member SequentialImmediate: computations: Async<'T> seq -> Async<'T array> /// Contains helpers related to the build graph [] @@ -102,7 +42,7 @@ module internal GraphNode = type internal GraphNode<'T> = /// - computation - The computation code to run. - new: computation: NodeCode<'T> -> GraphNode<'T> + new: computation: Async<'T> -> GraphNode<'T> /// Creates a GraphNode with given result already cached. static member FromResult: 'T -> GraphNode<'T> @@ -110,7 +50,7 @@ type internal GraphNode<'T> = /// Return NodeCode which, when executed, will get the value of the computation if already computed, or /// await an existing in-progress computation for the node if one exists, or else will synchronously /// start the computation on the current thread. - member GetOrComputeValue: unit -> NodeCode<'T> + member GetOrComputeValue: unit -> Async<'T> /// Return 'Some' if the computation has already been computed, else None if /// the computation is in-progress or has not yet been started. diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fs b/src/Compiler/Facilities/DiagnosticsLogger.fs index 08a46d1a25d..17b0705ec34 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.Threading /// Represents the style being used to format errors [] @@ -342,11 +343,12 @@ let DiscardErrorsLogger = member _.ErrorCount = 0 } -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 +let UninitializedDiagnosticsLogger = + { new DiagnosticsLogger("UninitialzedDiagnosticsLogger") with + member _.DiagnosticSink(diagnostic, severity) = + failwith "DiagnosticsAsyncState.DiagnosticsLogger not set." + + member _.ErrorCount = failwith "DiagnosticsAsyncState.DiagnosticsLogger not set." } type CapturingDiagnosticsLogger(nm, ?eagerFormat) = @@ -375,28 +377,23 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = errors |> Array.iter diagnosticsLogger.DiagnosticSink /// Type holds thread-static globals for use by the compiler. -type internal DiagnosticsThreadStatics = - [] - static val mutable private buildPhase: BuildPhase +type DiagnosticsAsyncState = + static let buildPhase = new AsyncLocal() + static let diagnosticsLogger = new AsyncLocal() - [] - static val mutable private diagnosticsLogger: DiagnosticsLogger - - static member BuildPhaseUnchecked = DiagnosticsThreadStatics.buildPhase + static let getOrCreate (holder: AsyncLocal<_>) defaultValue = + holder.Value + |> ValueOption.defaultWith (fun () -> + holder.Value <- ValueSome defaultValue + defaultValue) static member BuildPhase - with get () = - match box DiagnosticsThreadStatics.buildPhase with - | Null -> BuildPhase.DefaultPhase - | _ -> DiagnosticsThreadStatics.buildPhase - and set v = DiagnosticsThreadStatics.buildPhase <- v + with get () = getOrCreate buildPhase BuildPhase.DefaultPhase + and set v = buildPhase.Value <- ValueSome v static member DiagnosticsLogger - with get () = - match box DiagnosticsThreadStatics.diagnosticsLogger with - | Null -> AssertFalseDiagnosticsLogger - | _ -> DiagnosticsThreadStatics.diagnosticsLogger - and set v = DiagnosticsThreadStatics.diagnosticsLogger <- v + with get () = getOrCreate diagnosticsLogger UninitializedDiagnosticsLogger + and set v = diagnosticsLogger.Value <- ValueSome v [] module DiagnosticsLoggerExtensions = @@ -438,7 +435,7 @@ module DiagnosticsLoggerExtensions = | ReportedError _ -> PreserveStackTrace exn raise exn - | _ -> x.DiagnosticSink(PhasedDiagnostic.Create(exn, DiagnosticsThreadStatics.BuildPhase), severity) + | _ -> x.DiagnosticSink(PhasedDiagnostic.Create(exn, DiagnosticsAsyncState.BuildPhase), severity) member x.ErrorR exn = x.EmitDiagnostic(exn, FSharpDiagnosticSeverity.Error) @@ -498,32 +495,32 @@ module DiagnosticsLoggerExtensions = /// NOTE: The change will be undone when the returned "unwind" object disposes let UseBuildPhase (phase: BuildPhase) = - let oldBuildPhase = DiagnosticsThreadStatics.BuildPhaseUnchecked - DiagnosticsThreadStatics.BuildPhase <- phase + let oldBuildPhase = DiagnosticsAsyncState.BuildPhase + DiagnosticsAsyncState.BuildPhase <- phase { new IDisposable with member x.Dispose() = - DiagnosticsThreadStatics.BuildPhase <- oldBuildPhase + DiagnosticsAsyncState.BuildPhase <- oldBuildPhase } /// 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 oldLogger = DiagnosticsAsyncState.DiagnosticsLogger + DiagnosticsAsyncState.DiagnosticsLogger <- transformer oldLogger { new IDisposable with member _.Dispose() = - DiagnosticsThreadStatics.DiagnosticsLogger <- oldLogger + DiagnosticsAsyncState.DiagnosticsLogger <- oldLogger } let UseDiagnosticsLogger newLogger = UseTransformedDiagnosticsLogger(fun _ -> newLogger) let SetThreadBuildPhaseNoUnwind (phase: BuildPhase) = - DiagnosticsThreadStatics.BuildPhase <- phase + DiagnosticsAsyncState.BuildPhase <- phase let SetThreadDiagnosticsLoggerNoUnwind diagnosticsLogger = - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsAsyncState.DiagnosticsLogger <- diagnosticsLogger /// This represents the thread-local state established as each task function runs as part of the build. /// @@ -541,30 +538,43 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B unwindBP.Dispose() unwindEL.Dispose() +type CaptureDiagnosticsConcurrently(target) = + let loggers = System.Collections.Concurrent.ConcurrentQueue() + + member _.LoggerForTask: DiagnosticsLogger = + let logger = CapturingDiagnosticsLogger("One of parallel computations") + loggers.Enqueue logger + logger + + interface IDisposable with + member _.Dispose() = + for logger in loggers do + logger.CommitDelayedDiagnostics target + // Global functions are still used by parser and TAST ops. /// Raises an exception with error recovery and returns unit. let errorR exn = - DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR exn + DiagnosticsAsyncState.DiagnosticsLogger.ErrorR exn /// Raises a warning with error recovery and returns unit. let warning exn = - DiagnosticsThreadStatics.DiagnosticsLogger.Warning exn + DiagnosticsAsyncState.DiagnosticsLogger.Warning exn /// Raises a warning with error recovery and returns unit. let informationalWarning exn = - DiagnosticsThreadStatics.DiagnosticsLogger.InformationalWarning exn + DiagnosticsAsyncState.DiagnosticsLogger.InformationalWarning exn /// Raises a special exception and returns 'T - can be caught later at an errorRecovery point. let error exn = - DiagnosticsThreadStatics.DiagnosticsLogger.Error exn + DiagnosticsAsyncState.DiagnosticsLogger.Error exn /// Simulates an error. For test purposes only. let simulateError (diagnostic: PhasedDiagnostic) = - DiagnosticsThreadStatics.DiagnosticsLogger.SimulateError diagnostic + DiagnosticsAsyncState.DiagnosticsLogger.SimulateError diagnostic let diagnosticSink (diagnostic, severity) = - DiagnosticsThreadStatics.DiagnosticsLogger.DiagnosticSink(diagnostic, severity) + DiagnosticsAsyncState.DiagnosticsLogger.DiagnosticSink(diagnostic, severity) let errorSink diagnostic = diagnosticSink (diagnostic, FSharpDiagnosticSeverity.Error) @@ -573,13 +583,13 @@ let warnSink diagnostic = diagnosticSink (diagnostic, FSharpDiagnosticSeverity.Warning) let errorRecovery exn m = - DiagnosticsThreadStatics.DiagnosticsLogger.ErrorRecovery exn m + DiagnosticsAsyncState.DiagnosticsLogger.ErrorRecovery exn m let stopProcessingRecovery exn m = - DiagnosticsThreadStatics.DiagnosticsLogger.StopProcessingRecovery exn m + DiagnosticsAsyncState.DiagnosticsLogger.StopProcessingRecovery exn m let errorRecoveryNoRange exn = - DiagnosticsThreadStatics.DiagnosticsLogger.ErrorRecoveryNoRange exn + DiagnosticsAsyncState.DiagnosticsLogger.ErrorRecoveryNoRange exn let deprecatedWithError s m = errorR (Deprecated(s, m)) @@ -598,7 +608,7 @@ let mlCompatError s m = [] let suppressErrorReporting f = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let diagnosticsLogger = DiagnosticsAsyncState.DiagnosticsLogger try let diagnosticsLogger = @@ -857,14 +867,11 @@ type StackGuard(maxDepth: int, name: string) = try if depth % maxDepth = 0 then - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let buildPhase = DiagnosticsThreadStatics.BuildPhase let ct = Cancellable.Token async { do! Async.SwitchToNewThread() Thread.CurrentThread.Name <- $"F# Extra Compilation Thread for {name} (depth {depth})" - use _scope = new CompilationGlobalsScope(diagnosticsLogger, buildPhase) use _token = Cancellable.UsingToken ct return f () } diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index e9040da36ed..5273baea5fc 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -209,7 +209,7 @@ type DiagnosticsLogger = val DiscardErrorsLogger: DiagnosticsLogger /// Represents a DiagnosticsLogger that ignores diagnostics and asserts -val AssertFalseDiagnosticsLogger: DiagnosticsLogger +val UninitializedDiagnosticsLogger: DiagnosticsLogger /// Represents a DiagnosticsLogger that captures all diagnostics, optionally formatting them /// eagerly. @@ -228,12 +228,10 @@ type CapturingDiagnosticsLogger = /// Thread statics for the installed diagnostic logger [] -type DiagnosticsThreadStatics = +type DiagnosticsAsyncState = static member BuildPhase: BuildPhase with get, set - static member BuildPhaseUnchecked: BuildPhase - static member DiagnosticsLogger: DiagnosticsLogger with get, set [] @@ -286,6 +284,13 @@ val SetThreadBuildPhaseNoUnwind: phase: BuildPhase -> unit val SetThreadDiagnosticsLoggerNoUnwind: diagnosticsLogger: DiagnosticsLogger -> unit +type CaptureDiagnosticsConcurrently = + new: target: DiagnosticsLogger -> CaptureDiagnosticsConcurrently + + member LoggerForTask: DiagnosticsLogger + + interface IDisposable + /// Reports an error diagnostic and continues val errorR: exn: exn -> unit diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index e5ff5b6c754..310c7b07aff 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -4594,7 +4594,7 @@ type FsiEvaluationSession let tcConfig = tcConfigP.Get(ctokStartup) checker.FrameworkImportsCache.Get tcConfig - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation with e -> stopProcessingRecovery e range0 failwithf "Error creating evaluation session: %A" e @@ -4608,7 +4608,7 @@ type FsiEvaluationSession unresolvedReferences, fsiOptions.DependencyProvider ) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediateWithoutCancellation with e -> stopProcessingRecovery e range0 failwithf "Error creating evaluation session: %A" e diff --git a/src/Compiler/Service/BackgroundCompiler.fs b/src/Compiler/Service/BackgroundCompiler.fs index f9f952dde70..e20e1a61426 100644 --- a/src/Compiler/Service/BackgroundCompiler.fs +++ b/src/Compiler/Service/BackgroundCompiler.fs @@ -57,7 +57,7 @@ type internal IBackgroundCompiler = sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available. abstract member CheckFileInProjectAllowingStaleCachedResults: @@ -67,7 +67,7 @@ type internal IBackgroundCompiler = sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract member ClearCache: options: seq * userOpName: string -> unit @@ -83,31 +83,31 @@ type internal IBackgroundCompiler = symbol: FSharp.Compiler.Symbols.FSharpSymbol * canInvalidateProject: bool * userOpName: string -> - NodeCode> + Async> abstract member FindReferencesInFile: fileName: string * projectSnapshot: FSharpProjectSnapshot * symbol: FSharp.Compiler.Symbols.FSharpSymbol * userOpName: string -> - NodeCode> + Async> abstract member GetAssemblyData: options: FSharpProjectOptions * outputFileName: string * userOpName: string -> - NodeCode + Async abstract member GetAssemblyData: projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string -> - NodeCode + Async /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API) abstract member GetBackgroundCheckResultsForFileInProject: - fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode + fileName: string * options: FSharpProjectOptions * userOpName: string -> Async /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API) abstract member GetBackgroundParseResultsForFileInProject: - fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode + fileName: string * options: FSharpProjectOptions * userOpName: string -> Async abstract member GetCachedCheckFileResult: builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions -> - NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> + Async<(FSharpParseFileResults * FSharpCheckFileResults) option> abstract member GetProjectOptionsFromScript: fileName: string * @@ -125,31 +125,31 @@ type internal IBackgroundCompiler = abstract member GetSemanticClassificationForFile: fileName: string * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract member GetSemanticClassificationForFile: fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string -> - NodeCode + Async abstract member InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit - abstract member NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode + abstract member NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> Async abstract member NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async /// Parses and checks the source file and returns untyped AST and check results. abstract member ParseAndCheckFileInProject: fileName: string * fileVersion: int * sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract member ParseAndCheckFileInProject: fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> - NodeCode + Async /// Parse and typecheck the whole project. - abstract member ParseAndCheckProject: options: FSharpProjectOptions * userOpName: string -> NodeCode + abstract member ParseAndCheckProject: options: FSharpProjectOptions * userOpName: string -> Async - abstract member ParseAndCheckProject: projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode + abstract member ParseAndCheckProject: projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async abstract member ParseFile: fileName: string * sourceText: ISourceText * options: FSharpParsingOptions * cache: bool * flatErrors: bool * userOpName: string -> @@ -294,10 +294,11 @@ type internal BackgroundCompiler then { new IProjectReference with member x.EvaluateRawContents() = - node { + async { Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm) return! self.GetAssemblyData(opts, userOpName + ".CheckReferencedProject(" + nm + ")") } + |> Async.CompilationScope member x.TryGetLogicalTimeStamp(cache) = self.TryGetLogicalTimeStampForProject(cache, opts) @@ -308,8 +309,8 @@ type internal BackgroundCompiler | FSharpReferencedProject.PEReference(getStamp, delayedReader) -> { new IProjectReference with member x.EvaluateRawContents() = - node { - let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable + async { + let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> Async.FromCancellableWithScope match ilReaderOpt with | Some ilReader -> @@ -321,6 +322,7 @@ type internal BackgroundCompiler // continue to try to use an on-disk DLL return ProjectAssemblyDataResult.Unavailable false } + |> Async.CompilationScope member x.TryGetLogicalTimeStamp _ = getStamp () |> Some member x.FileName = delayedReader.OutputFile @@ -335,7 +337,7 @@ type internal BackgroundCompiler let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData return ProjectAssemblyDataResult.Available data } - |> NodeCode.FromCancellable + |> Async.FromCancellableWithScope member x.TryGetLogicalTimeStamp _ = getStamp () |> Some member x.FileName = nm @@ -345,7 +347,7 @@ type internal BackgroundCompiler /// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also /// creates an incremental builder used by the command line compiler. let CreateOneIncrementalBuilder (options: FSharpProjectOptions, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.CreateOneIncrementalBuilder" [| Activity.Tags.project, options.ProjectFileName |] @@ -452,14 +454,14 @@ type internal BackgroundCompiler let tryGetBuilderNode options = incrementalBuildersCache.TryGet(AnyCallerThread, options) - let tryGetBuilder options : NodeCode option = + let tryGetBuilder options : Async option = tryGetBuilderNode options |> Option.map (fun x -> x.GetOrComputeValue()) - let tryGetSimilarBuilder options : NodeCode option = + let tryGetSimilarBuilder options : Async option = incrementalBuildersCache.TryGetSimilar(AnyCallerThread, options) |> Option.map (fun x -> x.GetOrComputeValue()) - let tryGetAnyBuilder options : NodeCode option = + let tryGetAnyBuilder options : Async option = incrementalBuildersCache.TryGetAny(AnyCallerThread, options) |> Option.map (fun x -> x.GetOrComputeValue()) @@ -473,16 +475,16 @@ type internal BackgroundCompiler getBuilderNode) let createAndGetBuilder (options, userOpName) = - node { - let! ct = NodeCode.CancellationToken + async { + let! ct = Async.CancellationToken let getBuilderNode = createBuilderNode (options, userOpName, ct) return! getBuilderNode.GetOrComputeValue() } - let getOrCreateBuilder (options, userOpName) : NodeCode = + let getOrCreateBuilder (options, userOpName) : Async = match tryGetBuilder options with | Some getBuilder -> - node { + async { match! getBuilder with | builderOpt, creationDiags when builderOpt.IsNone || not builderOpt.Value.IsReferencesInvalidated -> return builderOpt, creationDiags @@ -534,7 +536,7 @@ type internal BackgroundCompiler | _ -> let res = GraphNode( - node { + async { let! res = self.CheckOneFileImplAux( parseResults, @@ -620,7 +622,7 @@ type internal BackgroundCompiler /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API) member _.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetBackgroundParseResultsForFileInProject" @@ -660,7 +662,7 @@ type internal BackgroundCompiler } member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |] @@ -697,9 +699,9 @@ type internal BackgroundCompiler tcPrior: PartialCheckResults, tcInfo: TcInfo, creationDiags: FSharpDiagnostic[] - ) : NodeCode = + ) : Async = - node { + async { // Get additional script #load closure information if applicable. // For scripts, this will have been recorded by GetProjectOptionsFromScript. let tcConfig = tcPrior.TcConfig @@ -727,7 +729,7 @@ type internal BackgroundCompiler keepAssemblyContents, suggestNamesForErrors ) - |> NodeCode.FromCancellable + |> Async.FromCancellableWithScope GraphNode.SetPreferredUILang tcConfig.preferredUiLang return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp) @@ -746,7 +748,7 @@ type internal BackgroundCompiler creationDiags: FSharpDiagnostic[] ) = - node { + async { match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with | Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results | _ -> @@ -767,7 +769,7 @@ type internal BackgroundCompiler options, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults" @@ -778,7 +780,7 @@ type internal BackgroundCompiler |] let! cachedResults = - node { + async { let! builderOpt, creationDiags = getAnyBuilder (options, userOpName) match builderOpt with @@ -827,7 +829,7 @@ type internal BackgroundCompiler options, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.CheckFileInProject" @@ -875,7 +877,7 @@ type internal BackgroundCompiler options: FSharpProjectOptions, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.ParseAndCheckFileInProject" @@ -910,7 +912,7 @@ type internal BackgroundCompiler ) GraphNode.SetPreferredUILang tcPrior.TcConfig.preferredUiLang - let! ct = NodeCode.CancellationToken + let! ct = Async.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -944,7 +946,7 @@ type internal BackgroundCompiler } member _.NotifyFileChanged(fileName, options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.NotifyFileChanged" @@ -963,7 +965,7 @@ type internal BackgroundCompiler /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API) member _.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.ParseAndCheckFileInProject" @@ -1075,7 +1077,7 @@ type internal BackgroundCompiler canInvalidateProject: bool, userOpName: string ) = - node { + async { use _ = Activity.start "BackgroundCompiler.FindReferencesInFile" @@ -1103,7 +1105,7 @@ type internal BackgroundCompiler } member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetSemanticClassificationForFile" @@ -1160,7 +1162,7 @@ type internal BackgroundCompiler /// Parse and typecheck the whole project (the implementation, called recursively as project graph is evaluated) member private _.ParseAndCheckProjectImpl(options, userOpName) = - node { + async { let! builderOpt, creationDiags = getOrCreateBuilder (options, userOpName) @@ -1233,7 +1235,7 @@ type internal BackgroundCompiler } member _.GetAssemblyData(options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetAssemblyData" @@ -1384,7 +1386,7 @@ type internal BackgroundCompiler return options, (diags @ diagnostics.Diagnostics) } - |> Cancellable.toAsync + |> Async.FromCancellableWithScope member bc.InvalidateConfiguration(options: FSharpProjectOptions, userOpName) = use _ = @@ -1486,7 +1488,7 @@ type internal BackgroundCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.CheckFileInProject(parseResults, fileName, fileVersion, sourceText, options, userOpName) member _.CheckFileInProjectAllowingStaleCachedResults @@ -1497,7 +1499,7 @@ type internal BackgroundCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.CheckFileInProjectAllowingStaleCachedResults(parseResults, fileName, fileVersion, sourceText, options, userOpName) member _.ClearCache(options: seq, userOpName: string) : unit = self.ClearCache(options, userOpName) @@ -1516,7 +1518,7 @@ type internal BackgroundCompiler symbol: FSharpSymbol, canInvalidateProject: bool, userOpName: string - ) : NodeCode> = + ) : Async> = self.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) member this.FindReferencesInFile(fileName, projectSnapshot, symbol, userOpName) = @@ -1524,12 +1526,7 @@ type internal BackgroundCompiler member _.FrameworkImportsCache: FrameworkImportsCache = self.FrameworkImportsCache - member _.GetAssemblyData - ( - options: FSharpProjectOptions, - _fileName: string, - userOpName: string - ) : NodeCode = + member _.GetAssemblyData(options: FSharpProjectOptions, _fileName: string, userOpName: string) : Async = self.GetAssemblyData(options, userOpName) member _.GetAssemblyData @@ -1537,7 +1534,7 @@ type internal BackgroundCompiler projectSnapshot: FSharpProjectSnapshot, _fileName: string, userOpName: string - ) : NodeCode = + ) : Async = self.GetAssemblyData(projectSnapshot.ToOptions(), userOpName) member _.GetBackgroundCheckResultsForFileInProject @@ -1545,7 +1542,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) member _.GetBackgroundParseResultsForFileInProject @@ -1553,7 +1550,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) member _.GetCachedCheckFileResult @@ -1562,7 +1559,7 @@ type internal BackgroundCompiler fileName: string, sourceText: ISourceText, options: FSharpProjectOptions - ) : NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> = + ) : Async<(FSharpParseFileResults * FSharpCheckFileResults) option> = self.GetCachedCheckFileResult(builder, fileName, sourceText, options) member _.GetProjectOptionsFromScript @@ -1598,7 +1595,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetSemanticClassificationForFile(fileName, options, userOpName) member _.GetSemanticClassificationForFile @@ -1606,13 +1603,13 @@ type internal BackgroundCompiler fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string - ) : NodeCode = + ) : Async = self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName) member _.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit = self.InvalidateConfiguration(options, userOpName) - member _.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : NodeCode = + member _.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : Async = self.NotifyFileChanged(fileName, options, userOpName) member _.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async = @@ -1625,7 +1622,7 @@ type internal BackgroundCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName) member _.ParseAndCheckFileInProject @@ -1633,22 +1630,22 @@ type internal BackgroundCompiler fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string - ) : NodeCode = - node { + ) : Async = + async { let fileSnapshot = projectSnapshot.ProjectSnapshot.SourceFiles |> Seq.find (fun f -> f.FileName = fileName) - let! sourceText = fileSnapshot.GetSource() |> NodeCode.AwaitTask + let! sourceText = fileSnapshot.GetSource() |> Async.AwaitTask let options = projectSnapshot.ToOptions() return! self.ParseAndCheckFileInProject(fileName, 0, sourceText, options, userOpName) } - member _.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : NodeCode = + member _.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : Async = self.ParseAndCheckProject(options, userOpName) - member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : NodeCode = + member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : Async = self.ParseAndCheckProject(projectSnapshot.ToOptions(), userOpName) member _.ParseFile @@ -1666,7 +1663,6 @@ type internal BackgroundCompiler let options = projectSnapshot.ToOptions() self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode member _.ProjectChecked: IEvent = self.ProjectChecked diff --git a/src/Compiler/Service/BackgroundCompiler.fsi b/src/Compiler/Service/BackgroundCompiler.fsi index f3bf3c96ccc..4fa73fc3b61 100644 --- a/src/Compiler/Service/BackgroundCompiler.fsi +++ b/src/Compiler/Service/BackgroundCompiler.fsi @@ -32,7 +32,7 @@ type internal IBackgroundCompiler = sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async /// Type-check the result obtained by parsing, but only if the antecedent type checking context is available. abstract CheckFileInProjectAllowingStaleCachedResults: @@ -42,7 +42,7 @@ type internal IBackgroundCompiler = sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract ClearCache: options: FSharpProjectOptions seq * userOpName: string -> unit @@ -57,7 +57,7 @@ type internal IBackgroundCompiler = projectSnapshot: FSharpProjectSnapshot * symbol: FSharp.Compiler.Symbols.FSharpSymbol * userOpName: string -> - NodeCode + Async abstract FindReferencesInFile: fileName: string * @@ -65,28 +65,27 @@ type internal IBackgroundCompiler = symbol: FSharp.Compiler.Symbols.FSharpSymbol * canInvalidateProject: bool * userOpName: string -> - NodeCode + Async abstract GetAssemblyData: projectSnapshot: FSharpProjectSnapshot * outputFileName: string * userOpName: string -> - NodeCode + Async abstract GetAssemblyData: - options: FSharpProjectOptions * outputFileName: string * userOpName: string -> - NodeCode + options: FSharpProjectOptions * outputFileName: string * userOpName: string -> Async /// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API) abstract GetBackgroundCheckResultsForFileInProject: fileName: string * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async /// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API) abstract GetBackgroundParseResultsForFileInProject: - fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode + fileName: string * options: FSharpProjectOptions * userOpName: string -> Async abstract GetCachedCheckFileResult: builder: IncrementalBuilder * fileName: string * sourceText: ISourceText * options: FSharpProjectOptions -> - NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> + Async<(FSharpParseFileResults * FSharpCheckFileResults) option> abstract GetProjectOptionsFromScript: fileName: string * @@ -104,21 +103,21 @@ type internal IBackgroundCompiler = abstract GetSemanticClassificationForFile: fileName: string * snapshot: FSharpProjectSnapshot * userOpName: string -> - NodeCode + Async abstract GetSemanticClassificationForFile: fileName: string * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract InvalidateConfiguration: options: FSharpProjectOptions * userOpName: string -> unit - abstract NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> NodeCode + abstract NotifyFileChanged: fileName: string * options: FSharpProjectOptions * userOpName: string -> Async abstract NotifyProjectCleaned: options: FSharpProjectOptions * userOpName: string -> Async abstract ParseAndCheckFileInProject: fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> - NodeCode + Async /// Parses and checks the source file and returns untyped AST and check results. abstract ParseAndCheckFileInProject: @@ -127,14 +126,14 @@ type internal IBackgroundCompiler = sourceText: ISourceText * options: FSharpProjectOptions * userOpName: string -> - NodeCode + Async abstract ParseAndCheckProject: - projectSnapshot: FSharpProjectSnapshot * userOpName: string -> NodeCode + projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async /// Parse and typecheck the whole project. abstract ParseAndCheckProject: - options: FSharpProjectOptions * userOpName: string -> NodeCode + options: FSharpProjectOptions * userOpName: string -> Async abstract ParseFile: fileName: string * projectSnapshot: FSharpProjectSnapshot * userOpName: string -> Async diff --git a/src/Compiler/Service/FSharpProjectSnapshot.fs b/src/Compiler/Service/FSharpProjectSnapshot.fs index cdc7ffe9252..07b730bd16b 100644 --- a/src/Compiler/Service/FSharpProjectSnapshot.fs +++ b/src/Compiler/Service/FSharpProjectSnapshot.fs @@ -19,6 +19,7 @@ open System.Runtime.CompilerServices open FSharp.Compiler.Syntax open FSharp.Compiler.Diagnostics open FSharp.Compiler.DiagnosticsLogger +open FSharp.Compiler.BuildGraph type internal ProjectIdentifier = string * string @@ -586,7 +587,7 @@ and [] FSha async.Return <| FSharpReferencedProjectSnapshot.ILModuleReference(outputName, getStamp, getReader)) - |> Async.Sequential + |> Async.SequentialImmediate let referencesOnDisk, otherOptions = options.OtherOptions diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..a81d1d167d5 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -136,7 +136,7 @@ module IncrementalBuildSyntaxTree = ), sourceRange, fileName, [||] let parse (source: FSharpSource) = - node { + async { IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed fileName) use _ = Activity.start "IncrementalBuildSyntaxTree.parse" @@ -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() let input = - match text :?> TextContainer with + match text with | TextContainer.Stream(stream) -> ParseOneInputStream(tcConfig, lexResourceManager, fileName, isLastCompiland, diagnosticsLogger, false, stream) | TextContainer.SourceText(sourceText) -> @@ -252,8 +252,8 @@ type BoundModel private ( ?tcStateOpt: GraphNode * GraphNode ) = - let getTypeCheck (syntaxTree: SyntaxTree) : NodeCode = - node { + let getTypeCheck (syntaxTree: SyntaxTree) : Async = + async { let! input, _sourceRange, fileName, parseErrors = syntaxTree.ParseNode.GetOrComputeValue() use _ = Activity.start "BoundModel.TypeCheck" [|Activity.Tags.fileName, fileName|] @@ -277,7 +277,7 @@ type BoundModel private ( None, TcResultsSink.WithSink sink, prevTcInfo.tcState, input ) - |> NodeCode.FromCancellable + |> Async.FromCancellableWithScope fileChecked.Trigger fileName @@ -322,13 +322,13 @@ type BoundModel private ( | _ -> None let getTcInfo (typeCheck: GraphNode) = - node { + async { let! tcInfo , _, _, _, _ = typeCheck.GetOrComputeValue() return tcInfo } |> GraphNode let getTcInfoExtras (typeCheck: GraphNode) = - node { + async { let! _ , sink, implFile, fileName, _ = typeCheck.GetOrComputeValue() // Build symbol keys let itemKeyStore, semanticClassification = @@ -366,17 +366,17 @@ type BoundModel private ( } } |> GraphNode - let defaultTypeCheck = node { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, "default typecheck - no syntaxTree", [||] } + let defaultTypeCheck = async { return prevTcInfo, TcResultsSinkImpl(tcGlobals), None, "default typecheck - no syntaxTree", [||] } let typeCheckNode = syntaxTreeOpt |> Option.map getTypeCheck |> Option.defaultValue defaultTypeCheck |> GraphNode let tcInfoExtras = getTcInfoExtras typeCheckNode let diagnostics = - node { + async { let! _, _, _, _, diags = typeCheckNode.GetOrComputeValue() return diags } |> GraphNode let startComputingFullTypeCheck = - node { + async { let! _ = tcInfoExtras.GetOrComputeValue() return! diagnostics.GetOrComputeValue() } @@ -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.CompilationScope |> Async.Catch |> Async.Ignore |> Async.Start getTcInfo typeCheckNode, tcInfoExtras member val Diagnostics = diagnostics @@ -417,13 +417,13 @@ type BoundModel private ( member this.GetOrComputeTcInfoExtras = this.TcInfoExtras.GetOrComputeValue - member this.GetOrComputeTcInfoWithExtras() = node { + member this.GetOrComputeTcInfoWithExtras() = async { let! tcInfo = this.TcInfo.GetOrComputeValue() let! tcInfoExtras = this.TcInfoExtras.GetOrComputeValue() return tcInfo, tcInfoExtras } - member this.Next(syntaxTree) = node { + member this.Next(syntaxTree) = async { let! tcInfo = this.TcInfo.GetOrComputeValue() return BoundModel( @@ -442,7 +442,7 @@ type BoundModel private ( } member this.Finish(finalTcDiagnosticsRev, finalTopAttribs) = - node { + async { let! tcInfo = this.TcInfo.GetOrComputeValue() let finishState = { tcInfo with tcDiagnosticsRev = finalTcDiagnosticsRev; topAttribs = finalTopAttribs } return @@ -536,7 +536,7 @@ type FrameworkImportsCache(size) = match frameworkTcImportsCache.TryGet (AnyCallerThread, key) with | Some lazyWork -> lazyWork | None -> - let lazyWork = GraphNode(node { + let lazyWork = GraphNode(async { let tcConfigP = TcConfigProvider.Constant tcConfig return! TcImports.BuildFrameworkTcImports (tcConfigP, frameworkDLLs, nonFrameworkResolutions) }) @@ -548,7 +548,7 @@ type FrameworkImportsCache(size) = /// This function strips the "System" assemblies from the tcConfig and returns a age-cached TcImports for them. member this.Get(tcConfig: TcConfig) = - node { + async { // Split into installed and not installed. let frameworkDLLs, nonFrameworkResolutions, unresolved = TcAssemblyResolutions.SplitNonFoundationalResolutions(tcConfig) let node = this.GetNode(tcConfig, frameworkDLLs, nonFrameworkResolutions) @@ -579,13 +579,13 @@ type PartialCheckResults (boundModel: BoundModel, timeStamp: DateTime, projectTi member _.GetOrComputeTcInfoWithExtras() = boundModel.GetOrComputeTcInfoWithExtras() member _.GetOrComputeItemKeyStoreIfEnabled() = - node { + async { let! info = boundModel.GetOrComputeTcInfoExtras() return info.itemKeyStore } member _.GetOrComputeSemanticClassificationIfEnabled() = - node { + async { let! info = boundModel.GetOrComputeTcInfoExtras() return info.semanticClassificationKeyStore } @@ -658,14 +658,14 @@ module IncrementalBuilderHelpers = #if !NO_TYPEPROVIDERS ,importsInvalidatedByTypeProvider: Event #endif - ) : NodeCode = + ) : Async = - node { + async { let diagnosticsLogger = CompilationDiagnosticLogger("CombineImportedAssembliesTask", tcConfig.diagnosticsOptions) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter) let! tcImports = - node { + async { try let! tcImports = TcImports.BuildNonFrameworkTcImports(tcConfigP, frameworkTcImports, nonFrameworkResolutions, unresolvedReferences, dependencyProvider) #if !NO_TYPEPROVIDERS @@ -736,28 +736,28 @@ module IncrementalBuilderHelpers = /// Finish up the typechecking to produce outputs for the rest of the compilation process let FinalizeTypeCheckTask (tcConfig: TcConfig) tcGlobals partialCheck assemblyName outfile (boundModels: GraphNode seq) = - node { + async { let diagnosticsLogger = CompilationDiagnosticLogger("FinalizeTypeCheckTask", tcConfig.diagnosticsOptions) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.TypeCheck) - let! computedBoundModels = boundModels |> Seq.map (fun g -> g.GetOrComputeValue()) |> NodeCode.Sequential + let! computedBoundModels = boundModels |> Seq.map (fun g -> g.GetOrComputeValue()) |> Async.SequentialImmediate let! tcInfos = computedBoundModels - |> Seq.map (fun boundModel -> node { return! boundModel.GetOrComputeTcInfo() }) - |> NodeCode.Sequential + |> Seq.map (fun boundModel -> async { return! boundModel.GetOrComputeTcInfo() }) + |> Async.SequentialImmediate // tcInfoExtras can be computed in parallel. This will check any previously skipped implementation files in parallel, too. let! latestImplFiles = computedBoundModels - |> Seq.map (fun boundModel -> node { + |> Seq.map (fun boundModel -> async { if partialCheck then return None else let! tcInfoExtras = boundModel.GetOrComputeTcInfoExtras() return tcInfoExtras.latestImplFile }) - |> NodeCode.Parallel + |> Async.Parallel let results = [ for tcInfo, latestImplFile in Seq.zip tcInfos latestImplFiles -> @@ -826,7 +826,7 @@ module IncrementalBuilderHelpers = let! partialDiagnostics = computedBoundModels |> Seq.map (fun m -> m.Diagnostics.GetOrComputeValue()) - |> NodeCode.Parallel + |> Async.Parallel let diagnostics = [ diagnosticsLogger.GetDiagnostics() yield! partialDiagnostics |> Seq.rev @@ -949,13 +949,13 @@ module IncrementalBuilderStateHelpers = type BuildStatus = Invalidated | Good let createBoundModelGraphNode (prevBoundModel: GraphNode) syntaxTree = - GraphNode(node { + GraphNode(async { let! prevBoundModel = prevBoundModel.GetOrComputeValue() return! prevBoundModel.Next(syntaxTree) }) let createFinalizeBoundModelGraphNode (initialState: IncrementalBuilderInitialState) (boundModels: GraphNode seq) = - GraphNode(node { + GraphNode(async { use _ = Activity.start "GetCheckResultsAndImplementationsForProject" [|Activity.Tags.project, initialState.outfile|] let! result = FinalizeTypeCheckTask @@ -1123,7 +1123,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc tryGetSlot state (slot - 1) let evalUpToTargetSlot (state: IncrementalBuilderState) targetSlot = - node { + async { if targetSlot < 0 then return Some(initialBoundModel, defaultTimeStamp) else @@ -1155,8 +1155,8 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let mutable currentState = state let setCurrentState state cache (ct: CancellationToken) = - node { - do! semaphore.WaitAsync(ct) |> NodeCode.AwaitTask + async { + do! semaphore.WaitAsync(ct) |> Async.AwaitTask try ct.ThrowIfCancellationRequested() currentState <- computeStampedFileNames initialState state cache @@ -1165,8 +1165,8 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc } let checkFileTimeStamps (cache: TimeStampCache) = - node { - let! ct = NodeCode.CancellationToken + async { + let! ct = Async.CancellationToken do! setCurrentState currentState cache ct } @@ -1196,7 +1196,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc member _.AllDependenciesDeprecated = allDependencies member _.PopulatePartialCheckingResults () = - node { + async { let cache = TimeStampCache defaultTimeStamp // One per step do! checkFileTimeStamps cache let! _ = currentState.finalizedBoundModel.GetOrComputeValue() @@ -1238,7 +1238,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc (builder.TryGetCheckResultsBeforeFileInProject fileName).IsSome member builder.GetCheckResultsBeforeSlotInProject slotOfFile = - node { + async { let cache = TimeStampCache defaultTimeStamp do! checkFileTimeStamps cache let! result = evalUpToTargetSlot currentState (slotOfFile - 1) @@ -1250,7 +1250,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc } member builder.GetFullCheckResultsBeforeSlotInProject slotOfFile = - node { + async { let cache = TimeStampCache defaultTimeStamp do! checkFileTimeStamps cache let! result = evalUpToTargetSlot currentState (slotOfFile - 1) @@ -1275,7 +1275,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc builder.GetFullCheckResultsBeforeSlotInProject slotOfFile member builder.GetFullCheckResultsAfterFileInProject fileName = - node { + async { let slotOfFile = builder.GetSlotOfFileName fileName + 1 let! result = builder.GetFullCheckResultsBeforeSlotInProject(slotOfFile) return result @@ -1285,7 +1285,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc builder.GetCheckResultsBeforeSlotInProject(builder.GetSlotsCount()) member builder.GetCheckResultsAndImplementationsForProject() = - node { + async { let cache = TimeStampCache(defaultTimeStamp) do! checkFileTimeStamps cache let! result = currentState.finalizedBoundModel.GetOrComputeValue() @@ -1297,7 +1297,7 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc } member builder.GetFullCheckResultsAndImplementationsForProject() = - node { + async { let! result = builder.GetCheckResultsAndImplementationsForProject() let results, _, _, _ = result let! _ = results.GetOrComputeTcInfoWithExtras() // Make sure we forcefully evaluate the info @@ -1342,14 +1342,14 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let slotOfFile = builder.GetSlotOfFileName fileName let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() - |> Async.AwaitNodeCode + |> Async.CompilationScope |> Async.RunSynchronously member builder.NotifyFileChanged(fileName, timeStamp) = - node { + async { let slotOfFile = builder.GetSlotOfFileName fileName let cache = TimeStampCache defaultTimeStamp - let! ct = NodeCode.CancellationToken + let! ct = Async.CancellationToken do! setCurrentState { currentState with slots = currentState.slots |> List.updateAt slotOfFile (currentState.slots[slotOfFile].Notify timeStamp) } @@ -1388,14 +1388,14 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let useSimpleResolutionSwitch = "--simpleresolution" - node { + async { // Trap and report diagnostics from creation. let delayedLogger = CapturingDiagnosticsLogger("IncrementalBuilderCreation") use _ = new CompilationGlobalsScope(delayedLogger, BuildPhase.Parameter) let! builderOpt = - node { + async { try // Create the builder. diff --git a/src/Compiler/Service/IncrementalBuild.fsi b/src/Compiler/Service/IncrementalBuild.fsi index 0dedfb02948..0fd380631e4 100644 --- a/src/Compiler/Service/IncrementalBuild.fsi +++ b/src/Compiler/Service/IncrementalBuild.fsi @@ -38,7 +38,7 @@ type internal FrameworkImportsCacheKey = type internal FrameworkImportsCache = new: size: int -> FrameworkImportsCache - member Get: TcConfig -> NodeCode + member Get: TcConfig -> Async member Clear: unit -> unit @@ -121,25 +121,25 @@ type internal PartialCheckResults = /// Compute the "TcInfo" part of the results. If `enablePartialTypeChecking` is false then /// extras will also be available. - member GetOrComputeTcInfo: unit -> NodeCode + member GetOrComputeTcInfo: unit -> Async /// Compute both the "TcInfo" and "TcInfoExtras" parts of the results. /// Can cause a second type-check if `enablePartialTypeChecking` is true in the checker. /// Only use when it's absolutely necessary to get rich information on a file. - member GetOrComputeTcInfoWithExtras: unit -> NodeCode + member GetOrComputeTcInfoWithExtras: unit -> Async /// Compute the "ItemKeyStore" parts of the results. /// Can cause a second type-check if `enablePartialTypeChecking` is true in the checker. /// Only use when it's absolutely necessary to get rich information on a file. /// /// Will return 'None' for enableBackgroundItemKeyStoreAndSemanticClassification=false. - member GetOrComputeItemKeyStoreIfEnabled: unit -> NodeCode + member GetOrComputeItemKeyStoreIfEnabled: unit -> Async /// Can cause a second type-check if `enablePartialTypeChecking` is true in the checker. /// Only use when it's absolutely necessary to get rich information on a file. /// /// Will return 'None' for enableBackgroundItemKeyStoreAndSemanticClassification=false. - member GetOrComputeSemanticClassificationIfEnabled: unit -> NodeCode + member GetOrComputeSemanticClassificationIfEnabled: unit -> Async member TimeStamp: DateTime @@ -194,7 +194,7 @@ type internal IncrementalBuilder = member AllDependenciesDeprecated: string[] /// The project build. Return true if the background work is finished. - member PopulatePartialCheckingResults: unit -> NodeCode + member PopulatePartialCheckingResults: unit -> Async /// Get the preceding typecheck state of a slot, without checking if it is up-to-date w.r.t. /// the timestamps on files and referenced DLLs prior to this one. Return None if the result is not available. @@ -228,38 +228,36 @@ type internal IncrementalBuilder = /// Get the preceding typecheck state of a slot. Compute the entire type check of the project up /// to the necessary point if the result is not available. This may be a long-running operation. - member GetCheckResultsBeforeFileInProject: fileName: string -> NodeCode + member GetCheckResultsBeforeFileInProject: fileName: string -> Async /// Get the preceding typecheck state of a slot. Compute the entire type check of the project up /// to the necessary point if the result is not available. This may be a long-running operation. /// This will get full type-check info for the file, meaning no partial type-checking. - member GetFullCheckResultsBeforeFileInProject: fileName: string -> NodeCode + member GetFullCheckResultsBeforeFileInProject: fileName: string -> Async /// Get the typecheck state after checking a file. Compute the entire type check of the project up /// to the necessary point if the result is not available. This may be a long-running operation. - member GetCheckResultsAfterFileInProject: fileName: string -> NodeCode + member GetCheckResultsAfterFileInProject: fileName: string -> Async /// Get the typecheck state after checking a file. Compute the entire type check of the project up /// to the necessary point if the result is not available. This may be a long-running operation. /// This will get full type-check info for the file, meaning no partial type-checking. - member GetFullCheckResultsAfterFileInProject: fileName: string -> NodeCode + member GetFullCheckResultsAfterFileInProject: fileName: string -> Async /// Get the typecheck result after the end of the last file. The typecheck of the project is not 'completed'. /// This may be a long-running operation. - member GetCheckResultsAfterLastFileInProject: unit -> NodeCode + member GetCheckResultsAfterLastFileInProject: unit -> Async /// Get the final typecheck result. If 'generateTypedImplFiles' was set on Create then the CheckedAssemblyAfterOptimization will contain implementations. /// This may be a long-running operation. member GetCheckResultsAndImplementationsForProject: - unit -> - NodeCode + unit -> Async /// Get the final typecheck result. If 'generateTypedImplFiles' was set on Create then the CheckedAssemblyAfterOptimization will contain implementations. /// This may be a long-running operation. /// This will get full type-check info for the project, meaning no partial type-checking. member GetFullCheckResultsAndImplementationsForProject: - unit -> - NodeCode + unit -> Async /// Get the logical time stamp that is associated with the output of the project if it were fully built immediately member GetLogicalTimeStampForProject: TimeStampCache -> DateTime @@ -273,7 +271,7 @@ type internal IncrementalBuilder = member GetParseResultsForFile: fileName: string -> ParsedInput * range * string * (PhasedDiagnostic * FSharpDiagnosticSeverity)[] - member NotifyFileChanged: fileName: string * timeStamp: DateTime -> NodeCode + member NotifyFileChanged: fileName: string * timeStamp: DateTime -> Async /// Create the incremental builder static member TryCreateIncrementalBuilderForProjectOptions: @@ -299,7 +297,7 @@ type internal IncrementalBuilder = getSource: (string -> Async) option * useChangeNotifications: bool * useSyntaxTreeCache: bool -> - NodeCode + Async /// Generalized Incremental Builder. This is exposed only for unit testing purposes. module internal IncrementalBuild = diff --git a/src/Compiler/Service/StandardError.txt b/src/Compiler/Service/StandardError.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 1d3ccf36906..f1eab1d9b6f 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -387,7 +387,7 @@ type internal TransparentCompiler = caches.ScriptClosure.Get( projectSnapshot.FileKey fileName, - node { + async { let useFsiAuxLib = defaultArg useFsiAuxLib true let useSdkRefs = defaultArg useSdkRefs true let reduceMemoryUsage = ReduceMemoryFlag.Yes @@ -440,7 +440,7 @@ type internal TransparentCompiler caches.FrameworkImports.Get( key, - node { + async { use _ = Activity.start "ComputeFrameworkImports" [] let tcConfigP = TcConfigProvider.Constant tcConfig @@ -464,14 +464,14 @@ type internal TransparentCompiler importsInvalidatedByTypeProvider: Event ) = - node { + async { let diagnosticsLogger = CompilationDiagnosticLogger("CombineImportedAssembliesTask", tcConfig.diagnosticsOptions) use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Parameter) let! tcImports = - node { + async { try let! tcImports = TcImports.BuildNonFrameworkTcImports( @@ -576,7 +576,7 @@ type internal TransparentCompiler then { new IProjectReference with member x.EvaluateRawContents() = - node { + async { Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm) return! @@ -596,8 +596,8 @@ type internal TransparentCompiler | FSharpReferencedProjectSnapshot.PEReference(getStamp, delayedReader) -> { new IProjectReference with member x.EvaluateRawContents() = - node { - let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> NodeCode.FromCancellable + async { + let! ilReaderOpt = delayedReader.TryGetILModuleReader() |> Async.FromCancellableWithScope match ilReaderOpt with | Some ilReader -> @@ -623,7 +623,7 @@ type internal TransparentCompiler let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData return ProjectAssemblyDataResult.Available data } - |> NodeCode.FromCancellable + |> Async.FromCancellableWithScope member x.TryGetLogicalTimeStamp _ = getStamp () |> Some member x.FileName = nm @@ -631,7 +631,7 @@ type internal TransparentCompiler ] let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshot) = - node { + async { let useSimpleResolutionSwitch = "--simpleresolution" let commandLineArgs = projectSnapshot.CommandLineOptions let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir @@ -651,8 +651,8 @@ type internal TransparentCompiler let! (loadClosureOpt: LoadClosure option) = match projectSnapshot.SourceFiles, projectSnapshot.UseScriptResolutionRules with | [ fsxFile ], true -> // assuming UseScriptResolutionRules and a single source file means we are doing this for a script - node { - let! source = fsxFile.GetSource() |> NodeCode.AwaitTask + async { + let! source = fsxFile.GetSource() |> Async.AwaitTask let! closure = ComputeScriptClosure @@ -668,7 +668,7 @@ type internal TransparentCompiler return (Some closure) } - | _ -> node { return None } + | _ -> async { return None } let sdkDirOverride = match loadClosureOpt with @@ -745,7 +745,7 @@ type internal TransparentCompiler caches.BootstrapInfoStatic.Get( projectSnapshot.CacheKeyWith("BootstrapInfoStatic", assemblyName), - node { + async { use _ = Activity.start "ComputeBootstrapInfoStatic" @@ -821,7 +821,7 @@ type internal TransparentCompiler ) let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) = - node { + async { let! tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot @@ -897,7 +897,7 @@ type internal TransparentCompiler caches.BootstrapInfo.Get( projectSnapshot.NoFileVersionsKey, - node { + async { use _ = Activity.start "ComputeBootstrapInfo" [| Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |] @@ -906,7 +906,7 @@ type internal TransparentCompiler use _ = new CompilationGlobalsScope(delayedLogger, BuildPhase.Parameter) let! bootstrapInfoOpt = - node { + async { try return! computeBootstrapInfoInner projectSnapshot with exn -> @@ -939,8 +939,8 @@ type internal TransparentCompiler // TODO: Not sure if we should cache this. For VS probably not. Maybe it can be configurable by FCS user. let LoadSource (file: FSharpFileSnapshot) isExe isLastCompiland = - node { - let! source = file.GetSource() |> NodeCode.AwaitTask + async { + let! source = file.GetSource() |> Async.AwaitTask return FSharpFileSnapshotWithSource( @@ -953,13 +953,13 @@ type internal TransparentCompiler } let LoadSources (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshot) = - node { + async { let isExe = bootstrapInfo.TcConfig.target.IsExe let! sources = projectSnapshot.SourceFiles |> Seq.map (fun f -> LoadSource f isExe (f.FileName = bootstrapInfo.LastFileName)) - |> NodeCode.Parallel + |> Async.Parallel return ProjectSnapshotWithSources(projectSnapshot.ProjectCore, sources |> Array.toList) @@ -983,7 +983,7 @@ type internal TransparentCompiler caches.ParseFile.Get( key, - node { + async { use _ = Activity.start "ComputeParseFile" @@ -1027,7 +1027,7 @@ type internal TransparentCompiler |> Graph.make let computeDependencyGraph (tcConfig: TcConfig) parsedInputs (processGraph: Graph -> Graph) = - node { + async { let sourceFiles: FileInProject array = parsedInputs |> Seq.toArray @@ -1161,7 +1161,7 @@ type internal TransparentCompiler caches.TcIntermediate.Get( key, - node { + async { let file = projectSnapshot.SourceFiles[index] @@ -1238,8 +1238,7 @@ type internal TransparentCompiler prevTcInfo.tcState, input, true) - |> Cancellable.toAsync - |> NodeCode.AwaitAsync + |> Async.FromCancellableWithScope //fileChecked.Trigger fileName @@ -1265,9 +1264,7 @@ type internal TransparentCompiler match fileNode with | NodeToTypeCheck.PhysicalFile index -> - let! tcIntermediate = - ComputeTcIntermediate projectSnapshot dependencyFiles index fileNode bootstrapInfo tcInfo - |> Async.AwaitNodeCode + let! tcIntermediate = ComputeTcIntermediate projectSnapshot dependencyFiles index fileNode bootstrapInfo tcInfo let (Finisher(node = node; finisher = finisher)) = tcIntermediate.finisher @@ -1366,11 +1363,11 @@ type internal TransparentCompiler } let parseSourceFiles (projectSnapshot: ProjectSnapshotWithSources) tcConfig = - node { + async { let! parsedInputs = projectSnapshot.SourceFiles |> Seq.map (ComputeParseFile projectSnapshot tcConfig) - |> NodeCode.Parallel + |> Async.Parallel return ProjectSnapshotBase<_>(projectSnapshot.ProjectCore, parsedInputs |> Array.toList) } @@ -1381,7 +1378,7 @@ type internal TransparentCompiler caches.TcLastFile.Get( projectSnapshot.FileKey fileName, - node { + async { let file = projectSnapshot.SourceFiles |> List.last use _ = @@ -1396,7 +1393,6 @@ type internal TransparentCompiler graph (processGraphNode projectSnapshot bootstrapInfo dependencyFiles false) bootstrapInfo.InitialTcInfo - |> NodeCode.AwaitAsync let lastResult = results |> List.head |> snd @@ -1405,7 +1401,7 @@ type internal TransparentCompiler ) let getParseResult (projectSnapshot: ProjectSnapshot) creationDiags file (tcConfig: TcConfig) = - node { + async { let! parsedFile = ComputeParseFile projectSnapshot tcConfig file let parseDiagnostics = @@ -1438,7 +1434,7 @@ type internal TransparentCompiler let ComputeParseAndCheckFileInProject (fileName: string) (projectSnapshot: ProjectSnapshot) = caches.ParseAndCheckFileInProject.Get( projectSnapshot.FileKey fileName, - node { + async { use _ = Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] @@ -1549,7 +1545,7 @@ type internal TransparentCompiler let ComputeParseAndCheckAllFilesInProject (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) = caches.ParseAndCheckAllFilesInProject.Get( projectSnapshot.FullKey, - node { + async { use _ = Activity.start "ComputeParseAndCheckAllFilesInProject" @@ -1564,14 +1560,14 @@ type internal TransparentCompiler graph (processGraphNode projectSnapshot bootstrapInfo dependencyFiles true) bootstrapInfo.InitialTcInfo - |> NodeCode.AwaitAsync + } ) let ComputeProjectExtras (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) = caches.ProjectExtras.Get( projectSnapshot.SignatureKey, - node { + async { let! results, finalInfo = ComputeParseAndCheckAllFilesInProject bootstrapInfo projectSnapshot @@ -1664,7 +1660,7 @@ type internal TransparentCompiler let ComputeAssemblyData (projectSnapshot: ProjectSnapshot) fileName = caches.AssemblyData.Get( projectSnapshot.SignatureKey, - node { + async { try @@ -1713,7 +1709,7 @@ type internal TransparentCompiler let ComputeParseAndCheckProject (projectSnapshot: ProjectSnapshot) = caches.ParseAndCheckProject.Get( projectSnapshot.FullKey, - node { + async { match! ComputeBootstrapInfo projectSnapshot with | None, creationDiags -> @@ -1786,7 +1782,7 @@ type internal TransparentCompiler ) let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) = - node { + async { match! ComputeBootstrapInfo projectSnapshot with | None, _ -> return None | Some bootstrapInfo, _creationDiags -> @@ -1801,7 +1797,7 @@ type internal TransparentCompiler let ComputeSemanticClassification (fileName: string, projectSnapshot: ProjectSnapshot) = caches.SemanticClassification.Get( projectSnapshot.FileKey fileName, - node { + async { use _ = Activity.start "ComputeSemanticClassification" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] @@ -1831,7 +1827,7 @@ type internal TransparentCompiler let ComputeItemKeyStore (fileName: string, projectSnapshot: ProjectSnapshot) = caches.ItemKeyStore.Get( projectSnapshot.FileKey fileName, - node { + async { use _ = Activity.start "ComputeItemKeyStore" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] @@ -1866,10 +1862,12 @@ type internal TransparentCompiler ) member _.ParseFile(fileName, projectSnapshot: ProjectSnapshot, _userOpName) = - node { + async { //use _ = // Activity.start "ParseFile" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] + // TODO: We don't do anything with the resulting diagnostics. This is here to not fail in case there is no global logger. + use _ = new DiagnosticsScope(false) // TODO: might need to deal with exceptions here: let! tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot @@ -1896,7 +1894,7 @@ type internal TransparentCompiler member _.FindReferencesInFile(fileName: string, projectSnapshot: ProjectSnapshot, symbol: FSharpSymbol, userOpName: string) = ignore userOpName - node { + async { match! ComputeItemKeyStore(fileName, projectSnapshot) with | None -> return Seq.empty | Some itemKeyStore -> return itemKeyStore.FindAll symbol.Item @@ -1921,11 +1919,9 @@ type internal TransparentCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) - |> NodeCode.AwaitAsync + ) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) ignore parseResults @@ -1942,11 +1938,9 @@ type internal TransparentCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) - |> NodeCode.AwaitAsync + ) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) ignore parseResults @@ -1994,13 +1988,11 @@ type internal TransparentCompiler symbol: FSharpSymbol, canInvalidateProject: bool, userOpName: string - ) : NodeCode> = - node { + ) : Async> = + async { ignore canInvalidateProject - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) return! this.FindReferencesInFile(fileName, snapshot.ProjectSnapshot, symbol, userOpName) } @@ -2011,11 +2003,9 @@ type internal TransparentCompiler member _.FrameworkImportsCache: FrameworkImportsCache = backgroundCompiler.FrameworkImportsCache - member this.GetAssemblyData(options: FSharpProjectOptions, fileName, userOpName: string) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + member this.GetAssemblyData(options: FSharpProjectOptions, fileName, userOpName: string) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) return! this.GetAssemblyData(snapshot.ProjectSnapshot, fileName, userOpName) } @@ -2025,7 +2015,7 @@ type internal TransparentCompiler projectSnapshot: FSharpProjectSnapshot, fileName, userOpName: string - ) : NodeCode = + ) : Async = this.GetAssemblyData(projectSnapshot.ProjectSnapshot, fileName, userOpName) member this.GetBackgroundCheckResultsForFileInProject @@ -2033,11 +2023,9 @@ type internal TransparentCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + ) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) match! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName) with | parseResult, FSharpCheckFileAnswer.Succeeded checkResult -> return parseResult, checkResult @@ -2049,11 +2037,9 @@ type internal TransparentCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + ) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) return! this.ParseFile(fileName, snapshot.ProjectSnapshot, userOpName) } @@ -2064,13 +2050,11 @@ type internal TransparentCompiler fileName: string, sourceText: ISourceText, options: FSharpProjectOptions - ) : NodeCode<(FSharpParseFileResults * FSharpCheckFileResults) option> = - node { + ) : Async<(FSharpParseFileResults * FSharpCheckFileResults) option> = + async { ignore builder - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, fileName, 1, sourceText, documentSource) - |> NodeCode.AwaitAsync + let! snapshot = FSharpProjectSnapshot.FromOptions(options, fileName, 1, sourceText, documentSource) match! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, "GetCachedCheckFileResult") with | parseResult, FSharpCheckFileAnswer.Succeeded checkResult -> return Some(parseResult, checkResult) @@ -2106,7 +2090,7 @@ type internal TransparentCompiler ) member this.GetSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string) = - node { + async { ignore userOpName return! ComputeSemanticClassification(fileName, snapshot.ProjectSnapshot) } @@ -2116,13 +2100,11 @@ type internal TransparentCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { + ) : Async = + async { ignore userOpName - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) return! ComputeSemanticClassification(fileName, snapshot.ProjectSnapshot) } @@ -2130,7 +2112,7 @@ type internal TransparentCompiler member this.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit = backgroundCompiler.InvalidateConfiguration(options, userOpName) - member this.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : NodeCode = + member this.NotifyFileChanged(fileName: string, options: FSharpProjectOptions, userOpName: string) : Async = backgroundCompiler.NotifyFileChanged(fileName, options, userOpName) member this.NotifyProjectCleaned(options: FSharpProjectOptions, userOpName: string) : Async = @@ -2143,11 +2125,9 @@ type internal TransparentCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = - node { - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) - |> NodeCode.AwaitAsync + ) : Async = + async { + let! snapshot = FSharpProjectSnapshot.FromOptions(options, fileName, fileVersion, sourceText, documentSource) return! this.ParseAndCheckFileInProject(fileName, snapshot.ProjectSnapshot, userOpName) } @@ -2155,26 +2135,23 @@ type internal TransparentCompiler member this.ParseAndCheckFileInProject(fileName: string, projectSnapshot: FSharpProjectSnapshot, userOpName: string) = this.ParseAndCheckFileInProject(fileName, projectSnapshot.ProjectSnapshot, userOpName) - member this.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : NodeCode = - node { + member this.ParseAndCheckProject(options: FSharpProjectOptions, userOpName: string) : Async = + async { ignore userOpName - let! snapshot = - FSharpProjectSnapshot.FromOptions(options, documentSource) - |> NodeCode.AwaitAsync + let! snapshot = FSharpProjectSnapshot.FromOptions(options, documentSource) return! ComputeParseAndCheckProject snapshot.ProjectSnapshot } - member this.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : NodeCode = - node { + member this.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, userOpName: string) : Async = + async { ignore userOpName return! ComputeParseAndCheckProject projectSnapshot.ProjectSnapshot } member this.ParseFile(fileName, projectSnapshot, userOpName) = this.ParseFile(fileName, projectSnapshot.ProjectSnapshot, userOpName) - |> Async.AwaitNodeCode member this.ParseFile ( diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 14562f34f15..e021ffd602a 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -158,19 +158,19 @@ type internal TransparentCompiler = member FindReferencesInFile: fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * symbol: FSharpSymbol * userOpName: string -> - NodeCode + Async member GetAssemblyData: projectSnapshot: ProjectSnapshot.ProjectSnapshot * fileName: string * _userOpName: string -> - NodeCode + Async member ParseAndCheckFileInProject: fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * userOpName: string -> - NodeCode + Async member ParseFile: fileName: string * projectSnapshot: ProjectSnapshot.ProjectSnapshot * _userOpName: 'a -> - NodeCode + Async member SetCacheSizeFactor: sizeFactor: int -> unit diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 803755ab13f..b17f3746240 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -325,13 +325,11 @@ type FSharpChecker let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode member _.GetBackgroundCheckResultsForFileInProject(fileName, options, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) - |> Async.AwaitNodeCode /// Try to get recent approximate type check results for a file. member _.TryGetRecentCheckResultsForFile(fileName: string, options: FSharpProjectOptions, ?sourceText, ?userOpName: string) = @@ -390,7 +388,6 @@ type FSharpChecker let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.NotifyFileChanged(fileName, options, userOpName) - |> Async.AwaitNodeCode /// Typecheck a source code file, returning a handle to the results of the /// parse including the reconstructed types in the file. @@ -413,7 +410,6 @@ type FSharpChecker options, userOpName ) - |> Async.AwaitNodeCode /// Typecheck a source code file, returning a handle to the results of the /// parse including the reconstructed types in the file. @@ -429,7 +425,6 @@ type FSharpChecker let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.CheckFileInProject(parseResults, fileName, fileVersion, sourceText, options, userOpName) - |> Async.AwaitNodeCode /// Typecheck a source code file, returning a handle to the results of the /// parse including the reconstructed types in the file. @@ -444,25 +439,21 @@ type FSharpChecker let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName) - |> Async.AwaitNodeCode member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: FSharpProjectSnapshot, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.ParseAndCheckFileInProject(fileName, projectSnapshot, userOpName) - |> Async.AwaitNodeCode member _.ParseAndCheckProject(options: FSharpProjectOptions, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.ParseAndCheckProject(options, userOpName) - |> Async.AwaitNodeCode member _.ParseAndCheckProject(projectSnapshot: FSharpProjectSnapshot, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.ParseAndCheckProject(projectSnapshot, userOpName) - |> Async.AwaitNodeCode member _.FindBackgroundReferencesInFile ( @@ -476,7 +467,7 @@ type FSharpChecker let canInvalidateProject = defaultArg canInvalidateProject true let userOpName = defaultArg userOpName "Unknown" - node { + async { if fastCheck <> Some true || not captureIdentifiersWhenParsing then return! backgroundCompiler.FindReferencesInFile(fileName, options, symbol, canInvalidateProject, userOpName) else @@ -490,15 +481,12 @@ type FSharpChecker else return Seq.empty } - |> Async.AwaitNodeCode member _.FindBackgroundReferencesInFile(fileName: string, projectSnapshot: FSharpProjectSnapshot, symbol: FSharpSymbol, ?userOpName: string) = let userOpName = defaultArg userOpName "Unknown" - node { - let! parseResults = - backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName) - |> NodeCode.AwaitAsync + async { + let! parseResults = backgroundCompiler.ParseFile(fileName, projectSnapshot, userOpName) if parseResults.ParseTree.Identifiers |> Set.contains symbol.DisplayNameCore @@ -508,19 +496,16 @@ type FSharpChecker else return Seq.empty } - |> Async.AwaitNodeCode member _.GetBackgroundSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, ?userOpName) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetSemanticClassificationForFile(fileName, options, userOpName) - |> Async.AwaitNodeCode member _.GetBackgroundSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, ?userOpName) = let userOpName = defaultArg userOpName "Unknown" backgroundCompiler.GetSemanticClassificationForFile(fileName, snapshot, userOpName) - |> Async.AwaitNodeCode /// For a given script file, get the ProjectOptions implied by the #load closure member _.GetProjectOptionsFromScript diff --git a/src/Compiler/Utilities/Cancellable.fs b/src/Compiler/Utilities/Cancellable.fs index 59e7def4c10..2d99cfa15ae 100644 --- a/src/Compiler/Utilities/Cancellable.fs +++ b/src/Compiler/Utilities/Cancellable.fs @@ -6,37 +6,20 @@ open Internal.Utilities.Library [] type Cancellable = - [] - static val mutable private tokens: CancellationToken list + static let token = AsyncLocal() + + static member UsingToken(ct) = + let previousToken = token.Value + token.Value <- ct - static let disposable = { new IDisposable with - member this.Dispose() = - Cancellable.Tokens <- Cancellable.Tokens |> List.tail + member this.Dispose() = token.Value <- previousToken } - static member Tokens - with private get () = - match box Cancellable.tokens with - | Null -> [] - | _ -> Cancellable.tokens - and private set v = Cancellable.tokens <- v - - static member UsingToken(ct) = - Cancellable.Tokens <- ct :: Cancellable.Tokens - disposable - - static member Token = - match Cancellable.Tokens with - | [] -> CancellationToken.None - | token :: _ -> token + static member Token = token.Value - /// There may be multiple tokens if `UsingToken` is called multiple times, producing scoped structure. - /// We're interested in the current, i.e. the most recent, one. static member CheckAndThrow() = - match Cancellable.Tokens with - | [] -> () - | token :: _ -> token.ThrowIfCancellationRequested() + token.Value.ThrowIfCancellationRequested() namespace Internal.Utilities.Library diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 7c252019e2d..9385758fae3 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -28,9 +28,9 @@ let waitUntil condition value = } let rec internal spinFor (duration: TimeSpan) = - node { + async { let sw = Stopwatch.StartNew() - do! Async.Sleep 10 |> NodeCode.AwaitAsync + do! Async.Sleep 10 let remaining = duration - sw.Elapsed if remaining > TimeSpan.Zero then return! spinFor remaining @@ -60,8 +60,8 @@ type internal EventRecorder<'a, 'b, 'c when 'a : equality and 'b : equality>(mem [] let ``Basics``() = - let computation key = node { - do! Async.Sleep 1 |> NodeCode.AwaitAsync + let computation key = async { + do! Async.Sleep 1 return key * 2 } @@ -77,8 +77,8 @@ let ``Basics``() = memoize.Get'(3, computation 3) memoize.Get'(2, computation 2) } - |> NodeCode.Parallel - |> NodeCode.RunImmediateWithoutCancellation + |> Async.Parallel + |> Async.RunImmediateWithoutCancellation let expected = [| 10; 10; 4; 10; 6; 4|] @@ -97,7 +97,7 @@ let ``We can cancel a job`` () = let jobStarted = new ManualResetEvent(false) - let computation action = node { + let computation action = async { action() |> ignore do! spinFor timeout failwith "Should be canceled before it gets here" @@ -112,13 +112,13 @@ let ``We can cancel a job`` () = let key = 1 - let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation jobStarted.Set), ct = cts1.Token) + let _task1 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation jobStarted.Set), ct = cts1.Token) waitFor jobStarted jobStarted.Reset() |> ignore - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts2.Token) - let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts3.Token) + let _task2 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts2.Token) + let _task3 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation ignore), ct = cts3.Token) do! waitUntil (events.CountOf Requested) 3 @@ -148,7 +148,7 @@ let ``Job is restarted if first requestor cancels`` () = let jobCanComplete = new ManualResetEvent(false) - let computation key = node { + let computation key = async { jobStarted.Set() |> ignore waitFor jobCanComplete return key * 2 @@ -164,13 +164,13 @@ let ``Job is restarted if first requestor cancels`` () = let key = 1 - let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + let _task1 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) waitFor jobStarted jobStarted.Reset() |> ignore - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) - let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + let _task2 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) do! waitUntil (events.CountOf Requested) 3 @@ -199,7 +199,7 @@ let ``Job is restarted if first requestor cancels but keeps running if second re let jobCanComplete = new ManualResetEvent(false) - let computation key = node { + let computation key = async { jobStarted.Set() |> ignore waitFor jobCanComplete return key * 2 @@ -215,13 +215,13 @@ let ``Job is restarted if first requestor cancels but keeps running if second re let key = 1 - let _task1 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) + let _task1 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts1.Token) waitFor jobStarted jobStarted.Reset() |> ignore - let _task2 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) - let _task3 = NodeCode.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) + let _task2 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts2.Token) + let _task3 = Async.StartAsTask_ForTesting( memoize.Get'(key, computation key), ct = cts3.Token) do! waitUntil (events.CountOf Requested) 3 @@ -277,21 +277,21 @@ let ``Stress test`` () = while (int s.ElapsedMilliseconds) < durationMs do number <- number + 1 % 12345 return [result] - } |> NodeCode.AwaitAsync + } let rec sleepyComputation durationMs result = - node { + async { if rng.NextDouble() < (exceptionProbability / (float durationMs / float stepMs)) then raise (ExpectedException()) if durationMs > 0 then - do! Async.Sleep (min stepMs durationMs) |> NodeCode.AwaitAsync + do! Async.Sleep (min stepMs durationMs) return! sleepyComputation (durationMs - stepMs) result else return [result] } let rec mixedComputation durationMs result = - node { + async { if durationMs > 0 then if rng.NextDouble() < 0.5 then let! _ = intenseComputation (min stepMs durationMs) () @@ -333,7 +333,7 @@ let ``Stress test`` () = let result = key * 2 let job = cache.Get'(key, computation durationMs result) let cts = new CancellationTokenSource() - let runningJob = NodeCode.StartAsTask_ForTesting(job, ct = cts.Token) + let runningJob = Async.StartAsTask_ForTesting(job, ct = cts.Token) cts.CancelAfter timeoutMs Interlocked.Increment &started |> ignore try @@ -387,7 +387,7 @@ let ``Cancel running jobs with the same key`` cancelDuplicate expectFinished = let job2started = new ManualResetEvent(false) let job2finished = new ManualResetEvent(false) - let work onStart onFinish = node { + let work onStart onFinish = async { Interlocked.Increment &started |> ignore onStart() |> ignore waitFor jobCanContinue @@ -402,7 +402,7 @@ let ``Cancel running jobs with the same key`` cancelDuplicate expectFinished = member _.GetVersion() = 1 member _.GetLabel() = "key1" } - cache.Get(key1, work job1started.Set job1finished.Set) |> Async.AwaitNodeCode |> Async.Start + cache.Get(key1, work job1started.Set job1finished.Set) |> Async.Start waitFor job1started @@ -412,7 +412,7 @@ let ``Cancel running jobs with the same key`` cancelDuplicate expectFinished = member _.GetVersion() = key1.GetVersion() + 1 member _.GetLabel() = "key2" } - cache.Get(key2, work job2started.Set job2finished.Set ) |> Async.AwaitNodeCode |> Async.Start + cache.Get(key2, work job2started.Set job2finished.Set ) |> Async.Start waitFor job2started @@ -440,18 +440,18 @@ let ``Preserve thread static diagnostics`` () = let job1Cache = AsyncMemoize() let job2Cache = AsyncMemoize() - let job1 (input: string) = node { - let! _ = Async.Sleep (rng.Next(1, 30)) |> NodeCode.AwaitAsync + let job1 (input: string) = async { + let! _ = Async.Sleep (rng.Next(1, 30)) let ex = DummyException("job1 error") - DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR(ex) + DiagnosticsAsyncState.DiagnosticsLogger.ErrorR(ex) return Ok input } - let job2 (input: int) = node { + let job2 (input: int) = async { - DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("job2 error 1")) + DiagnosticsAsyncState.DiagnosticsLogger.Warning(DummyException("job2 error 1")) - let! _ = Async.Sleep (rng.Next(1, 30)) |> NodeCode.AwaitAsync + let! _ = Async.Sleep (rng.Next(1, 30)) let key = { new ICacheKey<_, _> with member _.GetKey() = "job1" @@ -460,7 +460,7 @@ let ``Preserve thread static diagnostics`` () = let! result = job1Cache.Get(key, job1 "${input}" ) - DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("job2 error 2")) + DiagnosticsAsyncState.DiagnosticsLogger.Warning(DummyException("job2 error 2")) return input, result @@ -475,7 +475,7 @@ let ``Preserve thread static diagnostics`` () = use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) - DiagnosticsThreadStatics.DiagnosticsLogger.Warning(DummyException("task error")) + DiagnosticsAsyncState.DiagnosticsLogger.Warning(DummyException("task error")) let key = { new ICacheKey<_, _> with @@ -483,7 +483,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() @@ -514,9 +514,9 @@ let ``Preserve thread static diagnostics already completed job`` () = member _.GetVersion() = 1 member _.GetLabel() = "job1" } - let job (input: string) = node { + let job (input: string) = async { let ex = DummyException($"job {input} error") - DiagnosticsThreadStatics.DiagnosticsLogger.ErrorR(ex) + DiagnosticsAsyncState.DiagnosticsLogger.ErrorR(ex) return Ok input } @@ -526,8 +526,8 @@ let ``Preserve thread static diagnostics already completed job`` () = 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 @@ -547,10 +547,10 @@ let ``We get diagnostics from the job that failed`` () = member _.GetVersion() = 1 member _.GetLabel() = "job1" } - let job (input: int) = node { + let job (input: int) = async { let ex = DummyException($"job {input} error") - do! Async.Sleep 100 |> NodeCode.AwaitAsync - DiagnosticsThreadStatics.DiagnosticsLogger.Error(ex) + do! Async.Sleep 100 + DiagnosticsAsyncState.DiagnosticsLogger.Error(ex) return 5 } @@ -562,7 +562,7 @@ let ``We get diagnostics from the job that failed`` () = use _ = new CompilationGlobalsScope(diagnosticsLogger, BuildPhase.Optimize) try - let! _ = cache.Get(key, job i ) |> Async.AwaitNodeCode + let! _ = cache.Get(key, job i ) () with _ -> () diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index cbc8e7690fe..fde34642ff9 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -626,7 +626,7 @@ let fuzzingTest seed (project: SyntheticProject) = task { () with | e -> - let _log = log.Values |> Seq.collect id |> Seq.sortBy p13 |> Seq.toArray + // let _log = log.Values |> Seq.collect id |> Seq.sortBy p13 |> Seq.toArray failwith $"Seed: {seed}\nException: %A{e}" } let log = log.Values |> Seq.collect id |> Seq.sortBy p13 |> Seq.toArray diff --git a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index 556a9b5bc42..55cfc166f43 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -3,6 +3,7 @@ namespace FSharp.Compiler.UnitTests open System open System.Threading +open System.Threading.Tasks open System.Runtime.CompilerServices open Xunit open FSharp.Test @@ -16,14 +17,14 @@ module BuildGraphTests = [] let private createNode () = let o = obj () - GraphNode(node { + GraphNode(async { Assert.shouldBeTrue (o <> null) return 1 }), WeakReference(o) [] let ``Intialization of graph node should not have a computed value``() = - let node = GraphNode(node { return 1 }) + let node = GraphNode(async { return 1 }) Assert.shouldBeTrue(node.TryPeekValue().IsNone) Assert.shouldBeFalse(node.HasValue) @@ -33,23 +34,23 @@ module BuildGraphTests = let resetEventInAsync = new ManualResetEvent(false) let graphNode = - GraphNode(node { + GraphNode(async { resetEventInAsync.Set() |> ignore - let! _ = NodeCode.AwaitWaitHandle_ForTesting(resetEvent) + let! _ = Async.AwaitWaitHandle(resetEvent) return 1 }) let task1 = - node { + async { let! _ = graphNode.GetOrComputeValue() () - } |> NodeCode.StartAsTask_ForTesting + } |> Async.StartAsTask_ForTesting let task2 = - node { + async { let! _ = graphNode.GetOrComputeValue() () - } |> NodeCode.StartAsTask_ForTesting + } |> Async.StartAsTask_ForTesting resetEventInAsync.WaitOne() |> ignore resetEvent.Set() |> ignore @@ -66,12 +67,12 @@ module BuildGraphTests = let mutable computationCount = 0 let graphNode = - GraphNode(node { + GraphNode(async { computationCount <- computationCount + 1 return 1 }) - let work = Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() |> Async.AwaitNodeCode)) + let work = Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() )) Async.RunImmediate(work) |> ignore @@ -82,9 +83,9 @@ module BuildGraphTests = let ``Many requests to get a value asynchronously should get the correct value``() = let requests = 10000 - let graphNode = GraphNode(node { return 1 }) + let graphNode = GraphNode(async { return 1 }) - let work = Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() |> Async.AwaitNodeCode)) + let work = Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() )) let result = Async.RunImmediate(work) @@ -101,7 +102,7 @@ module BuildGraphTests = Assert.shouldBeTrue weak.IsAlive - NodeCode.RunImmediateWithoutCancellation(graphNode.GetOrComputeValue()) + Async.RunImmediateWithoutCancellation(graphNode.GetOrComputeValue()) |> ignore GC.Collect(2, GCCollectionMode.Forced, true) @@ -118,7 +119,7 @@ module BuildGraphTests = Assert.shouldBeTrue weak.IsAlive - Async.RunImmediate(Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() |> Async.AwaitNodeCode))) + Async.RunImmediate(Async.Parallel(Array.init requests (fun _ -> graphNode.GetOrComputeValue() ))) |> ignore GC.Collect(2, GCCollectionMode.Forced, true) @@ -128,59 +129,45 @@ module BuildGraphTests = [] let ``A request can cancel``() = let graphNode = - GraphNode(node { + GraphNode(async { return 1 }) use cts = new CancellationTokenSource() - let work = - node { + let work(): Task = + Async.StartAsTask( + async { cts.Cancel() return! graphNode.GetOrComputeValue() - } - - let ex = - try - NodeCode.RunImmediate(work, ct = cts.Token) - |> ignore - failwith "Should have canceled" - with - | :? OperationCanceledException as ex -> - ex + }, cancellationToken = cts.Token) - Assert.shouldBeTrue(ex <> null) + Assert.ThrowsAnyAsync(work).Wait() [] let ``A request can cancel 2``() = let resetEvent = new ManualResetEvent(false) let graphNode = - GraphNode(node { - let! _ = NodeCode.AwaitWaitHandle_ForTesting(resetEvent) - return 1 + GraphNode(async { + let! _ = Async.AwaitWaitHandle(resetEvent) + failwith "Should have canceled" }) use cts = new CancellationTokenSource() let task = - node { + async { cts.Cancel() resetEvent.Set() |> ignore } - |> NodeCode.StartAsTask_ForTesting + |> Async.StartAsTask_ForTesting - let ex = - try - NodeCode.RunImmediate(graphNode.GetOrComputeValue(), ct = cts.Token) - |> ignore - failwith "Should have canceled" - with - | :? OperationCanceledException as ex -> - ex + Assert.ThrowsAnyAsync(fun () -> + Async.StartImmediateAsTask(graphNode.GetOrComputeValue(), cancellationToken = cts.Token) + ) |> ignore - Assert.shouldBeTrue(ex <> null) - try task.Wait(1000) |> ignore with | :? TimeoutException -> reraise() | _ -> () + if task.Wait(1000) |> not then raise (TimeoutException()) [] let ``Many requests to get a value asynchronously might evaluate the computation more than once even when some requests get canceled``() = @@ -190,9 +177,9 @@ module BuildGraphTests = let mutable computationCount = 0 let graphNode = - GraphNode(node { + GraphNode(async { computationCountBeforeSleep <- computationCountBeforeSleep + 1 - let! _ = NodeCode.AwaitWaitHandle_ForTesting(resetEvent) + let! _ = Async.AwaitWaitHandle(resetEvent) computationCount <- computationCount + 1 return 1 }) @@ -200,7 +187,7 @@ module BuildGraphTests = use cts = new CancellationTokenSource() let work = - node { + async { let! _ = graphNode.GetOrComputeValue() () } @@ -209,15 +196,15 @@ module BuildGraphTests = for i = 0 to requests - 1 do if i % 10 = 0 then - NodeCode.StartAsTask_ForTesting(work, ct = cts.Token) + Async.StartAsTask_ForTesting(work, ct = cts.Token) |> tasks.Add else - NodeCode.StartAsTask_ForTesting(work) + Async.StartAsTask_ForTesting(work) |> tasks.Add cts.Cancel() resetEvent.Set() |> ignore - NodeCode.RunImmediateWithoutCancellation(work) + Async.RunImmediateWithoutCancellation(work) |> ignore Assert.shouldBeTrue cts.IsCancellationRequested @@ -242,16 +229,16 @@ module BuildGraphTests = 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 job phase _ = async { + do! random 10 |> Async.Sleep + Assert.Equal(phase, DiagnosticsAsyncState.BuildPhase) } let work (phase: BuildPhase) = - node { + async { use _ = new CompilationGlobalsScope(DiscardErrorsLogger, phase) - let! _ = Seq.init 8 (job phase) |> NodeCode.Parallel - Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) + let! _ = Seq.init 8 (job phase) |> Async.Parallel + Assert.Equal(phase, DiagnosticsAsyncState.BuildPhase) } let phases = [| @@ -270,6 +257,6 @@ module BuildGraphTests = let pickRandomPhase _ = phases[random phases.Length] Seq.init 100 pickRandomPhase - |> Seq.map (work >> Async.AwaitNodeCode) + |> Seq.map work |> Async.Parallel |> Async.RunSynchronously diff --git a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs index 9f94b7b1c06..0cb607603e7 100644 --- a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs +++ b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs @@ -64,7 +64,7 @@ type public HashIfExpression() = let startPos = Position.Empty let args = mkLexargs (defines, indentationSyntaxStatus, resourceManager, [], diagnosticsLogger, PathMap.empty, applyLineDirectives) - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger + DiagnosticsAsyncState.DiagnosticsLogger <- diagnosticsLogger let parser (s : string) = let lexbuf = LexBuffer.FromChars (true, LanguageVersion.Default, None, s.ToCharArray ()) @@ -76,12 +76,11 @@ type public HashIfExpression() = errors, warnings, parser - do // Setup - DiagnosticsThreadStatics.BuildPhase <- BuildPhase.Compile + // Setup + let globalScope = new CompilationGlobalsScope(DiagnosticsAsyncState.DiagnosticsLogger, BuildPhase.Compile) + interface IDisposable with // Teardown - member _.Dispose() = - DiagnosticsThreadStatics.BuildPhase <- BuildPhase.DefaultPhase - DiagnosticsThreadStatics.DiagnosticsLogger <- DiagnosticsThreadStatics.DiagnosticsLogger + member _.Dispose() = (globalScope :> IDisposable).Dispose() [] member _.PositiveParserTestCases()= diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 2157d8fb7fe..000b700258d 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -27,6 +27,7 @@ open System.Xml open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.CodeAnalysis.ProjectSnapshot open FSharp.Compiler.Diagnostics +open FSharp.Compiler.BuildGraph open FSharp.Compiler.Text open Xunit @@ -797,7 +798,7 @@ module ProjectOperations = let! projects = p.DependsOn |> Seq.map (absorbAutoGeneratedSignatures checker) - |> Async.Sequential + |> Async.SequentialImmediate return { p with SourceFiles = files diff --git a/tests/service/ExprTests.fs b/tests/service/ExprTests.fs index 1371c24f231..4ab3108b454 100644 --- a/tests/service/ExprTests.fs +++ b/tests/service/ExprTests.fs @@ -18,6 +18,7 @@ open System.Diagnostics open System.Threading open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Diagnostics +open FSharp.Compiler.DiagnosticsLogger open FSharp.Compiler.IO open FSharp.Compiler.Service.Tests.Common open FSharp.Compiler.Symbols @@ -736,6 +737,10 @@ let ignoreTestIfStackOverflowExpected () = [] [] let ``Test Unoptimized Declarations Project1`` useTransparentCompiler = + + // TODO: Figure out why Transparent Compiler pushes diagnostics to the outer scope here. + if useTransparentCompiler then DiagnosticsAsyncState.DiagnosticsLogger <- DiscardErrorsLogger + let cleanup, options = Project1.createOptionsWithArgs [ "--langversion:preview" ] use _holder = cleanup let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) @@ -877,6 +882,10 @@ let ``Test Unoptimized Declarations Project1`` useTransparentCompiler = [] [] let ``Test Optimized Declarations Project1`` useTransparentCompiler = + + // TODO: Figure out why Transparent Compiler pushes diagnostics to the outer scope here. + if useTransparentCompiler then DiagnosticsAsyncState.DiagnosticsLogger <- DiscardErrorsLogger + let cleanup, options = Project1.createOptionsWithArgs [ "--langversion:preview" ] use _holder = cleanup let exprChecker = FSharpChecker.Create(keepAssemblyContents=true, useTransparentCompiler=useTransparentCompiler) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 154517ed6a1..c729ad1342f 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -317,15 +317,15 @@ module private CheckerExtensions = snapshotCache.Get( key, - node { - let! ct = NodeCode.CancellationToken + async { + let! ct = Async.CancellationToken return! createProjectSnapshot snapshotAccumulatorOpt project options ct - |> NodeCode.AwaitTask + |> Async.AwaitTask + |> Async.CompilationScope } ) - |> Async.AwaitNodeCode let getProjectSnapshotForDocument (document: Document, options: FSharpProjectOptions) = getOrCreateSnapshotForProject document.Project (Some options) None