From e1acf3969f763db80b99c563d02c5476f79287c7 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:06:33 +0100 Subject: [PATCH 01/14] refactor AsyncMemoize, introduce AsyncLazy --- src/Compiler/Facilities/AsyncMemoize.fs | 661 +++++------------- src/Compiler/Facilities/AsyncMemoize.fsi | 15 +- src/Compiler/Service/TransparentCompiler.fs | 1 - .../CompilerService/AsyncLock.fs | 26 - .../CompilerService/AsyncMemoize.fs | 300 ++++---- .../FSharp.Compiler.ComponentTests.fsproj | 1 - 6 files changed, 324 insertions(+), 680 deletions(-) delete mode 100644 tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index af1a52e5e5d..703c956a784 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -1,19 +1,85 @@ namespace Internal.Utilities.Collections open System -open System.Collections.Generic open System.Diagnostics open System.IO open System.Threading open System.Threading.Tasks -open FSharp.Compiler -open FSharp.Compiler.BuildGraph -open FSharp.Compiler.Diagnostics open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library open System.Runtime.CompilerServices +type AsyncLazyState<'t> = + | Initial of Async<'t> + | Created of Task<'t> * CancellationTokenSource * int + +/// Represents a computation that will execute only once but can be requested by multiple clients. +/// It keeps track of the number of requests. When all clients cancel their requests, the underlying computation will also cancel and can be restarted. +/// If cancelUnawaited is set to false, the computation will run to completion even when all requests are canceled. +type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = + + let cancelUnawaited = defaultArg cancelUnawaited true + + let stateUpdateSync = obj() + let mutable state = Initial computation + + let cancelIfUnawaited () = + match state with + | Created(work, cts, 0) when not work.IsCompleted -> + cts.Cancel() + state <- Initial computation + | _ -> () + + let afterRequest () = + match state with + | Created(work, cts, count) -> + state <- Created(work, cts, count - 1) + if cancelUnawaited then cancelIfUnawaited () + | _ -> () + + let request () = + match state with + | Initial computation -> + let cts = new CancellationTokenSource() + let work = Async.StartAsTask(computation, cancellationToken = cts.Token) + state <- Created (work, cts, 1) + work, true + | Created (work, cts, count) -> + state <- Created (work, cts, count + 1) + work, (count = 0) + + member _.Request = + async { + let work, firstRequest = lock stateUpdateSync request + try + let! ct = Async.CancellationToken + let options = if firstRequest then TaskContinuationOptions.ExecuteSynchronously else TaskContinuationOptions.None + try + return! + // Using ContinueWith with a CancellationToken allows detaching from the running 'work' task. + // This ensures the lazy 'work' and its awaiting requests can be independently managed + // by separate CancellationTokenSources, enabling individual cancellation. + // Essentially, if this async computation is canceled, it won't wait for the 'work' to complete + // but will immediately proceed to the finally block. + work.ContinueWith((fun (t: Task<_>) -> t.Result), ct, options, TaskScheduler.Current) + |> Async.AwaitTask + // Cancellation check before entering the `with` ensures TaskCanceledEXception coming from the ContinueWith task will never be raised here. + // The cancellation continuation will always be called in case of cancellation. + with ex -> return raise ex + finally + lock stateUpdateSync afterRequest + } + + + member _.CancelIfUnawaited() = lock stateUpdateSync cancelIfUnawaited + + member _.Task = match state with Created(t, _, _) -> Some t | _ -> None + member this.Result = + this.Task + |> Option.filter (fun t -> t.Status = TaskStatus.RanToCompletion) + |> Option.map _.Result + [] module internal Utils = @@ -29,52 +95,6 @@ module internal Utils = $"{dir}{Path.GetFileName path}" - let replayDiagnostics (logger: DiagnosticsLogger) = Seq.iter ((<|) logger.DiagnosticSink) - - [] - let (|TaskCancelled|_|) (ex: exn) = - match ex with - | :? System.Threading.Tasks.TaskCanceledException as tce -> ValueSome tce - //| :? System.AggregateException as ae -> - // if ae.InnerExceptions |> Seq.forall (fun e -> e :? System.Threading.Tasks.TaskCanceledException) then - // ae.InnerExceptions |> Seq.tryHead |> Option.map (fun e -> e :?> System.Threading.Tasks.TaskCanceledException) - // else - // None - | _ -> ValueNone - -type internal StateUpdate<'TValue> = - | CancelRequest - | OriginatorCanceled - | JobCompleted of 'TValue * (PhasedDiagnostic * FSharpDiagnosticSeverity) list - | JobFailed of exn * (PhasedDiagnostic * FSharpDiagnosticSeverity) list - -type internal MemoizeReply<'TValue> = - | New of CancellationToken - | Existing of Task<'TValue> - -type internal MemoizeRequest<'TValue> = GetOrCompute of Async<'TValue> * CancellationToken - -[] -type internal Job<'TValue> = - | 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 - - member this.DebuggerDisplay = - match this with - | Running(_, cts, _, ts, _) -> - let cancellation = - if cts.IsCancellationRequested then - " ! Cancellation Requested" - else - "" - - $"Running since {ts.ToShortTimeString()}{cancellation}" - | Completed(value, diags) -> $"Completed {value}" + (if diags.Length > 0 then $" ({diags.Length})" else "") - | Canceled _ -> "Canceled" - | Failed(_, ex) -> $"Failed {ex}" - type internal JobEvent = | Requested | Started @@ -87,6 +107,7 @@ type internal JobEvent = | Strengthened | Failed | Cleared + static member AllEvents = [Requested; Started; Restarted; Finished; Canceled; Evicted; Collected; Weakened; Strengthened; Failed; Cleared] type internal ICacheKey<'TKey, 'TVersion> = abstract member GetKey: unit -> 'TKey @@ -111,40 +132,7 @@ type private KeyData<'TKey, 'TVersion> = Version: 'TVersion } -type internal AsyncLock() = - - let semaphore = new SemaphoreSlim(1, 1) - - member _.Semaphore = semaphore - - member _.Do(f) = - task { - do! semaphore.WaitAsync() - - try - return! f () - finally - semaphore.Release() |> ignore - } - - interface IDisposable with - member _.Dispose() = semaphore.Dispose() - -type internal CachingDiagnosticsLogger(originalLogger: DiagnosticsLogger option) = - inherit DiagnosticsLogger($"CachingDiagnosticsLogger") - - let capturedDiagnostics = ResizeArray() - - override _.ErrorCount = - originalLogger - |> Option.map (fun x -> x.ErrorCount) - |> Option.defaultValue capturedDiagnostics.Count - - override _.DiagnosticSink(diagnostic: PhasedDiagnostic, severity: FSharpDiagnosticSeverity) = - originalLogger |> Option.iter (fun x -> x.DiagnosticSink(diagnostic, severity)) - capturedDiagnostics.Add(diagnostic, severity) - - member _.CapturedDiagnostics = capturedDiagnostics |> Seq.toList +type Job<'t> = AsyncLazy> [] type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality @@ -153,342 +141,39 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T and 'TVersion:not null #endif > - (?keepStrongly, ?keepWeakly, ?name: string, ?cancelDuplicateRunningJobs: bool) = + (?keepStrongly, ?keepWeakly, ?name: string, ?cancelUnawaitedJobs: bool, ?cancelDuplicateRunningJobs: bool) = let name = defaultArg name "N/A" + let cancelUnawaitedJobs = defaultArg cancelUnawaitedJobs true let cancelDuplicateRunningJobs = defaultArg cancelDuplicateRunningJobs false let event = Event<_>() - let mutable errors = 0 + let eventCounts = [for j in JobEvent.AllEvents -> j, ref 0] |> dict let mutable hits = 0 - let mutable started = 0 - let mutable completed = 0 - let mutable canceled = 0 - let mutable restarted = 0 - let mutable failed = 0 - let mutable evicted = 0 - let mutable collected = 0 - let mutable strengthened = 0 - let mutable cleared = 0 - - let mutable updates_in_flight = 0 - - let mutable cancel_ct_registration_original = 0 - let mutable cancel_exception_original = 0 - let mutable cancel_original_processed = 0 - let mutable cancel_ct_registration_subsequent = 0 - let mutable cancel_exception_subsequent = 0 - let mutable cancel_subsequent_processed = 0 - - let failures = ResizeArray() - let mutable avgDurationMs = 0.0 + let mutable duration = 0L + + let keyTuple (keyData: KeyData<_, _>) = keyData.Label, keyData.Key, keyData.Version + + let logK (eventType: JobEvent) key = + Interlocked.Increment(eventCounts[eventType]) |> ignore + event.Trigger(eventType, key) + + let log eventType keyData = logK eventType (keyTuple keyData) let cache = LruCache<'TKey, 'TVersion, Job<'TValue>>( keepStrongly = defaultArg keepStrongly 100, keepWeakly = defaultArg keepWeakly 200, - requiredToKeep = - (function - | Running _ -> true - | Job.Canceled at when at > DateTime.Now.AddMinutes -5.0 -> true - | Job.Failed(at, _) when at > DateTime.Now.AddMinutes -5.0 -> true - | _ -> false), event = (function - | CacheEvent.Evicted -> - (fun k -> - Interlocked.Increment &evicted |> ignore - event.Trigger(JobEvent.Evicted, k)) - | CacheEvent.Collected -> - (fun k -> - Interlocked.Increment &collected |> ignore - event.Trigger(JobEvent.Collected, k)) - | CacheEvent.Weakened -> (fun k -> event.Trigger(JobEvent.Weakened, k)) - | CacheEvent.Strengthened -> - (fun k -> - Interlocked.Increment &strengthened |> ignore - event.Trigger(JobEvent.Strengthened, k)) - | CacheEvent.Cleared -> - (fun k -> - Interlocked.Increment &cleared |> ignore - event.Trigger(JobEvent.Cleared, k))) - ) - - let requestCounts = Dictionary, int>() - let cancellationRegistrations = Dictionary<_, _>() - - let saveRegistration key registration = - cancellationRegistrations[key] <- - match cancellationRegistrations.TryGetValue key with - | true, registrations -> registration :: registrations - | _ -> [ registration ] - - let cancelRegistration key = - match cancellationRegistrations.TryGetValue key with - | true, registrations -> - for r: CancellationTokenRegistration in registrations do - r.Dispose() - - cancellationRegistrations.Remove key |> ignore - | _ -> () - - let incrRequestCount key = - requestCounts[key] <- - if requestCounts.ContainsKey key then - requestCounts[key] + 1 - else - 1 - - let decrRequestCount key = - if requestCounts.ContainsKey key then - requestCounts[key] <- requestCounts[key] - 1 - - let log (eventType, keyData: KeyData<_, _>) = - event.Trigger(eventType, (keyData.Label, keyData.Key, keyData.Version)) - - let lock = new AsyncLock() - - let processRequest post (key: KeyData<_, _>, msg) diagnosticLogger = - - lock.Do(fun () -> - task { - - let cached, otherVersions = cache.GetAll(key.Key, key.Version) - - let result = - match msg, cached with - | GetOrCompute _, Some(Completed(result, diags)) -> - Interlocked.Increment &hits |> ignore - diags |> replayDiagnostics diagnosticLogger - Existing(Task.FromResult result) - | GetOrCompute(_, ct), Some(Running(tcs, _, _, _, loggers)) -> - Interlocked.Increment &hits |> ignore - incrRequestCount key - - ct.Register(fun _ -> - let _name = name - Interlocked.Increment &cancel_ct_registration_subsequent |> ignore - post (key, CancelRequest)) - |> saveRegistration key - - loggers.Add diagnosticLogger - - Existing tcs.Task - - | GetOrCompute(computation, ct), None - | GetOrCompute(computation, ct), Some(Job.Canceled _) - | GetOrCompute(computation, ct), Some(Job.Failed _) -> - Interlocked.Increment &started |> ignore - incrRequestCount key - - ct.Register(fun _ -> - let _name = name - Interlocked.Increment &cancel_ct_registration_original |> ignore - post (key, OriginatorCanceled)) - |> saveRegistration key - - let cts = new CancellationTokenSource() - - cache.Set( - key.Key, - key.Version, - key.Label, - (Running( - TaskCompletionSource<'TValue>(TaskCreationOptions.RunContinuationsAsynchronously), - cts, - computation, - DateTime.Now, - ResizeArray() - )) - ) - - otherVersions - |> Seq.choose (function - | v, Running(_tcs, cts, _, _, _) -> Some(v, cts) - | _ -> None) - |> Seq.iter (fun (_v, cts) -> - use _ = Activity.start $"{name}: Duplicate running job" [| "key", key.Label |] - //System.Diagnostics.Trace.TraceWarning($"{name} Duplicate {key.Label}") - if cancelDuplicateRunningJobs then - //System.Diagnostics.Trace.TraceWarning("Canceling") - cts.Cancel()) - - New cts.Token - - log (Requested, key) - return result - }) - - let internalError key message = - let ex = exn (message) - failures.Add(key, ex) - Interlocked.Increment &errors |> ignore - // raise ex -- Suppose there's no need to raise here - where does it even go? - - let processStateUpdate post (key: KeyData<_, _>, action: StateUpdate<_>) = - lock.Do(fun () -> - task { - - let cached = cache.TryGet(key.Key, key.Version) - - match action, cached with - - | OriginatorCanceled, Some(Running(tcs, cts, computation, _, _)) -> - - Interlocked.Increment &cancel_original_processed |> ignore - - decrRequestCount key - - if requestCounts[key] < 1 then - cancelRegistration key - cts.Cancel() - tcs.TrySetCanceled() |> ignore - // Remember the job in case it completes after cancellation - cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now) - requestCounts.Remove key |> ignore - log (Canceled, key) - Interlocked.Increment &canceled |> ignore - use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |] - () - - else - // We need to restart the computation - Task.Run(fun () -> - Async.StartAsTask( - async { - - let cachingLogger = new CachingDiagnosticsLogger(None) - - try - // TODO: Should unify starting and restarting - log (Restarted, key) - Interlocked.Increment &restarted |> ignore - System.Diagnostics.Trace.TraceInformation $"{name} Restarted {key.Label}" - let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger - DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger - - try - let! result = computation - post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) - return () - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger - with - | TaskCancelled _ -> - Interlocked.Increment &cancel_exception_subsequent |> ignore - post (key, CancelRequest) - () - | ex -> post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics))) - } - ), - cts.Token) - |> ignore - - | CancelRequest, Some(Running(tcs, cts, _c, _, _)) -> - - Interlocked.Increment &cancel_subsequent_processed |> ignore - - decrRequestCount key - - if requestCounts[key] < 1 then - cancelRegistration key - cts.Cancel() - tcs.TrySetCanceled() |> ignore - // Remember the job in case it completes after cancellation - cache.Set(key.Key, key.Version, key.Label, Job.Canceled DateTime.Now) - requestCounts.Remove key |> ignore - log (Canceled, key) - Interlocked.Increment &canceled |> ignore - use _ = Activity.start $"{name}: Canceled job" [| "key", key.Label |] - () - - // Probably in some cases cancellation can be fired off even after we just unregistered it - | CancelRequest, None - | CancelRequest, Some(Completed _) - | CancelRequest, Some(Job.Canceled _) - | CancelRequest, Some(Job.Failed _) - | OriginatorCanceled, None - | OriginatorCanceled, Some(Completed _) - | OriginatorCanceled, Some(Job.Canceled _) - | OriginatorCanceled, Some(Job.Failed _) -> () - - | JobFailed(ex, diags), Some(Running(tcs, _cts, _c, _ts, loggers)) -> - cancelRegistration key - cache.Set(key.Key, key.Version, key.Label, Job.Failed(DateTime.Now, ex)) - requestCounts.Remove key |> ignore - log (Failed, key) - Interlocked.Increment &failed |> ignore - failures.Add(key.Label, ex) - - for logger in loggers do - diags |> replayDiagnostics logger - - tcs.TrySetException ex |> ignore - - | JobCompleted(result, diags), Some(Running(tcs, _cts, _c, started, loggers)) -> - cancelRegistration key - cache.Set(key.Key, key.Version, key.Label, (Completed(result, diags))) - requestCounts.Remove key |> ignore - log (Finished, key) - Interlocked.Increment &completed |> ignore - let duration = float (DateTime.Now - started).Milliseconds - - avgDurationMs <- - if completed < 2 then - duration - else - avgDurationMs + (duration - avgDurationMs) / float completed - - for logger in loggers do - diags |> replayDiagnostics logger - - if tcs.TrySetResult result = false then - internalError key.Label "Invalid state: Completed job already completed" - - // Sometimes job can be canceled but it still manages to complete (or fail) - | JobFailed _, Some(Job.Canceled _) - | JobCompleted _, Some(Job.Canceled _) -> () - - // Job can't be evicted from cache while it's running because then subsequent requesters would be waiting forever - | JobFailed _, None -> internalError key.Label "Invalid state: Running job missing in cache (failed)" - - | JobCompleted _, None -> internalError key.Label "Invalid state: Running job missing in cache (completed)" - - | JobFailed(ex, _diags), Some(Completed(_job, _diags2)) -> - internalError key.Label $"Invalid state: Failed Completed job \n%A{ex}" - - | JobCompleted(_result, _diags), Some(Completed(_job, _diags2)) -> - internalError key.Label "Invalid state: Double-Completed job" - - | JobFailed(ex, _diags), Some(Job.Failed(_, ex2)) -> - internalError key.Label $"Invalid state: Double-Failed job \n%A{ex} \n%A{ex2}" - - | JobCompleted(_result, _diags), Some(Job.Failed(_, ex2)) -> - internalError key.Label $"Invalid state: Completed Failed job \n%A{ex2}" - }) - - let rec post msg = - Interlocked.Increment &updates_in_flight |> ignore - backgroundTask { - do! processStateUpdate post msg - Interlocked.Decrement &updates_in_flight |> ignore - } - |> ignore - - member this.Get'(key, computation) = - - let wrappedKey = - { new ICacheKey<_, _> with - member _.GetKey() = key - member _.GetVersion() = Unchecked.defaultof<_> - member _.GetLabel() = match key.ToString() with | null -> "" | s -> s - } - - this.Get(wrappedKey, computation) + | CacheEvent.Evicted -> logK JobEvent.Evicted + | CacheEvent.Collected -> logK JobEvent.Collected + | CacheEvent.Weakened -> logK JobEvent.Weakened + | CacheEvent.Strengthened -> logK JobEvent.Strengthened + | CacheEvent.Cleared -> logK JobEvent.Cleared)) member _.Get(key: ICacheKey<_, _>, computation) = - let key = { Label = key.GetLabel() @@ -496,66 +181,70 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Version = key.GetVersion() } - async { - let! ct = Async.CancellationToken + let wrappedComputation = + async { + use! _handler = Async.OnCancel (fun () -> log Canceled key) + let sw = Stopwatch.StartNew() + log Started key + let logger = CapturingDiagnosticsLogger "cache" + SetThreadDiagnosticsLoggerNoUnwind logger - let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger + match! Async.Catch computation with - match! - processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger - |> Async.AwaitTask - with - | New internalCt -> + | Choice1Of2 result -> + log Finished key + Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore + return Result.Ok(result, logger) - let linkedCtSource = CancellationTokenSource.CreateLinkedTokenSource(ct, internalCt) - let cachingLogger = new CachingDiagnosticsLogger(Some callerDiagnosticLogger) + | Choice2Of2 ex -> + log Failed key + return Result.Error(ex, logger) + } - try - return! - Async.StartAsTask( - async { - // TODO: Should unify starting and restarting - let currentLogger = DiagnosticsThreadStatics.DiagnosticsLogger - DiagnosticsThreadStatics.DiagnosticsLogger <- cachingLogger - - log (Started, key) - - try - let! result = computation - post (key, (JobCompleted(result, cachingLogger.CapturedDiagnostics))) - return result - finally - DiagnosticsThreadStatics.DiagnosticsLogger <- currentLogger - }, - cancellationToken = linkedCtSource.Token - ) - |> Async.AwaitTask - with - | TaskCancelled ex -> - // TODO: do we need to do anything else here? Presumably it should be done by the registration on - // the cancellation token or before we triggered our own cancellation + let getOrAdd () = + let cached, otherVersions = cache.GetAll(key.Key, key.Version) + + let countHit v = Interlocked.Increment &hits |> ignore; v + let cacheSetNewJob () = + let job = Job(wrappedComputation, cancelUnawaited = cancelUnawaitedJobs) + cache.Set(key.Key, key.Version, key.Label, job) + job + + otherVersions, + + cached + |> Option.map countHit + |> Option.defaultWith cacheSetNewJob - // Let's send this again just in case. It seems sometimes it's not triggered from the registration? + async { + let otherVersions, job = lock cache getOrAdd - Interlocked.Increment &cancel_exception_original |> ignore + log Requested key - post (key, (OriginatorCanceled)) - return raise ex - | ex -> - post (key, (JobFailed(ex, cachingLogger.CapturedDiagnostics))) - return raise ex + if cancelDuplicateRunningJobs && not cancelUnawaitedJobs then + otherVersions |> Seq.map snd |> Seq.iter _.CancelIfUnawaited() - | Existing job -> return! job |> Async.AwaitTask + use _ = new CompilationGlobalsScope() + match! job.Request with + + | Result.Ok(result, logger) -> + logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + return result + + | Result.Error(ex, logger) -> + logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + return raise ex } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = - let versionsAndJobs = cache.GetAll(key) + let versionsAndJobs = + lock cache <| fun () ->cache.GetAll(key) versionsAndJobs |> Seq.tryPick (fun (version, job) -> - match predicate version, job with - | true, Completed(completed, _) -> Some completed + match predicate version, job.Result with + | true, Some(Result.Ok(result, _)) -> Some result | _ -> None) member _.Clear() = cache.Clear() @@ -566,66 +255,50 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member this.OnEvent = this.Event.Add - member _.Count = lock.Do(fun () -> Task.FromResult cache.Count).Result + member this.Count = lock cache <| fun () -> cache.Count - member _.Updating = updates_in_flight > 0 + member _.Updating = false - member _.Locked = lock.Semaphore.CurrentCount < 1 + member this.DebuggerDisplay = - member _.Running = - cache.GetValues() - |> Seq.filter (function - | _, _, Running _ -> true - | _ -> false) - |> Seq.toArray + let (|Running|_|) (job: Job<_>) = job.Task |> Option.filter (_.IsCompleted >> not) + let (|Faulted|_|) (job: Job<_>) = job.Task |> Option.filter _.IsFaulted - member this.DebuggerDisplay = - let locked = if this.Locked then " [LOCKED]" else "" + let status = function + | Running _ -> "Running" + | Faulted _ -> "Faulted" + | _ -> "other" - let valueStats = - cache.GetValues() - |> Seq.countBy (function - | _, _, Running _ -> "Running" - | _, _, Completed _ -> "Completed" - | _, _, Job.Canceled _ -> "Canceled" - | _, _, Job.Failed _ -> "Failed") - |> Map + let cachedJobs = cache.GetValues() |> Seq.map (fun (_,_,job) -> job) + + let valueStats = cachedJobs |> Seq.countBy status |> Map + let getStat key = valueStats.TryFind key |> Option.defaultValue 0 let running = - valueStats.TryFind "Running" - |> Option.map (sprintf " Running: %d ") - |> Option.defaultValue "" + let count = getStat "Running" + if count > 0 then $" Running {count}" else "" - let avgDuration = avgDurationMs |> sprintf "| Avg: %.0f ms" + let finished = eventCounts[Finished].Value + let avgDuration = if finished = 0 then "" else $"| Avg: %.0f{float duration / float finished} ms" - let hitRatio = - if started > 0 then - $" (%.0f{float hits / (float (started + hits)) * 100.0} %%)" - else - "" + let requests = eventCounts[Requested].Value + let hitRatio = if requests = 0 then "" else $" (%.0f{float hits / (float (requests)) * 100.0} %%)" + + let faulted = getStat "Faulted" + let failed = eventCounts[Failed].Value let stats = - [| - if errors + failed > 0 then + seq { + if faulted + failed > 0 then " (_!_) " - if errors > 0 then $"| ERRORS: {errors} " else "" - if failed > 0 then $"| FAILED: {failed} " else "" + for j in eventCounts.Keys do + let count = eventCounts[j].Value + if count > 0 then $"| {j}: {count}" else "" $"| hits: {hits}{hitRatio} " - if started > 0 then $"| started: {started} " else "" - if completed > 0 then $"| completed: {completed} " else "" - if canceled > 0 then $"| canceled: {canceled} " else "" - if restarted > 0 then $"| restarted: {restarted} " else "" - if evicted > 0 then $"| evicted: {evicted} " else "" - if collected > 0 then $"| collected: {collected} " else "" - if cleared > 0 then $"| cleared: {cleared} " else "" - if strengthened > 0 then - $"| strengthened: {strengthened} " - else - "" - |] + } |> String.concat "" - $"{locked}{running}{cache.DebuggerDisplay} {stats}{avgDuration}" + $"{running} {cache.DebuggerDisplay} {stats}{avgDuration}" /// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time. [] diff --git a/src/Compiler/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi index d86352d9987..4b312e6fb9c 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fsi +++ b/src/Compiler/Facilities/AsyncMemoize.fsi @@ -9,9 +9,6 @@ module internal Utils = /// Return file name with one directory above it val shortPath: path: string -> string - [] - val (|TaskCancelled|_|): ex: exn -> TaskCanceledException voption - type internal JobEvent = | Requested | Started @@ -39,13 +36,6 @@ type Extensions = [] static member internal WithExtraVersion: cacheKey: ICacheKey<'a, 'b> * extraVersion: 'c -> ICacheKey<'a, ('b * 'c)> -type internal AsyncLock = - interface System.IDisposable - - new: unit -> AsyncLock - - member Do: f: (unit -> #Task<'b>) -> Task<'b> - /// /// A cache/memoization for computations that makes sure that the same computation will only be computed once even if it's needed /// at multiple places/times. @@ -62,9 +52,10 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T /// Maximum number of strongly held results to keep in the cache /// Maximum number of weakly held results to keep in the cache /// Name of the cache - used in tracing messages + /// Cancels a job when all the awaiting requests are canceled. If set to false, unawaited job will run to completion and it's result will be cached. /// If true, when a job is started, all other jobs with the same key will be canceled. new: - ?keepStrongly: int * ?keepWeakly: int * ?name: string * ?cancelDuplicateRunningJobs: bool -> + ?keepStrongly: int * ?keepWeakly: int * ?name: string * ?cancelUnawaitedJobs: bool * ?cancelDuplicateRunningJobs: bool -> AsyncMemoize<'TKey, 'TVersion, 'TValue> member Clear: unit -> unit @@ -73,8 +64,6 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member Get: key: ICacheKey<'TKey, 'TVersion> * computation: Async<'TValue> -> Async<'TValue> - member Get': key: 'TKey * computation: Async<'TValue> -> Async<'TValue> - member TryGet: key: 'TKey * predicate: ('TVersion -> bool) -> 'TValue option member Event: IEvent diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 5158ac7f25c..f6c5bd6071e 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -1822,7 +1822,6 @@ type internal TransparentCompiler return assemblyDataResult with - | TaskCancelled ex -> return raise ex | ex -> errorR (exn ($"Error while computing assembly data for project {projectSnapshot.Label}: {ex}")) return ProjectAssemblyDataResult.Unavailable true diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs deleted file mode 100644 index ef4b69a3910..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncLock.fs +++ /dev/null @@ -1,26 +0,0 @@ -module CompilerService.AsyncLock - -open Internal.Utilities.Collections - -open Xunit -open System.Threading.Tasks - - -[] -let ``Async lock works`` () = - task { - use lock = new AsyncLock() - - let mutable x = 0 - - let job () = task { - let y = x - do! Task.Delay(10) - x <- y + 1 - } - - let jobs = [ for _ in 1..100 -> lock.Do job ] - let! _ = Task.WhenAll(jobs) - - Assert.Equal(100, x) - } \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index e178ccaa911..cf96bfb99c4 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -17,35 +17,40 @@ let internal record (cache: AsyncMemoize<_,_,_>) = let events = Collections.Concurrent.ConcurrentQueue() - let waitForIdle() = SpinWait.SpinUntil(fun () -> not cache.Updating) - - waitForIdle() cache.Event |> Event.map (fun (e, (_, k, _)) -> e, k) |> Event.add events.Enqueue - let getEvents () = - waitForIdle() - events |> List.ofSeq |> tap (printfn "events: %A") + let getEvents () = events |> List.ofSeq getEvents -let check getEvents assertFunction = +let check getEvents assertFunction expected = let actual = getEvents() - assertFunction actual + assertFunction expected actual let waitUntil getEvents condition = - while getEvents() |> condition |> not do () + SpinWait.SpinUntil(fun () -> getEvents() |> condition) let recorded (expected: 't list) (actual: 't list) = Assert.Equal<'t>(expected, actual) -let countOf value count events = - events |> Seq.filter (fst >> (=) value) |> Seq.length |> (=) count +let countOf value events = + events |> Seq.filter (fst >> (=) value) |> Seq.length let received value events = events |> List.tryLast |> Option.map (fst >> (=) value) |> Option.defaultValue false +let internal wrapKey key = + { new ICacheKey<_, _> with + member _.GetKey() = key + member _.GetVersion() = Unchecked.defaultof<_> + member _.GetLabel() = match key.ToString() with | null -> "" | s -> s + } + +let assertTaskCanceled (task: Task<_>) = + Assert.ThrowsAnyAsync(fun () -> task).Result |> ignore + [] let ``Basics``() = let computation key = async { @@ -58,12 +63,12 @@ let ``Basics``() = let result = seq { - memoize.Get'(5, computation 5) - memoize.Get'(5, computation 5) - memoize.Get'(2, computation 2) - memoize.Get'(5, computation 5) - memoize.Get'(3, computation 3) - memoize.Get'(2, computation 2) + memoize.Get(wrapKey 5, computation 5) + memoize.Get(wrapKey 5, computation 5) + memoize.Get(wrapKey 2, computation 2) + memoize.Get(wrapKey 5, computation 5) + memoize.Get(wrapKey 3, computation 3) + memoize.Get(wrapKey 2, computation 2) } |> Async.Parallel |> Async.RunSynchronously @@ -72,138 +77,148 @@ let ``Basics``() = Assert.Equal(expected, result) - check events <| fun events -> - let groups = events |> Seq.groupBy snd |> Seq.toList - Assert.Equal(3, groups.Length) - for key, events in groups do - Assert.Equal>(Set [ Requested, key; Started, key; Finished, key ], Set events) + let groups = events() |> Seq.groupBy snd |> Seq.toList + Assert.Equal(3, groups.Length) + for key, events in groups do + Assert.Equal>(Set [ Requested, key; Started, key; Finished, key ], Set events) [] -let ``We can cancel a job`` () = - task { +let ``We can disconnect a request from a running job`` () = - let jobStarted = new ManualResetEventSlim(false) - let cts = new CancellationTokenSource() - let ctsCancelled = new ManualResetEventSlim(false) + let cts = new CancellationTokenSource() + let canFinish = new ManualResetEventSlim(false) - let computation = async { - use! _catch = Async.OnCancel ignore - jobStarted.Set() - ctsCancelled.Wait() - do! async { } - failwith "Should be canceled before it gets here" - } + let computation = async { + canFinish.Wait() + } + + let memoize = AsyncMemoize<_, int, _>(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true) + let events = record memoize + + let key = 1 + + let task1 = Async.StartAsTask( memoize.Get(wrapKey 1, computation), cancellationToken = cts.Token) + + waitUntil events (received Started) + cts.Cancel() + + assertTaskCanceled task1 + + canFinish.Set() |> ignore - let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + waitUntil events (received Finished) - let key = 1 + check events recorded + [ Requested, key + Started, key + Finished, key ] - let _task1 = Async.StartAsTask( memoize.Get'(1, computation), cancellationToken = cts.Token) +[] +let ``We can cancel a job`` () = - jobStarted.Wait() - cts.Cancel() - ctsCancelled.Set() + let cts = new CancellationTokenSource() - check events recorded - [ Requested, key - Started, key - Canceled, key ] + let computation = async { + while true do + do! Async.Sleep 1000 } + let memoize = AsyncMemoize<_, int, _>() + let events = record memoize + + let key = 1 + + let task1 = Async.StartAsTask( memoize.Get(wrapKey 1, computation), cancellationToken = cts.Token) + + waitUntil events (received Started) + + cts.Cancel() + + assertTaskCanceled task1 + + check events recorded + [ Requested, key + Started, key + Canceled, key ] + [] let ``Job is restarted if first requestor cancels`` () = - let jobStarted = new SemaphoreSlim(0) + let jobCanComplete = new ManualResetEventSlim(false) - let jobCanComplete = new ManualResetEventSlim(false) - - let computation key = async { - jobStarted.Release() |> ignore + let computation key = async { + jobCanComplete.Wait() + return key * 2 + } - jobCanComplete.Wait() - return key * 2 - } + let memoize = AsyncMemoize<_, int, _>() + let events = record memoize - let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + use cts1 = new CancellationTokenSource() - use cts1 = new CancellationTokenSource() + let key = 1 - let key = 1 + let _task1 = Async.StartAsTask( memoize.Get(wrapKey key, computation key), cancellationToken = cts1.Token) - let _task1 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = cts1.Token) + waitUntil events (received Started) + cts1.Cancel() - jobStarted.Wait() - let task2 = Async.StartAsTask( memoize.Get'(key, computation key)) - let task3 = Async.StartAsTask( memoize.Get'(key, computation key)) + waitUntil events (received Canceled) - waitUntil events (countOf Requested 3) + let task2 = Async.StartAsTask( memoize.Get(wrapKey key, computation key)) - cts1.Cancel() + waitUntil events (countOf Started >> (=) 2) - jobCanComplete.Set() |> ignore + jobCanComplete.Set() |> ignore - jobStarted.Wait() + Assert.Equal(2, task2.Result) - Assert.Equal(2, task2.Result) - Assert.Equal(2, task3.Result) + check events recorded + [ Requested, key + Started, key + Canceled, key + Requested, key + Started, key + Finished, key ] - check events recorded - [ Requested, key - Started, key - Requested, key - Requested, key - Restarted, key - Finished, key ] [] -let ``Job is restarted if first requestor cancels but keeps running if second requestor cancels`` () = - let jobStarted = new ManualResetEventSlim(false) +let ``Job keeps running if only one requestor cancels`` () = - let jobCanComplete = new ManualResetEventSlim(false) + let jobCanComplete = new ManualResetEventSlim(false) - let computation key = async { - jobStarted.Set() |> ignore - jobCanComplete.Wait() - return key * 2 - } + let computation key = async { + jobCanComplete.Wait() + return key * 2 + } - let memoize = AsyncMemoize<_, int, _>() - let events = record memoize - - use cts1 = new CancellationTokenSource() - use cts2 = new CancellationTokenSource() - - let key = 1 + let memoize = AsyncMemoize<_, int, _>() + let events = record memoize - let _task1 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = cts1.Token) + use cts = new CancellationTokenSource() - jobStarted.Wait() - jobStarted.Reset() |> ignore + let key = 1 - let _task2 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = cts2.Token) - let task3 = Async.StartAsTask( memoize.Get'(key, computation key)) + let task1 = Async.StartAsTask( memoize.Get(wrapKey key, computation key)) - waitUntil events (countOf Requested 3) + waitUntil events (received Started) - cts1.Cancel() + let task2 = Async.StartAsTask( memoize.Get(wrapKey key, computation key) |> Async.Ignore, cancellationToken = cts.Token) - jobStarted.Wait() + waitUntil events (countOf Requested >> (=) 2) - cts2.Cancel() + cts.Cancel() - jobCanComplete.Set() |> ignore + assertTaskCanceled task2 - Assert.Equal(2, task3.Result) + jobCanComplete.Set() |> ignore - check events recorded - [ Requested, key - Started, key - Requested, key - Requested, key - Restarted, key - Finished, key ] + Assert.Equal(2, task1.Result) + check events recorded + [ Requested, key + Started, key + Requested, key + Finished, key ] type ExpectedException() = inherit Exception() @@ -290,7 +305,7 @@ let ``Stress test`` () = let timeoutMs = rng.Next(minTimeout, maxTimeout) let key = keys[rng.Next keys.Length] let result = key * 2 - let job = cache.Get'(key, computation durationMs result) + let job = cache.Get(wrapKey key, computation durationMs result) let cts = new CancellationTokenSource() let runningJob = Async.StartAsTask(job, cancellationToken = cts.Token) cts.CancelAfter timeoutMs @@ -328,60 +343,55 @@ let ``Stress test`` () = Assert.True ((float completed) > ((float started) * 0.1), "Less than 10 % completed jobs") -[] -[] -[] -let ``Cancel running jobs with the same key`` cancelDuplicate expectFinished = - let cache = AsyncMemoize(cancelDuplicateRunningJobs=cancelDuplicate) - - let mutable started = 0 - let mutable finished = 0 +[] +let ``Cancel running jobs with the same key`` () = + let cache = AsyncMemoize(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true) - let job1started = new ManualResetEventSlim(false) - let job1finished = new ManualResetEventSlim(false) + let events = record cache let jobCanContinue = new ManualResetEventSlim(false) - let job2started = new ManualResetEventSlim(false) - let job2finished = new ManualResetEventSlim(false) - - let work onStart onFinish = async { - Interlocked.Increment &started |> ignore - onStart() |> ignore + let work = async { jobCanContinue.Wait() - do! Async.Sleep 100 - Interlocked.Increment &finished |> ignore - onFinish() |> ignore } - let key1 = + let key version = { new ICacheKey<_, _> with member _.GetKey() = 1 - member _.GetVersion() = 1 - member _.GetLabel() = "key1" } + member _.GetVersion() = version + member _.GetLabel() = $"key1 {version}" } - cache.Get(key1, work job1started.Set job1finished.Set) |> Async.Catch |> Async.Ignore |> Async.Start + let cts = new CancellationTokenSource() - job1started.Wait() + let jobsToCancel = + [ for i in 1 .. 10 -> Async.StartAsTask(cache.Get(key i , work), cancellationToken = cts.Token) ] - let key2 = - { new ICacheKey<_, _> with - member _.GetKey() = key1.GetKey() - member _.GetVersion() = key1.GetVersion() + 1 - member _.GetLabel() = "key2" } + waitUntil events (countOf Started >> (=) 10) + + // detach requests from their running computations + cts.Cancel() + + for job in jobsToCancel do assertTaskCanceled job - cache.Get(key2, work job2started.Set job2finished.Set ) |> Async.Catch |> Async.Ignore |> Async.Start + // now the jobs should continue running unobserved + Assert.Equal(0, events() |> countOf Canceled) - job2started.Wait() + // new request should cancel the unobserved jobs + let job = cache.Get(key 11, work) |> Async.StartAsTask + + waitUntil events (countOf Canceled >> (=) 10) + + waitUntil events (countOf Started >> (=) 11) jobCanContinue.Set() |> ignore - - job2finished.Wait() - - if not cancelDuplicate then - job1finished.Wait() - Assert.Equal((2, expectFinished), (started, finished)) + job.Wait() + + Assert.Equal(0, events() |> countOf Failed) + + Assert.Equal(10, events() |> countOf Canceled) + + Assert.Equal(1, events() |> countOf Finished) type DummyException(msg) = inherit Exception(msg) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index ffc935a2075..a1a46d7a99e 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -300,7 +300,6 @@ - From 6fbfa4439c9182a6da9458c6c83080b89c76cb34 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:08:39 +0100 Subject: [PATCH 02/14] use lock free mb in tests --- src/Compiler/Facilities/AsyncMemoize.fs | 40 ++++---- src/Compiler/Service/TransparentCompiler.fs | 3 +- .../CompilerService/AsyncMemoize.fs | 92 +++++++++++-------- 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 703c956a784..a8a2501d946 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -44,17 +44,17 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = let cts = new CancellationTokenSource() let work = Async.StartAsTask(computation, cancellationToken = cts.Token) state <- Created (work, cts, 1) - work, true + work | Created (work, cts, count) -> state <- Created (work, cts, count + 1) - work, (count = 0) + work member _.Request = async { - let work, firstRequest = lock stateUpdateSync request + let work = lock stateUpdateSync request try let! ct = Async.CancellationToken - let options = if firstRequest then TaskContinuationOptions.ExecuteSynchronously else TaskContinuationOptions.None + let options = TaskContinuationOptions.ExecuteSynchronously try return! // Using ContinueWith with a CancellationToken allows detaching from the running 'work' task. @@ -132,7 +132,7 @@ type private KeyData<'TKey, 'TVersion> = Version: 'TVersion } -type Job<'t> = AsyncLazy> +type Job<'t> = AsyncLazy * CapturingDiagnosticsLogger> [] type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality @@ -152,6 +152,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let eventCounts = [for j in JobEvent.AllEvents -> j, ref 0] |> dict let mutable hits = 0 let mutable duration = 0L + let mutable events_in_flight = 0 let keyTuple (keyData: KeyData<_, _>) = keyData.Label, keyData.Key, keyData.Version @@ -189,16 +190,15 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let logger = CapturingDiagnosticsLogger "cache" SetThreadDiagnosticsLoggerNoUnwind logger - match! Async.Catch computation with - - | Choice1Of2 result -> + try + let! result = computation log Finished key Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore - return Result.Ok(result, logger) - - | Choice2Of2 ex -> + return Result.Ok result, logger + with + | ex -> log Failed key - return Result.Error(ex, logger) + return Result.Error ex, logger } let getOrAdd () = @@ -226,14 +226,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T use _ = new CompilationGlobalsScope() - match! job.Request with - - | Result.Ok(result, logger) -> - logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + let! result, logger = job.Request + logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + match result with + | Ok result -> return result - - | Result.Error(ex, logger) -> - logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + | Error ex -> return raise ex } @@ -244,7 +242,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T versionsAndJobs |> Seq.tryPick (fun (version, job) -> match predicate version, job.Result with - | true, Some(Result.Ok(result, _)) -> Some result + | true, Some(Ok result, _) -> Some result | _ -> None) member _.Clear() = cache.Clear() @@ -313,4 +311,4 @@ type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equalit Interlocked.Increment &requests |> ignore computation - member _.DebuggerDisplay = $"(disabled) requests: {requests}" + member _.DebuggerDisplay = $"(disabled) requests: {requests}" \ No newline at end of file diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index f6c5bd6071e..d0fb6b63768 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -1821,8 +1821,7 @@ type internal TransparentCompiler Trace.TraceInformation($"Using in-memory project reference: {name}") return assemblyDataResult - with - | ex -> + with ex -> errorR (exn ($"Error while computing assembly data for project {projectSnapshot.Label}: {ex}")) return ProjectAssemblyDataResult.Unavailable true } diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs index cf96bfb99c4..2c0b6c311be 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -11,35 +11,48 @@ open FSharp.Compiler.Diagnostics open Xunit -let tap f x = f x; x +let internal observe (cache: AsyncMemoize<_,_,_>) = -let internal record (cache: AsyncMemoize<_,_,_>) = + let collected = new MailboxProcessor<_>(fun _ -> async {}) - let events = Collections.Concurrent.ConcurrentQueue() + let arrivals = MailboxProcessor.Start(fun inbox -> + let rec loop events = async { + let! (e, (_, k, _)) = inbox.Receive() + let events = (e, k) :: events + printfn $"{k}: {e}" + collected.Post events + do! loop events + } + loop [] + ) + + cache.Event.Add arrivals.Post - cache.Event - |> Event.map (fun (e, (_, k, _)) -> e, k) - |> Event.add events.Enqueue + let next () = collected.Receive(10_000) - let getEvents () = events |> List.ofSeq + next - getEvents +let rec awaitEvents next condition = + async { + match! next () with + | events when condition events -> return events + | _ -> return! awaitEvents next condition + } -let check getEvents assertFunction expected = - let actual = getEvents() - assertFunction expected actual +let rec eventsWhen next condition = + awaitEvents next condition |> Async.RunSynchronously -let waitUntil getEvents condition = - SpinWait.SpinUntil(fun () -> getEvents() |> condition) +let waitUntil next condition = + eventsWhen next condition |> ignore -let recorded (expected: 't list) (actual: 't list) = - Assert.Equal<'t>(expected, actual) +let expect next (expected: 't list) = + let actual = eventsWhen next (List.length >> (=) expected.Length) + Assert.Equal<'t list>(expected, actual |> List.rev) let countOf value events = events |> Seq.filter (fst >> (=) value) |> Seq.length -let received value events = - events |> List.tryLast |> Option.map (fst >> (=) value) |> Option.defaultValue false +let received event = function (a, _) :: _ when a = event -> true | _ -> false let internal wrapKey key = { new ICacheKey<_, _> with @@ -59,7 +72,7 @@ let ``Basics``() = } let memoize = AsyncMemoize() - let events = record memoize + let events = observe memoize let result = seq { @@ -77,7 +90,9 @@ let ``Basics``() = Assert.Equal(expected, result) - let groups = events() |> Seq.groupBy snd |> Seq.toList + let events = eventsWhen events (countOf Finished >> (=) 3) + + let groups = events |> Seq.groupBy snd |> Seq.toList Assert.Equal(3, groups.Length) for key, events in groups do Assert.Equal>(Set [ Requested, key; Started, key; Finished, key ], Set events) @@ -93,7 +108,7 @@ let ``We can disconnect a request from a running job`` () = } let memoize = AsyncMemoize<_, int, _>(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true) - let events = record memoize + let events = observe memoize let key = 1 @@ -106,9 +121,7 @@ let ``We can disconnect a request from a running job`` () = canFinish.Set() |> ignore - waitUntil events (received Finished) - - check events recorded + expect events [ Requested, key Started, key Finished, key ] @@ -124,7 +137,7 @@ let ``We can cancel a job`` () = } let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + let events = observe memoize let key = 1 @@ -136,7 +149,7 @@ let ``We can cancel a job`` () = assertTaskCanceled task1 - check events recorded + expect events [ Requested, key Started, key Canceled, key ] @@ -151,7 +164,7 @@ let ``Job is restarted if first requestor cancels`` () = } let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + let events = observe memoize use cts1 = new CancellationTokenSource() @@ -172,7 +185,7 @@ let ``Job is restarted if first requestor cancels`` () = Assert.Equal(2, task2.Result) - check events recorded + expect events [ Requested, key Started, key Canceled, key @@ -192,7 +205,7 @@ let ``Job keeps running if only one requestor cancels`` () = } let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + let events = observe memoize use cts = new CancellationTokenSource() @@ -214,7 +227,7 @@ let ``Job keeps running if only one requestor cancels`` () = Assert.Equal(2, task1.Result) - check events recorded + expect events [ Requested, key Started, key Requested, key @@ -347,7 +360,7 @@ let ``Stress test`` () = let ``Cancel running jobs with the same key`` () = let cache = AsyncMemoize(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true) - let events = record cache + let events = observe cache let jobCanContinue = new ManualResetEventSlim(false) @@ -373,25 +386,26 @@ let ``Cancel running jobs with the same key`` () = for job in jobsToCancel do assertTaskCanceled job - // now the jobs should continue running unobserved - Assert.Equal(0, events() |> countOf Canceled) - - // new request should cancel the unobserved jobs let job = cache.Get(key 11, work) |> Async.StartAsTask - waitUntil events (countOf Canceled >> (=) 10) + // up til now the jobs should have been running unobserved + let current = eventsWhen events (received Requested) + Assert.Equal(0, current |> countOf Canceled) - waitUntil events (countOf Started >> (=) 11) + // new request should cancel the unobserved jobs + waitUntil events (received Started) jobCanContinue.Set() |> ignore job.Wait() - Assert.Equal(0, events() |> countOf Failed) + let events = eventsWhen events (received Finished) + + Assert.Equal(0, events |> countOf Failed) - Assert.Equal(10, events() |> countOf Canceled) + Assert.Equal(10, events |> countOf Canceled) - Assert.Equal(1, events() |> countOf Finished) + Assert.Equal(1, events |> countOf Finished) type DummyException(msg) = inherit Exception(msg) From 4c4c9e9389c9210cd5aac93c763e64363d481dda Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:29:59 +0100 Subject: [PATCH 03/14] remove unnecessary prop --- src/Compiler/Facilities/AsyncMemoize.fs | 2 -- src/Compiler/Facilities/AsyncMemoize.fsi | 2 -- .../FSharpChecker/TransparentCompiler.fs | 4 ---- 3 files changed, 8 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index a8a2501d946..50ae2e6a76e 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -255,8 +255,6 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member this.Count = lock cache <| fun () -> cache.Count - member _.Updating = false - member this.DebuggerDisplay = let (|Running|_|) (job: Job<_>) = job.Task |> Option.filter (_.IsCompleted >> not) diff --git a/src/Compiler/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi index 4b312e6fb9c..37d2d397fe2 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fsi +++ b/src/Compiler/Facilities/AsyncMemoize.fsi @@ -72,8 +72,6 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member Count: int - member Updating: bool - /// A drop-in replacement for AsyncMemoize that disables caching and just runs the computation every time. type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality> = diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index b05096303d4..8667ca0b2af 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -33,17 +33,13 @@ let internal recordAllEvents groupBy = let mutable cache : AsyncMemoize<_,_,_> option = None let events = ConcurrentQueue() - let waitForIdle() = SpinWait.SpinUntil(fun () -> not cache.Value.Updating) - let observe (getCache: CompilerCaches -> AsyncMemoize<_,_,_>) (checker: FSharpChecker) = cache <- Some (getCache checker.Caches) - waitForIdle() cache.Value.Event |> Event.map (fun (e, k) -> groupBy k, e) |> Event.add events.Enqueue let getEvents () = - waitForIdle() events |> List.ofSeq observe, getEvents From 34c86407e25a66d15e8a9d290b8949ee32d74b9d Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:30:13 +0100 Subject: [PATCH 04/14] update il baselines --- ...lverify_FSharp.Compiler.Service_Debug_net9.0.bsl | 12 ++++++------ ...FSharp.Compiler.Service_Debug_netstandard2.0.bsl | 12 ++++++------ ...erify_FSharp.Compiler.Service_Release_net9.0.bsl | 13 +++++++------ ...harp.Compiler.Service_Release_netstandard2.0.bsl | 13 +++++++------ 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl index 527bb371db2..ca7431af41c 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl @@ -21,14 +21,14 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-779::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-774::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@110::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::getCompilerOption([FSharp.Compiler.Service]FSharp.Compiler.CompilerOptions+CompilerOption, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1)][offset 0x000000E6][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::AddPathMapping([FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, string)][offset 0x0000000B][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl index 164060143d0..10da48090c7 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl @@ -28,18 +28,18 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000039][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-779::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-774::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x0000001B][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@110::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x00000059][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1&)][offset 0x000000DA][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-6::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000605][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-491::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$Symbols+fullName@2490-1::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000015][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CreateILModule+MainModuleBuilder::ConvertProductVersionToILVersionInfo(string)][offset 0x00000011][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl index 7de0bcd6baf..6fb78dda558 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl @@ -7,6 +7,7 @@ [IL]: Error [StackByRef]: : FSharp.Compiler.IO.RawByteMemory::set_Item(int32, uint8)][offset 0x0000001B][found Native Int] Expected ByRef on the stack. [IL]: Error [ReturnPtrToStack]: : Internal.Utilities.Text.Lexing.LexBuffer`1::get_LexemeView()][offset 0x00000017] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : Internal.Utilities.Text.Lexing.UnicodeTables::scanUntilSentinel([FSharp.Compiler.Service]Internal.Utilities.Text.Lexing.LexBuffer`1, int32)][offset 0x0000008D][found Short] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.AsyncMemoize`3::TryGet(!0, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2)][offset 0x0000000F][found ref '[FSharp.Compiler.Service].$AsyncMemoize+TryGet@243'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,FSharp.Compiler.DiagnosticsLogger+CapturingDiagnosticsLogger>>>,Microsoft.FSharp.Core.FSharpOption`1>'] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Xml.XmlDoc::processLines([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1)][offset 0x0000002C][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::getTfmNumber(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::tryGetRunningTfm()][offset 0x00000011][found Char] Unexpected type on the stack. @@ -21,13 +22,13 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-835::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-816::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x00000014][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index 21b484b426d..537d9f367d6 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl @@ -7,6 +7,7 @@ [IL]: Error [StackByRef]: : FSharp.Compiler.IO.RawByteMemory::set_Item(int32, uint8)][offset 0x0000001B][found Native Int] Expected ByRef on the stack. [IL]: Error [ReturnPtrToStack]: : Internal.Utilities.Text.Lexing.LexBuffer`1::get_LexemeView()][offset 0x00000017] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : Internal.Utilities.Text.Lexing.UnicodeTables::scanUntilSentinel([FSharp.Compiler.Service]Internal.Utilities.Text.Lexing.LexBuffer`1, int32)][offset 0x0000008D][found Short] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.AsyncMemoize`3::TryGet(!0, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2)][offset 0x0000000F][found ref '[FSharp.Compiler.Service].$AsyncMemoize+TryGet@243'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,FSharp.Compiler.DiagnosticsLogger+CapturingDiagnosticsLogger>>>,Microsoft.FSharp.Core.FSharpOption`1>'] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Xml.XmlDoc::processLines([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1)][offset 0x0000002C][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::getTfmNumber(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::tryGetRunningTfm()][offset 0x00000011][found Char] Unexpected type on the stack. @@ -28,17 +29,17 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000032][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-835::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-816::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x00000024][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x0000002B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1&)][offset 0x000000BB][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-11::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000618][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-528::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$Symbols+fullName@2490-3::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000030][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x00000014][found Char] Unexpected type on the stack. From b08a37efba12a4049373b701e72d157891d08dbe Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:54:22 +0100 Subject: [PATCH 05/14] rename --- src/Compiler/Facilities/AsyncMemoize.fs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 50ae2e6a76e..a98cf15c214 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -66,7 +66,7 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = |> Async.AwaitTask // Cancellation check before entering the `with` ensures TaskCanceledEXception coming from the ContinueWith task will never be raised here. // The cancellation continuation will always be called in case of cancellation. - with ex -> return raise ex + with exn -> return raise exn finally lock stateUpdateSync afterRequest } @@ -152,7 +152,6 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let eventCounts = [for j in JobEvent.AllEvents -> j, ref 0] |> dict let mutable hits = 0 let mutable duration = 0L - let mutable events_in_flight = 0 let keyTuple (keyData: KeyData<_, _>) = keyData.Label, keyData.Key, keyData.Version @@ -196,9 +195,9 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore return Result.Ok result, logger with - | ex -> + | exn -> log Failed key - return Result.Error ex, logger + return Result.Error exn, logger } let getOrAdd () = @@ -231,8 +230,8 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T match result with | Ok result -> return result - | Error ex -> - return raise ex + | Error exn -> + return raise exn } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = From d3b483b2361c3f2b075655cb426548e71253d1df Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:50:29 +0100 Subject: [PATCH 06/14] add requestCount name Co-authored-by: Petr Pokorny --- src/Compiler/Facilities/AsyncMemoize.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 50ae2e6a76e..a0a7fd5808f 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -12,7 +12,7 @@ open System.Runtime.CompilerServices type AsyncLazyState<'t> = | Initial of Async<'t> - | Created of Task<'t> * CancellationTokenSource * int + | Created of Task<'t> * CancellationTokenSource * requestCount: int /// Represents a computation that will execute only once but can be requested by multiple clients. /// It keeps track of the number of requests. When all clients cancel their requests, the underlying computation will also cancel and can be restarted. From 5569dc645fc2745b4c3fb1167536b682f3d00f17 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:51:51 +0100 Subject: [PATCH 07/14] improve as reviewed --- src/Compiler/Facilities/AsyncMemoize.fs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index a98cf15c214..eb9a1a0892e 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -36,7 +36,7 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = | Created(work, cts, count) -> state <- Created(work, cts, count - 1) if cancelUnawaited then cancelIfUnawaited () - | _ -> () + | state -> failwith $"Invalid AsyncLazyState: {state}" let request () = match state with @@ -75,7 +75,7 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = member _.CancelIfUnawaited() = lock stateUpdateSync cancelIfUnawaited member _.Task = match state with Created(t, _, _) -> Some t | _ -> None - member this.Result = + member this.TryResult = this.Task |> Option.filter (fun t -> t.Status = TaskStatus.RanToCompletion) |> Option.map _.Result @@ -240,7 +240,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T versionsAndJobs |> Seq.tryPick (fun (version, job) -> - match predicate version, job.Result with + match predicate version, job.TryResult with | true, Some(Ok result, _) -> Some result | _ -> None) From f72c2a4ca6a1deaa6ffc2927151572bb1103e087 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:24:19 +0100 Subject: [PATCH 08/14] lock for TryGet --- src/Compiler/Facilities/AsyncMemoize.fs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 2507f2f1098..1dc957df092 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -235,14 +235,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = - let versionsAndJobs = - lock cache <| fun () ->cache.GetAll(key) - - versionsAndJobs - |> Seq.tryPick (fun (version, job) -> - match predicate version, job.TryResult with - | true, Some(Ok result, _) -> Some result - | _ -> None) + lock cache <| fun () -> + cache.GetAll(key) + |> Seq.tryPick (fun (version, job) -> + match predicate version, job.TryResult with + | true, Some(Ok result, _) -> Some result + | _ -> None) member _.Clear() = cache.Clear() From 4c25ac60bba7c064da93c987aa8f03faeb917303 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:39:21 +0100 Subject: [PATCH 09/14] more locks --- src/Compiler/Facilities/AsyncMemoize.fs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 1dc957df092..ec7a2371aba 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -235,16 +235,16 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = - lock cache <| fun () -> - cache.GetAll(key) - |> Seq.tryPick (fun (version, job) -> - match predicate version, job.TryResult with - | true, Some(Ok result, _) -> Some result - | _ -> None) + let versionsAndJobs = lock cache <| fun () -> cache.GetAll(key) |> Seq.toList + versionsAndJobs + |> Seq.tryPick (fun (version, job) -> + match predicate version, job.TryResult with + | true, Some(Ok result, _) -> Some result + | _ -> None) - member _.Clear() = cache.Clear() + member _.Clear() = lock cache cache.Clear - member _.Clear predicate = cache.Clear predicate + member _.Clear predicate = lock cache <| fun () -> cache.Clear predicate member val Event = event.Publish From 2633596dd583e2676747bd9cc8d1e1456e92e176 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:40:18 +0100 Subject: [PATCH 10/14] ilverify --- eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl | 1 - eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl | 1 - 2 files changed, 2 deletions(-) diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl index 6fb78dda558..f607ccb6eb6 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl @@ -7,7 +7,6 @@ [IL]: Error [StackByRef]: : FSharp.Compiler.IO.RawByteMemory::set_Item(int32, uint8)][offset 0x0000001B][found Native Int] Expected ByRef on the stack. [IL]: Error [ReturnPtrToStack]: : Internal.Utilities.Text.Lexing.LexBuffer`1::get_LexemeView()][offset 0x00000017] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : Internal.Utilities.Text.Lexing.UnicodeTables::scanUntilSentinel([FSharp.Compiler.Service]Internal.Utilities.Text.Lexing.LexBuffer`1, int32)][offset 0x0000008D][found Short] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.AsyncMemoize`3::TryGet(!0, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2)][offset 0x0000000F][found ref '[FSharp.Compiler.Service].$AsyncMemoize+TryGet@243'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,FSharp.Compiler.DiagnosticsLogger+CapturingDiagnosticsLogger>>>,Microsoft.FSharp.Core.FSharpOption`1>'] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Xml.XmlDoc::processLines([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1)][offset 0x0000002C][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::getTfmNumber(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::tryGetRunningTfm()][offset 0x00000011][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index 537d9f367d6..a5df2d786f1 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl @@ -7,7 +7,6 @@ [IL]: Error [StackByRef]: : FSharp.Compiler.IO.RawByteMemory::set_Item(int32, uint8)][offset 0x0000001B][found Native Int] Expected ByRef on the stack. [IL]: Error [ReturnPtrToStack]: : Internal.Utilities.Text.Lexing.LexBuffer`1::get_LexemeView()][offset 0x00000017] Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. [IL]: Error [StackUnexpected]: : Internal.Utilities.Text.Lexing.UnicodeTables::scanUntilSentinel([FSharp.Compiler.Service]Internal.Utilities.Text.Lexing.LexBuffer`1, int32)][offset 0x0000008D][found Short] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : Internal.Utilities.Collections.AsyncMemoize`3::TryGet(!0, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2)][offset 0x0000000F][found ref '[FSharp.Compiler.Service].$AsyncMemoize+TryGet@243'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,FSharp.Compiler.DiagnosticsLogger+CapturingDiagnosticsLogger>>>,Microsoft.FSharp.Core.FSharpOption`1>'] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Xml.XmlDoc::processLines([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1)][offset 0x0000002C][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::getTfmNumber(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.FxResolver::tryGetRunningTfm()][offset 0x00000011][found Char] Unexpected type on the stack. From aad924b66c613f5570d6f738804ac1b93af8e808 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:56:29 +0100 Subject: [PATCH 11/14] deallocate computation when completed --- src/Compiler/Facilities/AsyncMemoize.fs | 111 ++++++++++++++---------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index ec7a2371aba..154f0842ed1 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -5,53 +5,49 @@ open System.Diagnostics open System.IO open System.Threading open System.Threading.Tasks +open System.Runtime.CompilerServices +open System.Runtime.ExceptionServices open FSharp.Compiler.DiagnosticsLogger open Internal.Utilities.Library -open System.Runtime.CompilerServices type AsyncLazyState<'t> = - | Initial of Async<'t> - | Created of Task<'t> * CancellationTokenSource * requestCount: int + | Initial of computation: Async<'t> + | Running of initialComputation: Async<'t> * work: Task<'t> * CancellationTokenSource * requestCount: int + | Completed of result: 't + | Faulted of exn /// Represents a computation that will execute only once but can be requested by multiple clients. /// It keeps track of the number of requests. When all clients cancel their requests, the underlying computation will also cancel and can be restarted. /// If cancelUnawaited is set to false, the computation will run to completion even when all requests are canceled. -type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = - - let cancelUnawaited = defaultArg cancelUnawaited true +type AsyncLazy<'t> private (initial: AsyncLazyState<'t>, cancelUnawaited: bool, cacheException: bool) = let stateUpdateSync = obj() - let mutable state = Initial computation + let mutable state = initial let cancelIfUnawaited () = match state with - | Created(work, cts, 0) when not work.IsCompleted -> + | Running(computation, _, cts, 0) -> cts.Cancel() state <- Initial computation | _ -> () let afterRequest () = match state with - | Created(work, cts, count) -> - state <- Created(work, cts, count - 1) + | Running(computation, work, _, _) when work.IsCompleted -> + state <- + try + Completed work.Result + with + | exn -> + if cacheException then Faulted exn else Initial computation + | Running(c, work, cts, count) -> + state <- Running(c, work, cts, count - 1) if cancelUnawaited then cancelIfUnawaited () - | state -> failwith $"Invalid AsyncLazyState: {state}" - - let request () = - match state with - | Initial computation -> - let cts = new CancellationTokenSource() - let work = Async.StartAsTask(computation, cancellationToken = cts.Token) - state <- Created (work, cts, 1) - work - | Created (work, cts, count) -> - state <- Created (work, cts, count + 1) - work + | _ -> () // Nothing more to do if another request already transitioned the state. - member _.Request = + let detachable (work: Task<'t>) = async { - let work = lock stateUpdateSync request try let! ct = Async.CancellationToken let options = TaskContinuationOptions.ExecuteSynchronously @@ -64,21 +60,42 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) = // but will immediately proceed to the finally block. work.ContinueWith((fun (t: Task<_>) -> t.Result), ct, options, TaskScheduler.Current) |> Async.AwaitTask - // Cancellation check before entering the `with` ensures TaskCanceledEXception coming from the ContinueWith task will never be raised here. + // Cancellation check before entering the `with` ensures TaskCanceledException coming from the ContinueWith task will never be raised here. // The cancellation continuation will always be called in case of cancellation. with exn -> return raise exn finally lock stateUpdateSync afterRequest } + let request () = + match state with + | Initial computation -> + let cts = new CancellationTokenSource() + let work = Async.StartAsTask(computation, cancellationToken = cts.Token) + state <- Running (computation, work, cts, 1) + detachable work + | Running (c, work, cts, count) -> + state <- Running (c, work, cts, count + 1) + detachable work + | Completed result -> + async { return result } + | Faulted exn -> + async { return raise exn } + + // computation will deallocate after state transition to Completed ot Faulted. + new (computation, ?cancelUnawaited: bool, ?cacheException) = + AsyncLazy(Initial computation, defaultArg cancelUnawaited true, defaultArg cacheException false) + + member _.Request() = lock stateUpdateSync request member _.CancelIfUnawaited() = lock stateUpdateSync cancelIfUnawaited - member _.Task = match state with Created(t, _, _) -> Some t | _ -> None - member this.TryResult = - this.Task - |> Option.filter (fun t -> t.Status = TaskStatus.RanToCompletion) - |> Option.map _.Result + member _.State = state + + member this.TryResult = + match state with + | Completed result -> Some result + | _ -> None [] module internal Utils = @@ -181,7 +198,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Version = key.GetVersion() } - let wrappedComputation = + let wrapppedComputation = async { use! _handler = Async.OnCancel (fun () -> log Canceled key) let sw = Stopwatch.StartNew() @@ -205,7 +222,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let countHit v = Interlocked.Increment &hits |> ignore; v let cacheSetNewJob () = - let job = Job(wrappedComputation, cancelUnawaited = cancelUnawaitedJobs) + let job = Job(wrapppedComputation, cancelUnawaited = cancelUnawaitedJobs) cache.Set(key.Key, key.Version, key.Label, job) job @@ -225,7 +242,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T use _ = new CompilationGlobalsScope() - let! result, logger = job.Request + let! result, logger = job.Request() logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger match result with | Ok result -> @@ -235,12 +252,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = - let versionsAndJobs = lock cache <| fun () -> cache.GetAll(key) |> Seq.toList - versionsAndJobs - |> Seq.tryPick (fun (version, job) -> - match predicate version, job.TryResult with - | true, Some(Ok result, _) -> Some result - | _ -> None) + lock cache <| fun () -> + cache.GetAll(key) + |> Seq.tryPick (fun (version, job) -> + match predicate version, job.TryResult with + | true, Some(Ok result, _) -> Some result + | _ -> None) member _.Clear() = lock cache cache.Clear @@ -254,17 +271,15 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T member this.DebuggerDisplay = - let (|Running|_|) (job: Job<_>) = job.Task |> Option.filter (_.IsCompleted >> not) - let (|Faulted|_|) (job: Job<_>) = job.Task |> Option.filter _.IsFaulted - - let status = function - | Running _ -> "Running" - | Faulted _ -> "Faulted" - | _ -> "other" - let cachedJobs = cache.GetValues() |> Seq.map (fun (_,_,job) -> job) - let valueStats = cachedJobs |> Seq.countBy status |> Map + let jobStateName = function + | Initial _ -> nameof Initial + | Running _ -> nameof Running + | Completed _ -> nameof Completed + | Faulted _ -> nameof Faulted + + let valueStats = cachedJobs |> Seq.countBy (_.State >> jobStateName) |> Map let getStat key = valueStats.TryFind key |> Option.defaultValue 0 let running = From 4c441fb02910048178d80c9c1a368806a07bd124 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 23:21:51 +0100 Subject: [PATCH 12/14] ilverify --- ...ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl | 12 ++++++------ ..._FSharp.Compiler.Service_Debug_netstandard2.0.bsl | 12 ++++++------ ...verify_FSharp.Compiler.Service_Release_net9.0.bsl | 12 ++++++------ ...Sharp.Compiler.Service_Release_netstandard2.0.bsl | 12 ++++++------ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl index ca7431af41c..d59c7d2adda 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl @@ -21,14 +21,14 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-774::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-791::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@110::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::getCompilerOption([FSharp.Compiler.Service]FSharp.Compiler.CompilerOptions+CompilerOption, [FSharp.Core]Microsoft.FSharp.Core.FSharpOption`1)][offset 0x000000E6][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CompilerOptions::AddPathMapping([FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, string)][offset 0x0000000B][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl index 10da48090c7..be76953ef7d 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl @@ -28,18 +28,18 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000039][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-774::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-791::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x0000001B][found Char] Unexpected type on the stack. [IL]: Error [UnmanagedPointer]: : FSharp.Compiler.Interactive.Shell+Utilities+pointerToNativeInt@110::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x00000059][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1&)][offset 0x000000DA][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-6::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000605][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-487::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000082][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000008B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-504::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000094][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$Symbols+fullName@2490-1::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000015][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CreateILModule+MainModuleBuilder::ConvertProductVersionToILVersionInfo(string)][offset 0x00000011][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl index f607ccb6eb6..1c6248ec60a 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl @@ -21,13 +21,13 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x00000082][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-816::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-831::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x00000014][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.StaticLinking+TypeForwarding::followTypeForwardForILTypeRef([FSharp.Compiler.Service]FSharp.Compiler.AbstractIL.IL+ILTypeRef)][offset 0x00000010][found Char] Unexpected type on the stack. diff --git a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index a5df2d786f1..c358adc7976 100644 --- a/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/eng/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl @@ -28,17 +28,17 @@ [IL]: Error [StackUnexpected]: : FSharp.Compiler.CodeAnalysis.Hosted.CompilerHelpers::fscCompile([FSharp.Compiler.Service]FSharp.Compiler.CodeAnalysis.LegacyReferenceResolver, string, string[])][offset 0x0000008B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiStdinSyphon::GetLine(string, int32)][offset 0x00000032][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+MagicAssemblyResolution::ResolveAssemblyCore([FSharp.Compiler.Service]Internal.Utilities.Library.CompilationThreadToken, [FSharp.Compiler.Service]FSharp.Compiler.Text.Range, [FSharp.Compiler.Service]FSharp.Compiler.CompilerConfig+TcConfigBuilder, [FSharp.Compiler.Service]FSharp.Compiler.CompilerImports+TcImports, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompiler, [FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiConsoleOutput, string)][offset 0x00000015][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-816::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3510-831::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+FsiInteractionProcessor::CompletionsForPartialLID([FSharp.Compiler.Service]FSharp.Compiler.Interactive.Shell+FsiDynamicCompilerState, string)][offset 0x00000024][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2205::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseMemberFunctionAndValues@176::Invoke([FSharp.Compiler.Service]FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue)][offset 0x0000002B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.AssemblyContent+traverseEntity@218::GenerateNext([S.P.CoreLib]System.Collections.Generic.IEnumerable`1&)][offset 0x000000BB][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.EditorServices.ParsedInput+visitor@1423-11::VisitExpr([FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>, [FSharp.Compiler.Service]FSharp.Compiler.Syntax.SynExpr)][offset 0x00000618][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-510::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000032][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000003B][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000064][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x0000006D][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@921-525::Invoke([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2,Microsoft.FSharp.Core.Unit>)][offset 0x00000076][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$Symbols+fullName@2490-3::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000030][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@301-1::Invoke(string)][offset 0x00000014][found Char] Unexpected type on the stack. From 1611ab037d4d37e8121aabd29cd10ed9c75379e2 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 18 Nov 2024 23:39:41 +0100 Subject: [PATCH 13/14] style --- src/Compiler/Facilities/AsyncMemoize.fs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 154f0842ed1..5063550e778 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -206,13 +206,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let logger = CapturingDiagnosticsLogger "cache" SetThreadDiagnosticsLoggerNoUnwind logger - try - let! result = computation + match! computation |> Async.Catch with + | Choice1Of2 result -> log Finished key Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore return Result.Ok result, logger - with - | exn -> + | Choice2Of2 exn -> log Failed key return Result.Error exn, logger } From c76e295ea887f9411dda01bf81dc2725aa23b16d Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 19 Nov 2024 21:07:23 +0100 Subject: [PATCH 14/14] typo --- src/Compiler/Facilities/AsyncMemoize.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index 5063550e778..88b0b84bb8c 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -198,7 +198,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T Version = key.GetVersion() } - let wrapppedComputation = + let wrappedComputation = async { use! _handler = Async.OnCancel (fun () -> log Canceled key) let sw = Stopwatch.StartNew() @@ -221,7 +221,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T let countHit v = Interlocked.Increment &hits |> ignore; v let cacheSetNewJob () = - let job = Job(wrapppedComputation, cancelUnawaited = cancelUnawaitedJobs) + let job = Job(wrappedComputation, cancelUnawaited = cancelUnawaitedJobs) cache.Set(key.Key, key.Version, key.Label, job) job