diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 0cd4f064cb4..4b1ccda719e 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -22,4 +22,5 @@ * Minor compiler perf improvements. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130)) * Improve error of Active Pattern case Argument Count Not Match ([PR #16846](https://github.com/dotnet/fsharp/pull/16846)) -* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822)) +* AsyncLocal diagnostics context. ([PR #16779](https://github.com/dotnet/fsharp/pull/16779)) +* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822)) \ No newline at end of file diff --git a/docs/release-notes/.VisualStudio/17.11.md b/docs/release-notes/.VisualStudio/17.11.md index faf48e2e377..d02f192462e 100644 --- a/docs/release-notes/.VisualStudio/17.11.md +++ b/docs/release-notes/.VisualStudio/17.11.md @@ -1,3 +1,7 @@ ### Fixed * Make tooltips work in file with no solution. ([PR #17054](https://github.com/dotnet/fsharp/pull/17054)) + +### Changed + +* Use AsyncLocal diagnostics context. ([PR #16779])(https://github.com/dotnet/fsharp/pull/16779)) diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index 54a69b05f8c..1586a01bbce 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 f5360fbb972..048a4209e2e 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 15fc99eccfd..76573599919 100644 --- a/src/Compiler/Driver/CompilerImports.fs +++ b/src/Compiler/Driver/CompilerImports.fs @@ -2159,14 +2159,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 @@ -2229,21 +2229,23 @@ 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 + | ParallelReferenceResolution.On -> MultipleDiagnosticsLoggers.Parallel + | ParallelReferenceResolution.Off -> MultipleDiagnosticsLoggers.Sequential let! results = nms |> List.map (fun nm -> - node { + async { try + use _ = new CompilationGlobalsScope() return! tcImports.TryRegisterAndPrepareToImportReferencedDll(ctok, nm) with e -> errorR (Error(FSComp.SR.buildProblemReadingAssembly (nm.resolvedPath, e.Message), nm.originalReference.Range)) @@ -2283,7 +2285,7 @@ and [] TcImports ReportWarnings warns tcImports.RegisterAndImportReferencedAssemblies(ctok, res) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediate |> ignore true @@ -2377,7 +2379,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 @@ -2454,7 +2456,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 @@ -2548,7 +2550,7 @@ and [] TcImports dependencyProvider ) = - node { + async { let ctok = CompilationThreadToken() let tcConfig = tcConfigP.Get ctok @@ -2566,7 +2568,7 @@ and [] TcImports } static member BuildTcImports(tcConfigP: TcConfigProvider, dependencyProvider) = - node { + async { let ctok = CompilationThreadToken() let tcConfig = tcConfigP.Get ctok @@ -2598,7 +2600,7 @@ let RequireReferences (ctok, tcImports: TcImports, tcEnv, thisAssemblyName, reso let ccuinfos = tcImports.RegisterAndImportReferencedAssemblies(ctok, resolutions) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediate 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/fsc.fs b/src/Compiler/Driver/fsc.fs index 190dbfcd806..26be4051cbd 100644 --- a/src/Compiler/Driver/fsc.fs +++ b/src/Compiler/Driver/fsc.fs @@ -615,7 +615,7 @@ let main1 // Import basic assemblies let tcGlobals, frameworkTcImports = TcImports.BuildFrameworkTcImports(foundationalTcConfigP, sysRes, otherRes) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediate let ilSourceDocs = [ @@ -664,7 +664,7 @@ let main1 let tcImports = TcImports.BuildNonFrameworkTcImports(tcConfigP, frameworkTcImports, otherRes, knownUnresolved, dependencyProvider) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediate // 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 1adcf5f8b89..49c08e5320a 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -51,11 +51,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 @@ -286,7 +286,13 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T key.Key, key.Version, key.Label, - (Running(TaskCompletionSource(), cts, computation, DateTime.Now, ResizeArray())) + (Running( + TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), + cts, + computation, + DateTime.Now, + ResizeArray() + )) ) otherVersions @@ -314,7 +320,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let processStateUpdate post (key: KeyData<_, _>, action: StateUpdate<_>) = task { - do! Task.Delay 0 + do! Task.Yield() do! lock.Do(fun () -> @@ -359,7 +365,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger try - let! result = computation |> Async.AwaitNodeCode + let! result = computation post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return () finally @@ -482,14 +488,14 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Version = key.GetVersion() } - node { - let! ct = NodeCode.CancellationToken + async { + let! ct = Async.CancellationToken let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger match! processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger - |> NodeCode.AwaitTask + |> Async.AwaitTask with | New internalCt -> @@ -507,7 +513,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T log (Started, key) try - let! result = computation |> Async.AwaitNodeCode + let! result = computation post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) return result finally @@ -515,7 +521,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T }, 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 @@ -531,7 +537,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 ea7ede2ebb8..88288ea4fc8 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fsi +++ b/src/Compiler/Facilities/AsyncMemoize.fsi @@ -66,9 +66,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 TryGet: key: 'TKey * predicate: ('TVersion -> bool) -> 'TValue option diff --git a/src/Compiler/Facilities/BuildGraph.fs b/src/Compiler/Facilities/BuildGraph.fs index cc521a07368..e928498c657 100644 --- a/src/Compiler/Facilities/BuildGraph.fs +++ b/src/Compiler/Facilities/BuildGraph.fs @@ -5,217 +5,16 @@ 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> +type Async with -let wrapThreadStaticInfo computation = - async { - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase - - try - return! computation - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - } - -let unwrapNode (Node(computation)) = computation - -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() - -[] -type NodeCode private () = - - static let cancellationToken = Node(wrapThreadStaticInfo Async.CancellationToken) - - static member RunImmediate(computation: NodeCode<'T>, ct: CancellationToken) = - let diagnosticsLogger = DiagnosticsThreadStatics.DiagnosticsLogger - let phase = DiagnosticsThreadStatics.BuildPhase - - try + static member FlattenException(computation: Async<'T>) = + async { try - let work = - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger - DiagnosticsThreadStatics.BuildPhase <- phase - return! computation |> Async.AwaitNodeCode - } - - 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 AwaitTask(task: Task) = - Node(wrapThreadStaticInfo (Async.AwaitTask task)) - - 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 { - let results = ResizeArray() - - for computation in computations do - let! res = computation - results.Add(res) - - return results.ToArray() - } - - static member Parallel(computations: NodeCode<'T> seq) = - node { - let concurrentLogging = new CaptureDiagnosticsConcurrently() - let phase = DiagnosticsThreadStatics.BuildPhase - // Why does it return just IDisposable? - use _ = concurrentLogging - - let injectLogger i computation = - let logger = concurrentLogging.GetLoggerForTask($"NodeCode.Parallel {i}") - - async { - DiagnosticsThreadStatics.DiagnosticsLogger <- logger - DiagnosticsThreadStatics.BuildPhase <- phase - return! unwrapNode computation - } - - return! - computations - |> Seq.mapi injectLogger - |> Async.Parallel - |> wrapThreadStaticInfo - |> Node + return! computation + with :? AggregateException as ex when ex.InnerExceptions.Count = 1 -> + return raise (ex.InnerExceptions[0]) } [] @@ -233,13 +32,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)) @@ -251,11 +50,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'. @@ -274,22 +73,21 @@ 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 Async.StartWithContinuations( async { Thread.CurrentThread.CurrentUICulture <- GraphNode.culture - return! p + return! computation }, (fun res -> cachedResult <- ValueSome res - cachedResultNode <- node.Return res + cachedResultNode <- async.Return res computation <- Unchecked.defaultof<_> tcs.SetResult(res)), (fun ex -> tcs.SetException(ex)), @@ -297,13 +95,14 @@ 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 finally Interlocked.Decrement(&requestCount) |> ignore } + |> Async.FlattenException member _.TryPeekValue() = cachedResult @@ -312,7 +111,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..2b3016bf99b 100644 --- a/src/Compiler/Facilities/BuildGraph.fsi +++ b/src/Compiler/Facilities/BuildGraph.fsi @@ -2,90 +2,6 @@ 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 - - /// 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 - - /// Execute the cancellable computation synchronously using the ambient cancellation token of - /// the NodeCode. - static member FromCancellable: computation: Cancellable<'T> -> NodeCode<'T> - - /// Only used for testing, do not use - static member StartAsTask_ForTesting: computation: NodeCode<'T> * ?ct: CancellationToken -> Task<'T> - - /// Only used for testing, do not use - static member AwaitWaitHandle_ForTesting: waitHandle: WaitHandle -> NodeCode - /// Contains helpers related to the build graph [] module internal GraphNode = @@ -102,7 +18,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 +26,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 3cc3e1b4755..bdec9ba40b4 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fs +++ b/src/Compiler/Facilities/DiagnosticsLogger.fs @@ -15,7 +15,7 @@ open System.Runtime.CompilerServices open System.Runtime.InteropServices open Internal.Utilities.Library open Internal.Utilities.Library.Extras -open System.Collections.Concurrent +open System.Threading.Tasks /// Represents the style being used to format errors [] @@ -392,29 +392,19 @@ type CapturingDiagnosticsLogger(nm, ?eagerFormat) = let errors = diagnostics.ToArray() errors |> Array.iter diagnosticsLogger.DiagnosticSink +let buildPhase = AsyncLocal() +let diagnosticsLogger = AsyncLocal() + /// Type holds thread-static globals for use by the compiler. type internal DiagnosticsThreadStatics = - [] - static val mutable private buildPhase: BuildPhase - - [] - static val mutable private diagnosticsLogger: DiagnosticsLogger - - static member BuildPhaseUnchecked = DiagnosticsThreadStatics.buildPhase static member BuildPhase - with get () = - match box DiagnosticsThreadStatics.buildPhase with - | Null -> BuildPhase.DefaultPhase - | _ -> DiagnosticsThreadStatics.buildPhase - and set v = DiagnosticsThreadStatics.buildPhase <- v + with get () = buildPhase.Value |> ValueOption.defaultValue 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 () = diagnosticsLogger.Value |> ValueOption.defaultValue AssertFalseDiagnosticsLogger + and set v = diagnosticsLogger.Value <- ValueSome v [] module DiagnosticsLoggerExtensions = @@ -445,6 +435,7 @@ module DiagnosticsLoggerExtensions = type DiagnosticsLogger with member x.EmitDiagnostic(exn, severity) = + match exn with | InternalError(s, _) | InternalException(_, s, _) @@ -516,16 +507,15 @@ module DiagnosticsLoggerExtensions = /// NOTE: The change will be undone when the returned "unwind" object disposes let UseBuildPhase (phase: BuildPhase) = - let oldBuildPhase = DiagnosticsThreadStatics.BuildPhaseUnchecked + let oldBuildPhase = buildPhase.Value DiagnosticsThreadStatics.BuildPhase <- phase { new IDisposable with - member x.Dispose() = - DiagnosticsThreadStatics.BuildPhase <- oldBuildPhase + member x.Dispose() = buildPhase.Value <- oldBuildPhase } /// NOTE: The change will be undone when the returned "unwind" object disposes -let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> #DiagnosticsLogger) = +let UseTransformedDiagnosticsLogger (transformer: DiagnosticsLogger -> DiagnosticsLogger) = let oldLogger = DiagnosticsThreadStatics.DiagnosticsLogger DiagnosticsThreadStatics.DiagnosticsLogger <- transformer oldLogger @@ -550,6 +540,8 @@ type CompilationGlobalsScope(diagnosticsLogger: DiagnosticsLogger, buildPhase: B let unwindEL = UseDiagnosticsLogger diagnosticsLogger let unwindBP = UseBuildPhase buildPhase + new() = new CompilationGlobalsScope(DiagnosticsThreadStatics.DiagnosticsLogger, DiagnosticsThreadStatics.BuildPhase) + member _.DiagnosticsLogger = diagnosticsLogger member _.BuildPhase = buildPhase @@ -893,14 +885,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 () } @@ -920,16 +909,58 @@ type StackGuard(maxDepth: int, name: string) = static member GetDepthOption(name: string) = GetEnvInteger ("FSHARP_" + name + "StackGuardDepth") StackGuard.DefaultDepth -type CaptureDiagnosticsConcurrently() = - let target = DiagnosticsThreadStatics.DiagnosticsLogger - let loggers = ResizeArray() +// UseMultipleDiagnosticLoggers in ParseAndCheckProject.fs provides similar functionality. +// We should probably adapt and reuse that code. +module MultipleDiagnosticsLoggers = + let Parallel computations = + let computationsWithLoggers, diagnosticsReady = + [ + for i, computation in computations |> Seq.indexed do + let diagnosticsReady = TaskCompletionSource<_>() + + let logger = CapturingDiagnosticsLogger($"CaptureDiagnosticsConcurrently {i}") + + // Inject capturing logger into the computation. Signal the TaskCompletionSource when done. + let computationsWithLoggers = + async { + SetThreadDiagnosticsLoggerNoUnwind logger + + try + return! computation + finally + diagnosticsReady.SetResult logger + } + + computationsWithLoggers, diagnosticsReady + ] + |> List.unzip + + // Commit diagnostics from computations as soon as it is possible, preserving the order. + let replayDiagnostics = + backgroundTask { + let target = DiagnosticsThreadStatics.DiagnosticsLogger + + for tcs in diagnosticsReady do + let! finishedLogger = tcs.Task + finishedLogger.CommitDelayedDiagnostics target + } - member _.GetLoggerForTask(name) : DiagnosticsLogger = - let logger = CapturingDiagnosticsLogger(name) - loggers.Add logger - logger + async { + try + // We want to restore the current diagnostics context when finished. + use _ = new CompilationGlobalsScope() + return! Async.Parallel computationsWithLoggers + finally + replayDiagnostics.Wait() + } - interface IDisposable with - member _.Dispose() = - for logger in loggers do - logger.CommitDelayedDiagnostics target + let Sequential computations = + async { + let results = ResizeArray() + + for computation in computations do + let! result = computation + results.Add result + + return results.ToArray() + } diff --git a/src/Compiler/Facilities/DiagnosticsLogger.fsi b/src/Compiler/Facilities/DiagnosticsLogger.fsi index 453e7d4c605..69aa0eddec1 100644 --- a/src/Compiler/Facilities/DiagnosticsLogger.fsi +++ b/src/Compiler/Facilities/DiagnosticsLogger.fsi @@ -235,8 +235,6 @@ type DiagnosticsThreadStatics = static member BuildPhase: BuildPhase with get, set - static member BuildPhaseUnchecked: BuildPhase - static member DiagnosticsLogger: DiagnosticsLogger with get, set [] @@ -281,7 +279,7 @@ module DiagnosticsLoggerExtensions = val UseBuildPhase: phase: BuildPhase -> IDisposable /// NOTE: The change will be undone when the returned "unwind" object disposes -val UseTransformedDiagnosticsLogger: transformer: (DiagnosticsLogger -> #DiagnosticsLogger) -> IDisposable +val UseTransformedDiagnosticsLogger: transformer: (DiagnosticsLogger -> DiagnosticsLogger) -> IDisposable val UseDiagnosticsLogger: newLogger: DiagnosticsLogger -> IDisposable @@ -466,15 +464,21 @@ type StackGuard = type CompilationGlobalsScope = new: diagnosticsLogger: DiagnosticsLogger * buildPhase: BuildPhase -> CompilationGlobalsScope + /// When disposed, restores caller's diagnostics logger and build phase. + new: unit -> CompilationGlobalsScope + interface IDisposable member DiagnosticsLogger: DiagnosticsLogger member BuildPhase: BuildPhase -type CaptureDiagnosticsConcurrently = - new: unit -> CaptureDiagnosticsConcurrently +module MultipleDiagnosticsLoggers = - member GetLoggerForTask: string -> DiagnosticsLogger + /// Run computations using Async.Parallel. + /// Captures the diagnostics from each computation and commits them to the caller's logger preserving their order. + /// When done, restores caller's build phase and diagnostics logger. + val Parallel: computations: Async<'T> seq -> Async<'T array> - interface IDisposable + /// Run computations sequentially starting immediately on the current thread. + val Sequential: computations: Async<'T> seq -> Async<'T array> diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index db33a53ee93..d932b95eda1 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -4603,8 +4603,7 @@ type FsiEvaluationSession try let tcConfig = tcConfigP.Get(ctokStartup) - checker.FrameworkImportsCache.Get tcConfig - |> NodeCode.RunImmediateWithoutCancellation + checker.FrameworkImportsCache.Get tcConfig |> Async.RunImmediate with e -> stopProcessingRecovery e range0 failwithf "Error creating evaluation session: %A" e @@ -4618,7 +4617,7 @@ type FsiEvaluationSession unresolvedReferences, fsiOptions.DependencyProvider ) - |> NodeCode.RunImmediateWithoutCancellation + |> Async.RunImmediate 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 3a9b805b676..9772fb56fea 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 * @@ -140,33 +140,33 @@ 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 InvalidateConfiguration: projectSnapshot: FSharpProjectSnapshot * 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 -> @@ -315,7 +315,7 @@ 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 + ")") } @@ -329,8 +329,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() |> Cancellable.toAsync match ilReaderOpt with | Some ilReader -> @@ -356,7 +356,7 @@ type internal BackgroundCompiler let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData return ProjectAssemblyDataResult.Available data } - |> NodeCode.FromCancellable + |> Cancellable.toAsync member x.TryGetLogicalTimeStamp _ = getStamp () |> Some member x.FileName = nm @@ -366,7 +366,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 |] @@ -473,14 +473,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()) @@ -494,16 +494,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 @@ -555,7 +555,7 @@ type internal BackgroundCompiler | _ -> let res = GraphNode( - node { + async { let! res = self.CheckOneFileImplAux( parseResults, @@ -641,7 +641,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" @@ -681,7 +681,7 @@ type internal BackgroundCompiler } member _.GetCachedCheckFileResult(builder: IncrementalBuilder, fileName, sourceText: ISourceText, options) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetCachedCheckFileResult" [| Activity.Tags.fileName, fileName |] @@ -718,9 +718,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 @@ -748,7 +748,7 @@ type internal BackgroundCompiler keepAssemblyContents, suggestNamesForErrors ) - |> NodeCode.FromCancellable + |> Cancellable.toAsync GraphNode.SetPreferredUILang tcConfig.preferredUiLang return (parseResults, checkAnswer, sourceText.GetHashCode() |> int64, tcPrior.ProjectTimeStamp) @@ -767,7 +767,7 @@ type internal BackgroundCompiler creationDiags: FSharpDiagnostic[] ) = - node { + async { match! bc.GetCachedCheckFileResult(builder, fileName, sourceText, options) with | Some(_, results) -> return FSharpCheckFileAnswer.Succeeded results | _ -> @@ -788,7 +788,7 @@ type internal BackgroundCompiler options, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.CheckFileInProjectAllowingStaleCachedResults" @@ -799,7 +799,7 @@ type internal BackgroundCompiler |] let! cachedResults = - node { + async { let! builderOpt, creationDiags = getAnyBuilder (options, userOpName) match builderOpt with @@ -848,7 +848,7 @@ type internal BackgroundCompiler options, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.CheckFileInProject" @@ -896,7 +896,7 @@ type internal BackgroundCompiler options: FSharpProjectOptions, userOpName ) = - node { + async { use _ = Activity.start "BackgroundCompiler.ParseAndCheckFileInProject" @@ -931,7 +931,7 @@ type internal BackgroundCompiler ) GraphNode.SetPreferredUILang tcPrior.TcConfig.preferredUiLang - let! ct = NodeCode.CancellationToken + let! ct = Async.CancellationToken let parseDiagnostics, parseTree, anyErrors = ParseAndCheckFile.parseFile ( @@ -965,7 +965,7 @@ type internal BackgroundCompiler } member _.NotifyFileChanged(fileName, options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.NotifyFileChanged" @@ -984,7 +984,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" @@ -1096,7 +1096,7 @@ type internal BackgroundCompiler canInvalidateProject: bool, userOpName: string ) = - node { + async { use _ = Activity.start "BackgroundCompiler.FindReferencesInFile" @@ -1124,7 +1124,7 @@ type internal BackgroundCompiler } member _.GetSemanticClassificationForFile(fileName: string, options: FSharpProjectOptions, userOpName: string) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetSemanticClassificationForFile" @@ -1191,7 +1191,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) @@ -1264,7 +1264,7 @@ type internal BackgroundCompiler } member _.GetAssemblyData(options, userOpName) = - node { + async { use _ = Activity.start "BackgroundCompiler.GetAssemblyData" @@ -1517,7 +1517,7 @@ type internal BackgroundCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.CheckFileInProject(parseResults, fileName, fileVersion, sourceText, options, userOpName) member _.CheckFileInProjectAllowingStaleCachedResults @@ -1528,7 +1528,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) @@ -1547,7 +1547,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) = @@ -1555,12 +1555,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 @@ -1568,7 +1563,7 @@ type internal BackgroundCompiler projectSnapshot: FSharpProjectSnapshot, _fileName: string, userOpName: string - ) : NodeCode = + ) : Async = self.GetAssemblyData(projectSnapshot.ToOptions(), userOpName) member _.GetBackgroundCheckResultsForFileInProject @@ -1576,7 +1571,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetBackgroundCheckResultsForFileInProject(fileName, options, userOpName) member _.GetBackgroundParseResultsForFileInProject @@ -1584,7 +1579,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetBackgroundParseResultsForFileInProject(fileName, options, userOpName) member _.GetCachedCheckFileResult @@ -1593,7 +1588,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 @@ -1664,7 +1659,7 @@ type internal BackgroundCompiler fileName: string, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.GetSemanticClassificationForFile(fileName, options, userOpName) member _.GetSemanticClassificationForFile @@ -1672,7 +1667,7 @@ type internal BackgroundCompiler fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string - ) : NodeCode = + ) : Async = self.GetSemanticClassificationForFile(fileName, snapshot.ToOptions(), userOpName) member _.InvalidateConfiguration(options: FSharpProjectOptions, userOpName: string) : unit = @@ -1682,7 +1677,7 @@ type internal BackgroundCompiler let options = projectSnapshot.ToOptions() 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 = @@ -1695,7 +1690,7 @@ type internal BackgroundCompiler sourceText: ISourceText, options: FSharpProjectOptions, userOpName: string - ) : NodeCode = + ) : Async = self.ParseAndCheckFileInProject(fileName, fileVersion, sourceText, options, userOpName) member _.ParseAndCheckFileInProject @@ -1703,22 +1698,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 @@ -1736,7 +1731,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 498e2a5102e..9ca174a5686 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 * @@ -119,23 +118,23 @@ 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 InvalidateConfiguration: projectSnapshot: FSharpProjectSnapshot * 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: @@ -144,14 +143,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 6efd49f11c1..8515231b836 100644 --- a/src/Compiler/Service/FSharpProjectSnapshot.fs +++ b/src/Compiler/Service/FSharpProjectSnapshot.fs @@ -590,7 +590,10 @@ and [] FSha // TODO: check if options is a good key here if not (snapshotAccumulator.ContainsKey options) then - let! sourceFiles = options.SourceFiles |> Seq.map (getFileSnapshot options) |> Async.Parallel + let! sourceFiles = + options.SourceFiles + |> Seq.map (getFileSnapshot options) + |> MultipleDiagnosticsLoggers.Parallel let! referencedProjects = options.ReferencedProjects @@ -607,7 +610,7 @@ and [] FSha async.Return <| FSharpReferencedProjectSnapshot.ILModuleReference(outputName, getStamp, getReader)) - |> Async.Sequential + |> MultipleDiagnosticsLoggers.Sequential let referencesOnDisk, otherOptions = options.OtherOptions diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index f59a1e9b6a5..76056026c1d 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 + |> Cancellable.toAsync 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.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()) |> MultipleDiagnosticsLoggers.Sequential let! tcInfos = computedBoundModels - |> Seq.map (fun boundModel -> node { return! boundModel.GetOrComputeTcInfo() }) - |> NodeCode.Sequential + |> Seq.map (fun boundModel -> async { return! boundModel.GetOrComputeTcInfo() }) + |> MultipleDiagnosticsLoggers.Sequential // 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 + |> MultipleDiagnosticsLoggers.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 + |> MultipleDiagnosticsLoggers.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,13 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc let slotOfFile = builder.GetSlotOfFileName fileName let syntaxTree = currentState.slots[slotOfFile].SyntaxTree syntaxTree.ParseNode.GetOrComputeValue() - |> Async.AwaitNodeCode |> 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 +1387,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/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 183f03a22cc..f2af93736e3 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -480,7 +480,7 @@ type internal TransparentCompiler caches.ScriptClosure.Get( key, - node { + async { return ComputeScriptClosureInner fileName @@ -515,7 +515,7 @@ type internal TransparentCompiler caches.FrameworkImports.Get( key, - node { + async { use _ = Activity.start "ComputeFrameworkImports" [] let tcConfigP = TcConfigProvider.Constant tcConfig @@ -539,14 +539,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( @@ -651,7 +651,7 @@ type internal TransparentCompiler then { new IProjectReference with member x.EvaluateRawContents() = - node { + async { Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm) return! @@ -671,8 +671,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() |> Cancellable.toAsync match ilReaderOpt with | Some ilReader -> @@ -698,7 +698,7 @@ type internal TransparentCompiler let data = RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData return ProjectAssemblyDataResult.Available data } - |> NodeCode.FromCancellable + |> Cancellable.toAsync member x.TryGetLogicalTimeStamp _ = getStamp () |> Some member x.FileName = nm @@ -706,7 +706,7 @@ type internal TransparentCompiler ] let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshot) = - node { + async { let useSimpleResolutionSwitch = "--simpleresolution" let commandLineArgs = projectSnapshot.CommandLineOptions let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir @@ -731,8 +731,8 @@ type internal TransparentCompiler match lastScriptFile, projectSnapshot.UseScriptResolutionRules with | Some 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 @@ -750,7 +750,7 @@ type internal TransparentCompiler return (Some closure) } - | _ -> node { return None } + | _ -> async { return None } let sdkDirOverride = match loadClosureOpt with @@ -828,7 +828,7 @@ type internal TransparentCompiler caches.BootstrapInfoStatic.Get( cacheKey, - node { + async { use _ = Activity.start "ComputeBootstrapInfoStatic" @@ -909,7 +909,7 @@ type internal TransparentCompiler ) let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) = - node { + async { let! tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot @@ -985,7 +985,7 @@ type internal TransparentCompiler caches.BootstrapInfo.Get( projectSnapshot.NoFileVersionsKey, - node { + async { use _ = Activity.start "ComputeBootstrapInfo" [| Activity.Tags.project, projectSnapshot.ProjectFileName |> Path.GetFileName |] @@ -994,7 +994,7 @@ type internal TransparentCompiler use _ = new CompilationGlobalsScope(delayedLogger, BuildPhase.Parameter) let! bootstrapInfoOpt = - node { + async { try return! computeBootstrapInfoInner projectSnapshot with exn -> @@ -1027,8 +1027,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( @@ -1041,13 +1041,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 + |> MultipleDiagnosticsLoggers.Parallel return ProjectSnapshotWithSources(projectSnapshot.ProjectCore, sources |> Array.toList) @@ -1071,7 +1071,7 @@ type internal TransparentCompiler caches.ParseFile.Get( key, - node { + async { use _ = Activity.start "ComputeParseFile" @@ -1115,7 +1115,7 @@ type internal TransparentCompiler |> Graph.make let computeDependencyGraph (tcConfig: TcConfig) parsedInputs (processGraph: Graph -> Graph) = - node { + async { let sourceFiles: FileInProject array = parsedInputs |> Seq.toArray @@ -1249,7 +1249,7 @@ type internal TransparentCompiler caches.TcIntermediate.Get( key, - node { + async { let file = projectSnapshot.SourceFiles[index] @@ -1329,7 +1329,6 @@ type internal TransparentCompiler input, true) |> Cancellable.toAsync - |> NodeCode.AwaitAsync //fileChecked.Trigger fileName @@ -1355,9 +1354,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 @@ -1456,11 +1453,11 @@ type internal TransparentCompiler } let parseSourceFiles (projectSnapshot: ProjectSnapshotWithSources) tcConfig = - node { + async { let! parsedInputs = projectSnapshot.SourceFiles |> Seq.map (ComputeParseFile projectSnapshot tcConfig) - |> NodeCode.Parallel + |> MultipleDiagnosticsLoggers.Parallel return ProjectSnapshotBase<_>(projectSnapshot.ProjectCore, parsedInputs |> Array.toList) } @@ -1471,7 +1468,7 @@ type internal TransparentCompiler caches.TcLastFile.Get( projectSnapshot.FileKey fileName, - node { + async { let file = projectSnapshot.SourceFiles |> List.last use _ = @@ -1486,7 +1483,6 @@ type internal TransparentCompiler graph (processGraphNode projectSnapshot bootstrapInfo dependencyFiles false) bootstrapInfo.InitialTcInfo - |> NodeCode.AwaitAsync let lastResult = results |> List.head |> snd @@ -1495,7 +1491,7 @@ type internal TransparentCompiler ) let getParseResult (projectSnapshot: ProjectSnapshot) creationDiags file (tcConfig: TcConfig) = - node { + async { let! parsedFile = ComputeParseFile projectSnapshot tcConfig file let parseDiagnostics = @@ -1528,7 +1524,7 @@ type internal TransparentCompiler let ComputeParseAndCheckFileInProject (fileName: string) (projectSnapshot: ProjectSnapshot) = caches.ParseAndCheckFileInProject.Get( projectSnapshot.FileKeyWithExtraFileSnapshotVersion fileName, - node { + async { use _ = Activity.start "ComputeParseAndCheckFileInProject" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] @@ -1640,7 +1636,7 @@ type internal TransparentCompiler let ComputeParseAndCheckAllFilesInProject (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) = caches.ParseAndCheckAllFilesInProject.Get( projectSnapshot.FullKey, - node { + async { use _ = Activity.start "ComputeParseAndCheckAllFilesInProject" @@ -1655,7 +1651,6 @@ type internal TransparentCompiler graph (processGraphNode projectSnapshot bootstrapInfo dependencyFiles true) bootstrapInfo.InitialTcInfo - |> NodeCode.AwaitAsync } ) @@ -1685,7 +1680,7 @@ type internal TransparentCompiler let ComputeProjectExtras (bootstrapInfo: BootstrapInfo) (projectSnapshot: ProjectSnapshotWithSources) = caches.ProjectExtras.Get( projectSnapshot.SignatureKey, - node { + async { let! results, finalInfo = ComputeParseAndCheckAllFilesInProject bootstrapInfo projectSnapshot @@ -1778,7 +1773,7 @@ type internal TransparentCompiler let ComputeAssemblyData (projectSnapshot: ProjectSnapshot) fileName = caches.AssemblyData.Get( projectSnapshot.SignatureKey, - node { + async { try @@ -1827,7 +1822,7 @@ type internal TransparentCompiler let ComputeParseAndCheckProject (projectSnapshot: ProjectSnapshot) = caches.ParseAndCheckProject.Get( projectSnapshot.FullKey, - node { + async { match! ComputeBootstrapInfo projectSnapshot with | None, creationDiags -> @@ -1899,7 +1894,7 @@ type internal TransparentCompiler ) let tryGetSink (fileName: string) (projectSnapshot: ProjectSnapshot) = - node { + async { match! ComputeBootstrapInfo projectSnapshot with | None, _ -> return None | Some bootstrapInfo, _creationDiags -> @@ -1914,7 +1909,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 |] @@ -1944,7 +1939,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 |] @@ -1979,11 +1974,12 @@ type internal TransparentCompiler ) member _.ParseFile(fileName, projectSnapshot: ProjectSnapshot, _userOpName) = - node { + async { //use _ = // Activity.start "ParseFile" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] // TODO: might need to deal with exceptions here: + use _ = new CompilationGlobalsScope(DiscardErrorsLogger, BuildPhase.Parse) let! tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot let tcConfig = TcConfig.Create(tcConfigB, validate = true) @@ -2063,8 +2059,7 @@ type internal TransparentCompiler |> Md5Hasher.toString } - caches.ParseFileWithoutProject.Get(cacheKey, NodeCode.AwaitAsync parseFileAsync) - |> Async.AwaitNodeCode + caches.ParseFileWithoutProject.Get(cacheKey, parseFileAsync) member _.ParseAndCheckFileInProject(fileName: string, projectSnapshot: ProjectSnapshot, userOpName: string) = ignore userOpName @@ -2073,7 +2068,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 @@ -2098,11 +2093,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 @@ -2119,11 +2112,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 @@ -2171,13 +2162,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) } @@ -2188,11 +2177,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) } @@ -2202,7 +2189,7 @@ type internal TransparentCompiler projectSnapshot: FSharpProjectSnapshot, fileName, userOpName: string - ) : NodeCode = + ) : Async = this.GetAssemblyData(projectSnapshot.ProjectSnapshot, fileName, userOpName) member this.GetBackgroundCheckResultsForFileInProject @@ -2210,11 +2197,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 @@ -2226,11 +2211,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) } @@ -2241,13 +2224,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) @@ -2379,9 +2360,7 @@ type internal TransparentCompiler optionsStamp // Populate the cache. - let! _ = - caches.ScriptClosure.Get(loadClosureKey, node { return loadClosure }) - |> Async.AwaitNodeCode + let! _ = caches.ScriptClosure.Get(loadClosureKey, async { return loadClosure }) let sourceFiles = loadClosure.SourceFiles @@ -2433,7 +2412,7 @@ type internal TransparentCompiler } member this.GetSemanticClassificationForFile(fileName: string, snapshot: FSharpProjectSnapshot, userOpName: string) = - node { + async { ignore userOpName return! ComputeSemanticClassification(fileName, snapshot.ProjectSnapshot) } @@ -2443,13 +2422,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) } @@ -2463,7 +2440,7 @@ type internal TransparentCompiler this.Caches.Clear(Set.singleton (ProjectIdentifier(projectFileName, outputFileName))) - 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 = @@ -2476,11 +2453,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) } @@ -2488,26 +2463,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 906ba4d698a..be1f5ab64fa 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -161,19 +161,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 093a692f1ec..5a1535b6f2d 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) = @@ -398,7 +396,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. @@ -421,7 +418,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. @@ -437,7 +433,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. @@ -452,25 +447,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 ( @@ -484,7 +475,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 @@ -498,15 +489,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 @@ -516,19 +504,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/illib.fs b/src/Compiler/Utilities/illib.fs index b065018e382..ec797443dd0 100644 --- a/src/Compiler/Utilities/illib.fs +++ b/src/Compiler/Utilities/illib.fs @@ -162,12 +162,17 @@ module internal PervasiveAutoOpens = static member RunImmediate(computation: Async<'T>, ?cancellationToken) = let cancellationToken = defaultArg cancellationToken Async.DefaultCancellationToken + let ts = TaskCompletionSource<'T>() + let task = ts.Task Async.StartWithContinuations(computation, (ts.SetResult), (ts.SetException), (fun _ -> ts.SetCanceled()), cancellationToken) - task.Result + try + task.Result + with :? AggregateException as ex when ex.InnerExceptions.Count = 1 -> + raise (ex.InnerExceptions[0]) [] type DelayInitArrayMap<'T, 'TDictKey, 'TDictValue>(f: unit -> 'T[]) = diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index 7c252019e2d..51631e0c466 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.RunSynchronously 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( memoize.Get'(key, computation jobStarted.Set), cancellationToken = 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( memoize.Get'(key, computation ignore), cancellationToken = cts2.Token) + let _task3 = Async.StartAsTask( memoize.Get'(key, computation ignore), cancellationToken = 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( memoize.Get'(key, computation key), cancellationToken = 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( memoize.Get'(key, computation key), cancellationToken = cts2.Token) + let _task3 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = 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( memoize.Get'(key, computation key), cancellationToken = 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( memoize.Get'(key, computation key), cancellationToken = cts2.Token) + let _task3 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = 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(job, cancellationToken = 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) return Ok input } - let job2 (input: int) = node { + let job2 (input: int) = async { DiagnosticsThreadStatics.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" @@ -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,7 +514,7 @@ 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) 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,9 +547,9 @@ 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 + do! Async.Sleep 100 DiagnosticsThreadStatics.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.UnitTests/BuildGraphTests.fs b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs index d07b23a5e99..e1ba5c1b420 100644 --- a/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs +++ b/tests/FSharp.Compiler.UnitTests/BuildGraphTests.fs @@ -3,27 +3,28 @@ namespace FSharp.Compiler.UnitTests open System open System.Threading +open System.Threading.Tasks open System.Runtime.CompilerServices open Xunit open FSharp.Test -open FSharp.Test.Compiler open FSharp.Compiler.BuildGraph open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library +open FSharp.Compiler.Diagnostics 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 let task2 = - node { + async { let! _ = graphNode.GetOrComputeValue() () - } |> NodeCode.StartAsTask_ForTesting + } |> Async.StartAsTask 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.RunImmediate(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,21 +129,21 @@ module BuildGraphTests = [] let ``A request can cancel``() = let graphNode = - GraphNode(node { + GraphNode(async { return 1 }) use cts = new CancellationTokenSource() let work = - node { + async { cts.Cancel() return! graphNode.GetOrComputeValue() } let ex = try - NodeCode.RunImmediate(work, ct = cts.Token) + Async.RunImmediate(work, cancellationToken = cts.Token) |> ignore failwith "Should have canceled" with @@ -156,23 +157,23 @@ module BuildGraphTests = let resetEvent = new ManualResetEvent(false) let graphNode = - GraphNode(node { - let! _ = NodeCode.AwaitWaitHandle_ForTesting(resetEvent) + GraphNode(async { + let! _ = Async.AwaitWaitHandle(resetEvent) return 1 }) use cts = new CancellationTokenSource() let task = - node { + async { cts.Cancel() resetEvent.Set() |> ignore } - |> NodeCode.StartAsTask_ForTesting + |> Async.StartAsTask let ex = try - NodeCode.RunImmediate(graphNode.GetOrComputeValue(), ct = cts.Token) + Async.RunImmediate(graphNode.GetOrComputeValue(), cancellationToken = cts.Token) |> ignore failwith "Should have canceled" with @@ -190,9 +191,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 +201,7 @@ module BuildGraphTests = use cts = new CancellationTokenSource() let work = - node { + async { let! _ = graphNode.GetOrComputeValue() () } @@ -209,15 +210,15 @@ module BuildGraphTests = for i = 0 to requests - 1 do if i % 10 = 0 then - NodeCode.StartAsTask_ForTesting(work, ct = cts.Token) + Async.StartAsTask(work, cancellationToken = cts.Token) |> tasks.Add else - NodeCode.StartAsTask_ForTesting(work) + Async.StartAsTask(work) |> tasks.Add cts.Cancel() resetEvent.Set() |> ignore - NodeCode.RunImmediateWithoutCancellation(work) + Async.RunImmediate(work) |> ignore Assert.shouldBeTrue cts.IsCancellationRequested @@ -238,30 +239,30 @@ module BuildGraphTests = type ExampleException(msg) = inherit System.Exception(msg) [] - let internal ``NodeCode preserves DiagnosticsThreadStatics`` () = + let internal ``DiagnosticsThreadStatics preserved in async`` () = let random = let rng = Random() fun n -> rng.Next n - let job phase i = node { - do! random 10 |> Async.Sleep |> NodeCode.AwaitAsync + let job phase i = async { + do! random 10 |> Async.Sleep Assert.Equal(phase, DiagnosticsThreadStatics.BuildPhase) DiagnosticsThreadStatics.DiagnosticsLogger.DebugDisplay() - |> Assert.shouldBe $"DiagnosticsLogger(NodeCode.Parallel {i})" + |> Assert.shouldBe $"DiagnosticsLogger(CaptureDiagnosticsConcurrently {i})" errorR (ExampleException $"job {i}") } let work (phase: BuildPhase) = - node { + async { let n = 8 let logger = CapturingDiagnosticsLogger("test NodeCode") use _ = new CompilationGlobalsScope(logger, phase) - let! _ = Seq.init n (job phase) |> NodeCode.Parallel + let! _ = Seq.init n (job phase) |> MultipleDiagnosticsLoggers.Parallel let diags = logger.Diagnostics |> List.map fst - diags |> List.map _.Phase |> Set |> Assert.shouldBe (Set.singleton phase) + diags |> List.map _.Phase |> List.distinct |> Assert.shouldBe [ phase ] diags |> List.map _.Exception.Message |> Assert.shouldBe (List.init n <| sprintf "job %d") @@ -284,6 +285,245 @@ module BuildGraphTests = let pickRandomPhase _ = phases[random phases.Length] Seq.init 100 pickRandomPhase - |> Seq.map (work >> Async.AwaitNodeCode) + |> Seq.map work |> Async.Parallel |> Async.RunSynchronously + + exception TestException + + type internal SimpleConcurrentLogger(name) = + inherit DiagnosticsLogger(name) + + let mutable errorCount = 0 + + override _.DiagnosticSink(d, s) = + if s = FSharpDiagnosticSeverity.Error then Interlocked.Increment(&errorCount) |> ignore + + override this.ErrorCount = errorCount + + let loggerShouldBe logger = + DiagnosticsThreadStatics.DiagnosticsLogger |> Assert.shouldBe logger + + let errorCountShouldBe ec = + DiagnosticsThreadStatics.DiagnosticsLogger.ErrorCount |> Assert.shouldBe ec + + [] + let ``AsyncLocal diagnostics context works with TPL`` () = + + let task1 () = + List.init 20 (sprintf "ListParallel logger %d") + |> Extras.ListParallel.map (fun name -> + let logger = CapturingDiagnosticsLogger(name) + use _ = UseDiagnosticsLogger logger + for _ in 1 .. 10 do + errorR TestException + Thread.Sleep 5 + errorCountShouldBe 10 + loggerShouldBe logger ) + |> ignore + + let task2 () = + let commonLogger = SimpleConcurrentLogger "ListParallel concurrent logger" + use _ = UseDiagnosticsLogger commonLogger + + [1 .. 20] + |> Extras.ListParallel.map (fun _ -> + for _ in 1 .. 10 do + errorR TestException + Thread.Sleep 5 + loggerShouldBe commonLogger ) + |> ignore + errorCountShouldBe 200 + loggerShouldBe commonLogger + + Tasks.Parallel.Invoke(task1, task2) + + + type internal DiagnosticsLoggerWithCallback(callback) = + inherit CapturingDiagnosticsLogger("test") + override _.DiagnosticSink(e, s) = + base.DiagnosticSink(e, s) + callback e.Exception.Message |> ignore + + [] + let ``MultipleDiagnosticsLoggers capture diagnostics in correct order`` () = + + let mutable prevError = "000." + + let errorCommitted msg = + // errors come in correct order + Assert.shouldBeTrue (msg > prevError) + prevError <- msg + + let work i = async { + for c in 'A' .. 'F' do + do! Async.SwitchToThreadPool() + errorR (ExampleException $"%03d{i}{c}") + } + + let tasks = Seq.init 100 work + + let logger = DiagnosticsLoggerWithCallback errorCommitted + use _ = UseDiagnosticsLogger logger + tasks |> Seq.take 50 |> MultipleDiagnosticsLoggers.Parallel |> Async.Ignore |> Async.RunImmediate + + // all errors committed + errorCountShouldBe 300 + + tasks |> Seq.skip 50 |> MultipleDiagnosticsLoggers.Sequential |> Async.Ignore |> Async.RunImmediate + + errorCountShouldBe 600 + + + + + [] + let ``AsyncLocal diagnostics context flows correctly`` () = + + let work logger = async { + SetThreadDiagnosticsLoggerNoUnwind logger + + errorR TestException + + loggerShouldBe logger + errorCountShouldBe 1 + + do! Async.SwitchToNewThread() + + errorR TestException + + loggerShouldBe logger + errorCountShouldBe 2 + + do! Async.SwitchToThreadPool() + + errorR TestException + + loggerShouldBe logger + errorCountShouldBe 3 + + let workInner = async { + do! async.Zero() + errorR TestException + loggerShouldBe logger + } + + let! child = workInner |> Async.StartChild + let! childTask = workInner |> Async.StartChildAsTask + + do! child + do! childTask |> Async.AwaitTask + errorCountShouldBe 5 + } + + let init n = + let name = $"AsyncLocal test {n}" + let logger = SimpleConcurrentLogger name + work logger + + Seq.init 10 init |> Async.Parallel |> Async.RunSynchronously |> ignore + + let logger = SimpleConcurrentLogger "main" + use _ = UseDiagnosticsLogger logger + + errorCountShouldBe 0 + + let btask = backgroundTask { + errorR TestException + do! Task.Yield() + errorR TestException + loggerShouldBe logger + } + + let noErrorsTask = backgroundTask { + SetThreadDiagnosticsLoggerNoUnwind DiscardErrorsLogger + errorR TestException + do! Task.Yield() + errorR TestException + loggerShouldBe DiscardErrorsLogger + } + + let task = task { + errorR TestException + do! Task.Yield() + errorR TestException + loggerShouldBe logger + } + + // A thread with inner logger. + let thread = Thread(ThreadStart(fun () -> + use _ = UseDiagnosticsLogger (CapturingDiagnosticsLogger("Thread logger")) + errorR TestException + errorR TestException + errorCountShouldBe 2 + )) + thread.Start() + thread.Join() + + loggerShouldBe logger + + // Ambient logger flows into this thread. + let thread = Thread(ThreadStart(fun () -> + errorR TestException + errorR TestException + )) + thread.Start() + thread.Join() + + Task.WaitAll(noErrorsTask, btask, task) + + Seq.init 11 (fun _ -> async { errorR TestException; loggerShouldBe logger } ) |> Async.Parallel |> Async.RunSynchronously |> ignore + + loggerShouldBe logger + errorCountShouldBe 17 + + async { + + // After Async.Parallel the continuation runs in the context of the last computation that finished. + do! + [ async { + SetThreadDiagnosticsLoggerNoUnwind DiscardErrorsLogger } ] + |> Async.Parallel + |> Async.Ignore + loggerShouldBe DiscardErrorsLogger + + SetThreadDiagnosticsLoggerNoUnwind logger + + // On the other hand, MultipleDiagnosticsLoggers.Parallel restores caller's context. + do! + [ async { + SetThreadDiagnosticsLoggerNoUnwind DiscardErrorsLogger } ] + |> MultipleDiagnosticsLoggers.Parallel + |> Async.Ignore + loggerShouldBe logger + } + |> Async.RunImmediate + + // Synchronus code will affect current context: + + // This is synchrouous, caller's context is affected + async { + SetThreadDiagnosticsLoggerNoUnwind DiscardErrorsLogger + do! Async.SwitchToNewThread() + loggerShouldBe DiscardErrorsLogger + } + |> Async.RunImmediate + loggerShouldBe DiscardErrorsLogger + + SetThreadDiagnosticsLoggerNoUnwind logger + // This runs in async continuation, so the context is forked. + async { + do! Async.Sleep 0 + SetThreadDiagnosticsLoggerNoUnwind DiscardErrorsLogger + do! Async.SwitchToNewThread() + loggerShouldBe DiscardErrorsLogger + } + |> Async.RunImmediate + loggerShouldBe logger + + + + + + + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 154517ed6a1..6c3b04f1844 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -317,15 +317,14 @@ 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.AwaitNodeCode let getProjectSnapshotForDocument (document: Document, options: FSharpProjectOptions) = getOrCreateSnapshotForProject document.Project (Some options) None