From 6460381b00c446c347d0aba81a47fe20b3fe37c0 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:48:31 +0200 Subject: [PATCH 1/4] improve metrics, add eviction stress test --- src/Compiler/Utilities/Caches.fs | 171 ++++++++---------- src/Compiler/Utilities/Caches.fsi | 8 +- .../CompilerService/Caches.fs | 88 ++++++--- .../src/FSharp.Editor/Common/Logging.fs | 12 -- .../LanguageService/LanguageService.fs | 2 - 5 files changed, 146 insertions(+), 135 deletions(-) diff --git a/src/Compiler/Utilities/Caches.fs b/src/Compiler/Utilities/Caches.fs index 30c45f1ecd9..590d5358d76 100644 --- a/src/Compiler/Utilities/Caches.fs +++ b/src/Compiler/Utilities/Caches.fs @@ -7,6 +7,7 @@ open System.Collections.Concurrent open System.Threading open System.Diagnostics open System.Diagnostics.Metrics +open System.Collections.Immutable [] type CacheOptions = @@ -57,97 +58,86 @@ type CachedEntity<'Key, 'Value> = // Currently the Cache itself exposes Metrics.Counters that count raw cache events: hits, misses, evictions etc. // This class observes those counters and keeps a snapshot of readings. For now this is used only to print cache stats in debug mode. // TODO: We could add some System.Diagnostics.Metrics.Gauge instruments to this class, to get computed stats also exposed as metrics. -type CacheMetrics(cacheId) = +type CacheMetrics(cacheId: string) = static let meter = new Meter("FSharp.Compiler.Cache") - static let observedCaches = ConcurrentDictionary() - let readings = ConcurrentDictionary() + let created = meter.CreateCounter("created", "count", cacheId) + let hits = meter.CreateCounter("hits", "count", cacheId) + let misses = meter.CreateCounter("misses", "count", cacheId) + let evictions = meter.CreateCounter("evictions", "count", cacheId) + let evictionFails = meter.CreateCounter("eviction-fails", "count", cacheId) + let allCouinters = [ created; hits; misses; evictions; evictionFails ] + + let totals = + let builder = ImmutableDictionary.CreateBuilder() + + for counter in allCouinters do + builder.Add(counter, ref 0L) + + builder.ToImmutable() + + let incr key v = + Interlocked.Add(totals[key], v) |> ignore + + let total key = totals[key].Value + + let mutable ratio = Double.NaN + + let updateRatio () = + ratio <- float (total hits) / float (total hits + total misses) let listener = new MeterListener() - do - listener.InstrumentPublished <- - fun i l -> - if i.Meter = meter && i.Description = cacheId then - l.EnableMeasurementEvents(i) + let startListening () = + for i in allCouinters do + listener.EnableMeasurementEvents i - listener.SetMeasurementEventCallback(fun k v _ _ -> Interlocked.Add(readings.GetOrAdd(k.Name, ref 0L), v) |> ignore) - listener.Start() + listener.SetMeasurementEventCallback(fun instrument v _ _ -> + incr instrument v - member this.Dispose() = listener.Dispose() + if instrument = hits || instrument = misses then + updateRatio ()) - member val CacheId = cacheId + listener.Start() - static member val Meter = meter + member val Created = created + member val Hits = hits + member val Misses = misses + member val Evictions = evictions + member val EvictionFails = evictionFails - member val RecentStats = "-" with get, set - - member this.TryUpdateStats(clearCounts) = - let ratio = - try - float readings["hits"].Value - / float (readings["hits"].Value + readings["misses"].Value) - * 100.0 - with _ -> - Double.NaN - - let stats = - [ - for name in readings.Keys do - let v = readings[name].Value - - if v > 0 then - $"{name}: {v}" - ] - |> String.concat ", " - |> sprintf "%s | hit ratio: %s %s" this.CacheId (if Double.IsNaN(ratio) then "-" else $"%.1f{ratio}%%") - - if clearCounts then - for r in readings.Values do - Interlocked.Exchange(r, 0L) |> ignore - - if stats <> this.RecentStats then - this.RecentStats <- stats - true - else - false + member this.ObserveMetrics() = + observedCaches[cacheId] <- this + startListening () - // TODO: Should return a Map, not a string - static member GetStats(cacheId) = - observedCaches[cacheId].TryUpdateStats(false) |> ignore - observedCaches[cacheId].RecentStats + member this.Dispose() = + observedCaches.TryRemove cacheId |> ignore + listener.Dispose() - static member GetStatsUpdateForAllCaches(clearCounts) = - [ - for i in observedCaches.Values do - if i.TryUpdateStats(clearCounts) then - i.RecentStats - ] - |> String.concat "\n" + member _.GetInstanceTotals() = + [ for k in totals.Keys -> k.Name, total k ] |> Map.ofList - static member AddInstrumentation(cacheId) = - if observedCaches.ContainsKey cacheId then - invalidArg "cacheId" $"cache with name {cacheId} already exists" + member _.GetInstanceStats() = [ "hit-ratio", ratio ] |> Map.ofList - observedCaches[cacheId] <- new CacheMetrics(cacheId) + static member val Meter = meter - static member RemoveInstrumentation(cacheId) = - observedCaches[cacheId].Dispose() - observedCaches.TryRemove(cacheId) |> ignore + static member GetTotals(cacheId) = + observedCaches[cacheId].GetInstanceTotals() -// Creates and after reclaiming holds entities for reuse. + static member GetStats(cacheId) = + observedCaches[cacheId].GetInstanceStats() + +// Creates and, after reclaiming, holds entities for reuse. // More than totalCapacity can be created, but it will hold for reuse at most totalCapacity. -type EntityPool<'Key, 'Value>(totalCapacity, cacheId) = +type EntityPool<'Key, 'Value>(totalCapacity, metrics: CacheMetrics) = let pool = ConcurrentBag>() - let created = CacheMetrics.Meter.CreateCounter("created", "count", cacheId) - member _.Acquire(key, value) = match pool.TryTake() with | true, entity -> entity.ReUse(key, value) | _ -> - created.Add 1L + metrics.Created.Add 1L CachedEntity.Create(key, value) member _.Reclaim(entity: CachedEntity<'Key, 'Value>) = @@ -176,25 +166,15 @@ type EvictionQueueMessage<'Key, 'Value> = [] [] -type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalCapacity, headroom, ?name, ?observeMetrics) = - - let instanceId = defaultArg name (Guid.NewGuid().ToString()) +type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalCapacity, headroom, name, listen) = - let observeMetrics = defaultArg observeMetrics false + let metrics = new CacheMetrics(name) do - if observeMetrics then - CacheMetrics.AddInstrumentation instanceId + if listen then + metrics.ObserveMetrics() - let meter = CacheMetrics.Meter - let hits = meter.CreateCounter("hits", "count", instanceId) - let misses = meter.CreateCounter("misses", "count", instanceId) - let evictions = meter.CreateCounter("evictions", "count", instanceId) - - let evictionFails = - meter.CreateCounter("eviction-fails", "count", instanceId) - - let pool = EntityPool<'Key, 'Value>(totalCapacity, instanceId) + let pool = EntityPool<'Key, 'Value>(totalCapacity, metrics) let store = ConcurrentDictionary<'Key, CachedEntity<'Key, 'Value>>(Environment.ProcessorCount, totalCapacity) @@ -205,6 +185,7 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC let capacity = totalCapacity - headroom let evicted = Event<_>() + let evictionFailed = Event<_>() let cts = new CancellationTokenSource() @@ -225,9 +206,12 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC | true, removed -> evictionQueue.Remove(first) pool.Reclaim(removed) - evictions.Add 1L + metrics.Evictions.Add 1L evicted.Trigger() - | _ -> evictionFails.Add 1L + | _ -> + // This should not be possible to happen, but if it does, we want to know. + metrics.EvictionFails.Add 1L + evictionFailed.Trigger() // Store updates are not synchronized. It is possible the entity is no longer in the queue. | EvictionQueueMessage.Update entity when isNull entity.Node.List -> () @@ -245,18 +229,17 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC ) member val Evicted = evicted.Publish - - member val Name = instanceId + member val EvictionFailed = evictionFailed.Publish member _.TryGetValue(key: 'Key, value: outref<'Value>) = match store.TryGetValue(key) with | true, entity -> - hits.Add 1L + metrics.Hits.Add 1L evictionProcessor.Post(EvictionQueueMessage.Update entity) value <- entity.Value true | _ -> - misses.Add 1L + metrics.Misses.Add 1L value <- Unchecked.defaultof<'Value> false @@ -278,14 +261,10 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC cts.Dispose() evictionProcessor.Dispose() store.Clear() - - if observeMetrics then - CacheMetrics.RemoveInstrumentation instanceId + metrics.Dispose() member this.Dispose() = (this :> IDisposable).Dispose() - member this.GetStats() = CacheMetrics.GetStats(this.Name) - static member Create<'Key, 'Value>(options: CacheOptions, ?name, ?observeMetrics) = if options.TotalCapacity < 0 then invalidArg "Capacity" "Capacity must be positive" @@ -298,7 +277,9 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC let headroom = int (float options.TotalCapacity * float options.HeadroomPercentage / 100.0) - let cache = - new Cache<_, _>(totalCapacity, headroom, ?name = name, ?observeMetrics = observeMetrics) + let name = defaultArg name (Guid.NewGuid().ToString()) + let observeMetrics = defaultArg observeMetrics false + + let cache = new Cache<_, _>(totalCapacity, headroom, name, observeMetrics) cache diff --git a/src/Compiler/Utilities/Caches.fsi b/src/Compiler/Utilities/Caches.fsi index 565342bf7f5..6d33917f40a 100644 --- a/src/Compiler/Utilities/Caches.fsi +++ b/src/Compiler/Utilities/Caches.fsi @@ -21,8 +21,6 @@ module internal Cache = [] type internal Cache<'Key, 'Value when 'Key: not null and 'Key: equality> = - new: totalCapacity: int * headroom: int * ?name: string * ?observeMetrics: bool -> Cache<'Key, 'Value> - member TryGetValue: key: 'Key * value: outref<'Value> -> bool member TryAdd: key: 'Key * value: 'Value -> bool /// Cancels the background eviction task. @@ -32,6 +30,7 @@ type internal Cache<'Key, 'Value when 'Key: not null and 'Key: equality> = /// For testing only member Evicted: IEvent + member EvictionFailed: IEvent static member Create<'Key, 'Value> : options: CacheOptions * ?name: string * ?observeMetrics: bool -> Cache<'Key, 'Value> @@ -39,6 +38,5 @@ type internal Cache<'Key, 'Value when 'Key: not null and 'Key: equality> = [] type internal CacheMetrics = static member Meter: Meter - static member GetStats: cacheId: string -> string - /// Retrieves current hit ratio, hits, misses, evictions etc. formatted for printing or logging. - static member GetStatsUpdateForAllCaches: clearCounts: bool -> string + static member GetStats: cacheId: string -> Map + static member GetTotals: cacheId: string -> Map diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs index 3849cd9addd..8e3555a1df3 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs @@ -2,8 +2,19 @@ module CompilerService.Caches open FSharp.Compiler.Caches -open System.Threading open Xunit +open FSharp.Test +open System.Threading.Tasks +open System.Diagnostics + +let assertTrue msg b = Assert.True(b, msg) + +#if DEBUG +let shouldNeverTimeout = 15_000 +#else +// accomodate unpredictable CI thread scheduling +let shouldNeverTimeout = 200_000 +#endif [] let ``Create and dispose many`` () = @@ -29,8 +40,8 @@ let ``Create and dispose many named`` () = let ``Basic add and retrieve`` () = use cache = Cache.Create(CacheOptions.Default, observeMetrics = true) - cache.TryAdd("key1", 1) |> ignore - cache.TryAdd("key2", 2) |> ignore + cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" + cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" let mutable value = 0 Assert.True(cache.TryGetValue("key1", &value), "Should retrieve key1") @@ -43,21 +54,23 @@ let ``Basic add and retrieve`` () = let ``Eviction of least recently used`` () = use cache = Cache.Create({ TotalCapacity = 2; HeadroomPercentage = 0 }, observeMetrics = true) - cache.TryAdd("key1", 1) |> ignore - cache.TryAdd("key2", 2) |> ignore + cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" + cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" // Make key1 recently used by accessing it let mutable value = 0 - cache.TryGetValue("key1", &value) |> ignore + cache.TryGetValue("key1", &value) |> assertTrue "failed to access key1" + + let evictionResult = TaskCompletionSource() + + cache.Evicted.Add evictionResult.SetResult + cache.EvictionFailed.Add (fun _ -> evictionResult.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed")) - let evicted = new ManualResetEvent(false) - cache.Evicted.Add(fun _ -> evicted.Set() |> ignore) - // Add a third item, which should schedule key2 for eviction - cache.TryAdd("key3", 3) |> ignore + cache.TryAdd("key3", 3) |> assertTrue "failed to add key3" // Wait for eviction to complete using the event - evicted.WaitOne() |> ignore + evictionResult.Task.Wait shouldNeverTimeout |> assertTrue "eviction did not complete in time" Assert.False(cache.TryGetValue("key2", &value), "key2 should have been evicted") Assert.True(cache.TryGetValue("key1", &value), "key1 should still be in cache") @@ -65,26 +78,59 @@ let ``Eviction of least recently used`` () = Assert.True(cache.TryGetValue("key3", &value), "key3 should be in cache") Assert.Equal(3, value) +[] +let ``Stress test evictions`` () = + let cacheSize = 100 + let iterations = 10_000 + let name = "Stress test evictions" + use cache = Cache.Create({ TotalCapacity = cacheSize; HeadroomPercentage = 0 }, name = name, observeMetrics = true) + + let evictionsCompleted = new TaskCompletionSource() + let expectedEvictions = iterations - cacheSize + + cache.Evicted.Add <| fun () -> + if CacheMetrics.GetTotals(name).["evictions"] = expectedEvictions then evictionsCompleted.SetResult() + + // Should not fail, but if it does, we want to know + cache.EvictionFailed.Add <| fun _ -> + evictionsCompleted.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed") + + for i in 1 .. iterations do + cache.TryAdd($"key{i}", i) |> assertTrue ($"failed to add key{i}") + + // Wait for all expected evictions to complete + evictionsCompleted.Task.Wait shouldNeverTimeout |> assertTrue "evictions did not complete in time" + + let mutable value = 0 + Assert.False(cache.TryGetValue($"key{iterations - cacheSize}", &value), "An old key should have been evicted") + Assert.True(cache.TryGetValue($"key{iterations - cacheSize + 1}", &value), "The first of the newest keys should be in cache") + Assert.Equal(iterations - cacheSize + 1, value) + Assert.True(cache.TryGetValue($"key{iterations}", &value), "The last key should be in cache") + Assert.Equal(iterations, value) + [] let ``Metrics can be retrieved`` () = use cache = Cache.Create({ TotalCapacity = 2; HeadroomPercentage = 0 }, name = "test_metrics", observeMetrics = true) - cache.TryAdd("key1", 1) |> ignore - cache.TryAdd("key2", 2) |> ignore + cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" + cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" // Make key1 recently used by accessing it let mutable value = 0 - cache.TryGetValue("key1", &value) |> ignore + cache.TryGetValue("key1", &value) |> assertTrue "failed to access key1" - let evicted = new ManualResetEvent(false) - cache.Evicted.Add(fun _ -> evicted.Set() |> ignore) + let evictionCompleted = TaskCompletionSource() + cache.Evicted.Add(fun _ -> evictionCompleted.SetResult()) + cache.EvictionFailed.Add(fun _ -> evictionCompleted.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed")) // Add a third item, which should schedule key2 for eviction - cache.TryAdd("key3", 3) |> ignore + cache.TryAdd("key3", 3) |> assertTrue "failed to add key3" - // Wait for eviction to complete using the event - evicted.WaitOne() |> ignore + // Wait for eviction to complete + evictionCompleted.Task.Wait shouldNeverTimeout |> assertTrue "eviction did not complete in time" - let metrics = CacheMetrics.GetStats "test_metrics" + let stats = CacheMetrics.GetStats "test_metrics" + let totals = CacheMetrics.GetTotals "test_metrics" - Assert.Contains("test_metrics | hit ratio", metrics) + Assert.Equal( 1.0, stats["hit-ratio"]) + Assert.Equal(1L, totals["evictions"]) diff --git a/vsintegration/src/FSharp.Editor/Common/Logging.fs b/vsintegration/src/FSharp.Editor/Common/Logging.fs index de96d46e254..eedd668d70c 100644 --- a/vsintegration/src/FSharp.Editor/Common/Logging.fs +++ b/vsintegration/src/FSharp.Editor/Common/Logging.fs @@ -150,18 +150,6 @@ module FSharpServiceTelemetry = ActivitySource.AddActivityListener(listener) - let logCacheMetricsToOutput () = - - let timer = new System.Timers.Timer(1000.0, AutoReset = true) - - timer.Elapsed.Add(fun _ -> - let stats = CacheMetrics.GetStatsUpdateForAllCaches(clearCounts = true) - - if stats <> "" then - logMsg $"\n{stats}") - - timer.Start() - #if DEBUG open OpenTelemetry.Resources open OpenTelemetry.Trace diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index b7e34648aed..59b56631a31 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -344,8 +344,6 @@ type internal FSharpPackage() as this = do FSharp.Interactive.Hooks.fsiConsoleWindowPackageCtorUnsited (this :> Package) #if DEBUG - do Logging.FSharpServiceTelemetry.logCacheMetricsToOutput () - let flushTelemetry = Logging.FSharpServiceTelemetry.otelExport () override this.Dispose(disposing: bool) = From a42417e8bf2fdbdb5a3d553f4212794ea9f9532b Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:53:45 +0200 Subject: [PATCH 2/4] make the tests look nicer --- .../CompilerService/Caches.fs | 124 +++++++++--------- 1 file changed, 59 insertions(+), 65 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs index 8e3555a1df3..c883c903549 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/Caches.fs @@ -1,14 +1,11 @@ module CompilerService.Caches open FSharp.Compiler.Caches - open Xunit -open FSharp.Test +open FSharp.Test.Assert open System.Threading.Tasks open System.Diagnostics -let assertTrue msg b = Assert.True(b, msg) - #if DEBUG let shouldNeverTimeout = 15_000 #else @@ -19,118 +16,115 @@ let shouldNeverTimeout = 200_000 [] let ``Create and dispose many`` () = let caches = - [ - for _ in 1 .. 100 do - Cache.Create(CacheOptions.Default, observeMetrics = true) - ] + [ for _ in 1 .. 100 do + Cache.Create(CacheOptions.Default, observeMetrics = true) ] - for c in caches do c.Dispose() + for c in caches do + c.Dispose() [] let ``Create and dispose many named`` () = let caches = - [ - for i in 1 .. 100 do - Cache.Create(CacheOptions.Default, name = $"testCache{i}", observeMetrics = true) - ] + [ for i in 1 .. 100 do + Cache.Create(CacheOptions.Default, name = $"testCache{i}", observeMetrics = true) ] - for c in caches do c.Dispose() + for c in caches do + c.Dispose() [] let ``Basic add and retrieve`` () = use cache = Cache.Create(CacheOptions.Default, observeMetrics = true) - - cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" - cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" - + + cache.TryAdd("key1", 1) |> shouldBeTrue + cache.TryAdd("key2", 2) |> shouldBeTrue + let mutable value = 0 - Assert.True(cache.TryGetValue("key1", &value), "Should retrieve key1") - Assert.Equal(1, value) - Assert.True(cache.TryGetValue("key2", &value), "Should retrieve key2") - Assert.Equal(2, value) - Assert.False(cache.TryGetValue("key3", &value), "Should not retrieve non-existent key3") + + cache.TryGetValue("key1", &value) |> shouldBeTrue + value |> shouldEqual 1 + + cache.TryGetValue("key2", &value) |> shouldBeTrue + value |> shouldEqual 2 + + cache.TryGetValue("key3", &value) |> shouldBeFalse [] let ``Eviction of least recently used`` () = use cache = Cache.Create({ TotalCapacity = 2; HeadroomPercentage = 0 }, observeMetrics = true) - - cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" - cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" - - // Make key1 recently used by accessing it + + cache.TryAdd("key1", 1) |> shouldBeTrue + cache.TryAdd("key2", 2) |> shouldBeTrue + let mutable value = 0 - cache.TryGetValue("key1", &value) |> assertTrue "failed to access key1" + cache.TryGetValue("key1", &value) |> shouldBeTrue let evictionResult = TaskCompletionSource() - cache.Evicted.Add evictionResult.SetResult cache.EvictionFailed.Add (fun _ -> evictionResult.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed")) - // Add a third item, which should schedule key2 for eviction - cache.TryAdd("key3", 3) |> assertTrue "failed to add key3" - - // Wait for eviction to complete using the event - evictionResult.Task.Wait shouldNeverTimeout |> assertTrue "eviction did not complete in time" - - Assert.False(cache.TryGetValue("key2", &value), "key2 should have been evicted") - Assert.True(cache.TryGetValue("key1", &value), "key1 should still be in cache") - Assert.Equal(1, value) - Assert.True(cache.TryGetValue("key3", &value), "key3 should be in cache") - Assert.Equal(3, value) + cache.TryAdd("key3", 3) |> shouldBeTrue + evictionResult.Task.Wait shouldNeverTimeout |> shouldBeTrue + + cache.TryGetValue("key2", &value) |> shouldBeFalse + + cache.TryGetValue("key1", &value) |> shouldBeTrue + value |> shouldEqual 1 + + cache.TryGetValue("key3", &value) |> shouldBeTrue + value |> shouldEqual 3 [] let ``Stress test evictions`` () = let cacheSize = 100 let iterations = 10_000 let name = "Stress test evictions" + use cache = Cache.Create({ TotalCapacity = cacheSize; HeadroomPercentage = 0 }, name = name, observeMetrics = true) let evictionsCompleted = new TaskCompletionSource() let expectedEvictions = iterations - cacheSize cache.Evicted.Add <| fun () -> - if CacheMetrics.GetTotals(name).["evictions"] = expectedEvictions then evictionsCompleted.SetResult() + if CacheMetrics.GetTotals(name).["evictions"] = expectedEvictions then + evictionsCompleted.SetResult() - // Should not fail, but if it does, we want to know cache.EvictionFailed.Add <| fun _ -> evictionsCompleted.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed") for i in 1 .. iterations do - cache.TryAdd($"key{i}", i) |> assertTrue ($"failed to add key{i}") + cache.TryAdd($"key{i}", i) |> shouldBeTrue - // Wait for all expected evictions to complete - evictionsCompleted.Task.Wait shouldNeverTimeout |> assertTrue "evictions did not complete in time" + evictionsCompleted.Task.Wait shouldNeverTimeout |> shouldBeTrue let mutable value = 0 - Assert.False(cache.TryGetValue($"key{iterations - cacheSize}", &value), "An old key should have been evicted") - Assert.True(cache.TryGetValue($"key{iterations - cacheSize + 1}", &value), "The first of the newest keys should be in cache") - Assert.Equal(iterations - cacheSize + 1, value) - Assert.True(cache.TryGetValue($"key{iterations}", &value), "The last key should be in cache") - Assert.Equal(iterations, value) + + cache.TryGetValue($"key{iterations - cacheSize}", &value) |> shouldBeFalse + + cache.TryGetValue($"key{iterations - cacheSize + 1}", &value) |> shouldBeTrue + value |> shouldEqual (iterations - cacheSize + 1) + + cache.TryGetValue($"key{iterations}", &value) |> shouldBeTrue + value |> shouldEqual iterations [] let ``Metrics can be retrieved`` () = use cache = Cache.Create({ TotalCapacity = 2; HeadroomPercentage = 0 }, name = "test_metrics", observeMetrics = true) - - cache.TryAdd("key1", 1) |> assertTrue "failed to add key1" - cache.TryAdd("key2", 2) |> assertTrue "failed to add key2" - - // Make key1 recently used by accessing it + + cache.TryAdd("key1", 1) |> shouldBeTrue + cache.TryAdd("key2", 2) |> shouldBeTrue + let mutable value = 0 - cache.TryGetValue("key1", &value) |> assertTrue "failed to access key1" + cache.TryGetValue("key1", &value) |> shouldBeTrue let evictionCompleted = TaskCompletionSource() cache.Evicted.Add(fun _ -> evictionCompleted.SetResult()) cache.EvictionFailed.Add(fun _ -> evictionCompleted.SetException(Xunit.Sdk.FailException.ForFailure "eviction failed")) - - // Add a third item, which should schedule key2 for eviction - cache.TryAdd("key3", 3) |> assertTrue "failed to add key3" - - // Wait for eviction to complete - evictionCompleted.Task.Wait shouldNeverTimeout |> assertTrue "eviction did not complete in time" + + cache.TryAdd("key3", 3) |> shouldBeTrue + evictionCompleted.Task.Wait shouldNeverTimeout |> shouldBeTrue let stats = CacheMetrics.GetStats "test_metrics" let totals = CacheMetrics.GetTotals "test_metrics" - Assert.Equal( 1.0, stats["hit-ratio"]) - Assert.Equal(1L, totals["evictions"]) + stats.["hit-ratio"] |> shouldEqual 1.0 + totals.["evictions"] |> shouldEqual 1L From 1e21ae983ccf1b24044b3d16a9a3c9594b8815b9 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:14:39 +0200 Subject: [PATCH 3/4] ilver --- ...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/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl index 496515c023d..49d551bf67a 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl +++ b/tests/ILVerify/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@3499-813::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3499-814::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@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000084][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-515::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@924-515::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@924-515::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@924-515::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@924-515::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@924-516::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@924-516::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@924-516::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@924-516::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@924-516::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/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl index e3eb5d75f65..9c8fd89e374 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Debug_netstandard2.0.bsl +++ b/tests/ILVerify/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@3499-813::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001E5][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3499-814::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@106::Invoke(object)][offset 0x00000007] Unmanaged pointers are not a verifiable type. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+dataTipOfReferences@2225::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@1431-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@924-515::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@924-515::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@924-515::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@924-515::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@924-515::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@924-516::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@924-516::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@924-516::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@924-516::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@924-516::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@2498-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/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl index ed3f064766f..6ab282e7053 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_net9.0.bsl +++ b/tests/ILVerify/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@3499-851::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3499-852::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : .$FSharpCheckerResults+GetReferenceResolutionStructuredToolTipText@2225::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000076][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-530::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@924-530::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@924-530::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@924-530::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@924-530::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@924-531::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@924-531::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@924-531::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@924-531::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@924-531::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@286-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@286-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/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl b/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl index 4ea9408c317..fe105e6f2ec 100644 --- a/tests/ILVerify/ilverify_FSharp.Compiler.Service_Release_netstandard2.0.bsl +++ b/tests/ILVerify/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@3499-851::Invoke([S.P.CoreLib]System.Tuple`3)][offset 0x000001C7][found Char] Unexpected type on the stack. +[IL]: Error [StackUnexpected]: : FSharp.Compiler.Interactive.Shell+clo@3499-852::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@2225::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@1431-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 0x00000620][found Char] Unexpected type on the stack. -[IL]: Error [StackUnexpected]: : .$ServiceLexing+clo@924-530::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@924-530::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@924-530::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@924-530::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@924-530::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@924-531::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@924-531::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@924-531::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@924-531::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@924-531::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@2498-3::Invoke([FSharp.Core]Microsoft.FSharp.Core.Unit)][offset 0x00000030][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@286-1::Invoke(string)][offset 0x0000000B][found Char] Unexpected type on the stack. [IL]: Error [StackUnexpected]: : FSharp.Compiler.Driver+ProcessCommandLineFlags@286-1::Invoke(string)][offset 0x00000014][found Char] Unexpected type on the stack. From 9ee3fb15edad24ecee283bca2b2e81c63496efd3 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:08:32 +0200 Subject: [PATCH 4/4] fix 18819 --- src/Compiler/Checking/TypeRelations.fs | 85 +++++++++++--------------- src/Compiler/Utilities/Caches.fs | 30 +-------- 2 files changed, 38 insertions(+), 77 deletions(-) diff --git a/src/Compiler/Checking/TypeRelations.fs b/src/Compiler/Checking/TypeRelations.fs index fddf6027875..1c97346384d 100644 --- a/src/Compiler/Checking/TypeRelations.fs +++ b/src/Compiler/Checking/TypeRelations.fs @@ -101,20 +101,6 @@ let TypesFeasiblyEquiv ndeep g amap m ty1 ty2 = let TypesFeasiblyEquivStripMeasures g amap m ty1 ty2 = TypesFeasiblyEquivalent true 0 g amap m ty1 ty2 -let inline TryGetCachedTypeSubsumption (g: TcGlobals) (amap: ImportMap) key = - if g.langVersion.SupportsFeature LanguageFeature.UseTypeSubsumptionCache then - match amap.TypeSubsumptionCache.TryGetValue(key) with - | true, subsumes -> - ValueSome subsumes - | false, _ -> - ValueNone - else - ValueNone - -let inline UpdateCachedTypeSubsumption (g: TcGlobals) (amap: ImportMap) key subsumes : unit = - if g.langVersion.SupportsFeature LanguageFeature.UseTypeSubsumptionCache then - amap.TypeSubsumptionCache.TryAdd(key, subsumes) |> ignore - /// The feasible coercion relation. Part of the language spec. let rec TypeFeasiblySubsumesType ndeep (g: TcGlobals) (amap: ImportMap) m (ty1: TType) (canCoerce: CanCoerce) (ty2: TType) = @@ -124,41 +110,42 @@ let rec TypeFeasiblySubsumesType ndeep (g: TcGlobals) (amap: ImportMap) m (ty1: let ty1 = stripTyEqns g ty1 let ty2 = stripTyEqns g ty2 - // Check if language feature supported - let key = TTypeCacheKey.FromStrippedTypes (ty1, ty2, canCoerce) - - match TryGetCachedTypeSubsumption g amap key with - | ValueSome subsumes -> - subsumes - | ValueNone -> - let subsumes = - match ty1, ty2 with - | TType_measure _, TType_measure _ - | TType_var _, _ | _, TType_var _ -> - true - - | TType_app (tc1, l1, _), TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 -> - List.lengthsEqAndForall2 (TypesFeasiblyEquiv ndeep g amap m) l1 l2 - - | TType_tuple _, TType_tuple _ - | TType_anon _, TType_anon _ - | TType_fun _, TType_fun _ -> - TypesFeasiblyEquiv ndeep g amap m ty1 ty2 - - | _ -> - // F# reference types are subtypes of type 'obj' - if isObjTyAnyNullness g ty1 && (canCoerce = CanCoerce || isRefTy g ty2) then - true - elif isAppTy g ty2 && (canCoerce = CanCoerce || isRefTy g ty2) && TypeFeasiblySubsumesTypeWithSupertypeCheck g amap m ndeep ty1 ty2 then - true - else - let interfaces = GetImmediateInterfacesOfType SkipUnrefInterfaces.Yes g amap m ty2 - // See if any interface in type hierarchy of ty2 is a supertype of ty1 - List.exists (TypeFeasiblySubsumesType (ndeep + 1) g amap m ty1 NoCoerce) interfaces - - UpdateCachedTypeSubsumption g amap key subsumes - - subsumes + let checkSubsumes ty1 ty2 = + match ty1, ty2 with + | TType_measure _, TType_measure _ + | TType_var _, _ | _, TType_var _ -> + true + + | TType_app (tc1, l1, _), TType_app (tc2, l2, _) when tyconRefEq g tc1 tc2 -> + List.lengthsEqAndForall2 (TypesFeasiblyEquiv ndeep g amap m) l1 l2 + + | TType_tuple _, TType_tuple _ + | TType_anon _, TType_anon _ + | TType_fun _, TType_fun _ -> + TypesFeasiblyEquiv ndeep g amap m ty1 ty2 + + | _ -> + // F# reference types are subtypes of type 'obj' + if isObjTyAnyNullness g ty1 && (canCoerce = CanCoerce || isRefTy g ty2) then + true + elif isAppTy g ty2 && (canCoerce = CanCoerce || isRefTy g ty2) && TypeFeasiblySubsumesTypeWithSupertypeCheck g amap m ndeep ty1 ty2 then + true + else + let interfaces = GetImmediateInterfacesOfType SkipUnrefInterfaces.Yes g amap m ty2 + // See if any interface in type hierarchy of ty2 is a supertype of ty1 + List.exists (TypeFeasiblySubsumesType (ndeep + 1) g amap m ty1 NoCoerce) interfaces + + if g.langVersion.SupportsFeature LanguageFeature.UseTypeSubsumptionCache then + let key = TTypeCacheKey.FromStrippedTypes (ty1, ty2, canCoerce) + + match amap.TypeSubsumptionCache.TryGetValue(key) with + | true, subsumes -> subsumes + | false, _ -> + let subsumes = checkSubsumes ty1 ty2 + amap.TypeSubsumptionCache.TryAdd(key, subsumes) |> ignore + subsumes + else + checkSubsumes ty1 ty2 and TypeFeasiblySubsumesTypeWithSupertypeCheck g amap m ndeep ty1 ty2 = match GetSuperTypeOfType g amap m ty2 with diff --git a/src/Compiler/Utilities/Caches.fs b/src/Compiler/Utilities/Caches.fs index 590d5358d76..658341258ad 100644 --- a/src/Compiler/Utilities/Caches.fs +++ b/src/Compiler/Utilities/Caches.fs @@ -48,11 +48,6 @@ type CachedEntity<'Key, 'Value> = entity.node <- LinkedListNode(entity) entity - member this.ReUse(key, value) = - this.key <- key - this.value <- value - this - override this.ToString() = $"{this.Key}" // Currently the Cache itself exposes Metrics.Counters that count raw cache events: hits, misses, evictions etc. @@ -128,22 +123,6 @@ type CacheMetrics(cacheId: string) = static member GetStats(cacheId) = observedCaches[cacheId].GetInstanceStats() -// Creates and, after reclaiming, holds entities for reuse. -// More than totalCapacity can be created, but it will hold for reuse at most totalCapacity. -type EntityPool<'Key, 'Value>(totalCapacity, metrics: CacheMetrics) = - let pool = ConcurrentBag>() - - member _.Acquire(key, value) = - match pool.TryTake() with - | true, entity -> entity.ReUse(key, value) - | _ -> - metrics.Created.Add 1L - CachedEntity.Create(key, value) - - member _.Reclaim(entity: CachedEntity<'Key, 'Value>) = - if pool.Count < totalCapacity then - pool.Add(entity) - module Cache = // During testing a lot of compilations are started in app domains and subprocesses. // This is a reliable way to pass the override to all of them. @@ -174,8 +153,6 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC if listen then metrics.ObserveMetrics() - let pool = EntityPool<'Key, 'Value>(totalCapacity, metrics) - let store = ConcurrentDictionary<'Key, CachedEntity<'Key, 'Value>>(Environment.ProcessorCount, totalCapacity) @@ -203,9 +180,8 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC let first = nonNull evictionQueue.First match store.TryRemove(first.Value.Key) with - | true, removed -> + | true, _ -> evictionQueue.Remove(first) - pool.Reclaim(removed) metrics.Evictions.Add 1L evicted.Trigger() | _ -> @@ -244,14 +220,12 @@ type Cache<'Key, 'Value when 'Key: not null and 'Key: equality> internal (totalC false member _.TryAdd(key: 'Key, value: 'Value) = - let entity = pool.Acquire(key, value) + let entity = CachedEntity.Create(key, value) let added = store.TryAdd(key, entity) if added then evictionProcessor.Post(EvictionQueueMessage.Add entity) - else - pool.Reclaim(entity) added