diff --git a/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl b/eng/ilverify_FSharp.Compiler.Service_Debug_net9.0.bsl index 527bb371db2..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-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-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-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-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 164060143d0..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-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-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-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-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 7de0bcd6baf..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-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-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-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-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 21b484b426d..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-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-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-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-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. diff --git a/src/Compiler/Facilities/AsyncMemoize.fs b/src/Compiler/Facilities/AsyncMemoize.fs index af1a52e5e5d..88b0b84bb8c 100644 --- a/src/Compiler/Facilities/AsyncMemoize.fs +++ b/src/Compiler/Facilities/AsyncMemoize.fs @@ -1,18 +1,101 @@ namespace Internal.Utilities.Collections open System -open System.Collections.Generic open System.Diagnostics open System.IO open System.Threading open System.Threading.Tasks +open System.Runtime.CompilerServices +open System.Runtime.ExceptionServices -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 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> private (initial: AsyncLazyState<'t>, cancelUnawaited: bool, cacheException: bool) = + + let stateUpdateSync = obj() + let mutable state = initial + + let cancelIfUnawaited () = + match state with + | Running(computation, _, cts, 0) -> + cts.Cancel() + state <- Initial computation + | _ -> () + + let afterRequest () = + match state with + | 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 () + | _ -> () // Nothing more to do if another request already transitioned the state. + + let detachable (work: Task<'t>) = + async { + try + let! ct = Async.CancellationToken + let options = TaskContinuationOptions.ExecuteSynchronously + 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 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 _.State = state + + member this.TryResult = + match state with + | Completed result -> Some result + | _ -> None [] module internal Utils = @@ -29,52 +112,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 +124,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 +149,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 * CapturingDiagnosticsLogger> [] type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality @@ -153,342 +158,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,136 +198,114 @@ 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 + + match! computation |> Async.Catch with + | Choice1Of2 result -> + log Finished key + Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore + return Result.Ok result, logger + | Choice2Of2 exn -> + log Failed key + return Result.Error exn, logger + } - let callerDiagnosticLogger = DiagnosticsThreadStatics.DiagnosticsLogger + let getOrAdd () = + let cached, otherVersions = cache.GetAll(key.Key, key.Version) - match! - processRequest post (key, GetOrCompute(computation, ct)) callerDiagnosticLogger - |> Async.AwaitTask - with - | New internalCt -> + 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 - let linkedCtSource = CancellationTokenSource.CreateLinkedTokenSource(ct, internalCt) - let cachingLogger = new CachingDiagnosticsLogger(Some callerDiagnosticLogger) + otherVersions, - 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 + 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() + let! result, logger = job.Request() + logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger + match result with + | Ok result -> + return result + | Error exn -> + return raise exn } member _.TryGet(key: 'TKey, predicate: 'TVersion -> bool) : 'TValue option = - let versionsAndJobs = cache.GetAll(key) - - versionsAndJobs - |> Seq.tryPick (fun (version, job) -> - match predicate version, job with - | true, Completed(completed, _) -> Some completed - | _ -> 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() + 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 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 _.Locked = lock.Semaphore.CurrentCount < 1 + member this.DebuggerDisplay = - member _.Running = - cache.GetValues() - |> Seq.filter (function - | _, _, Running _ -> true - | _ -> false) - |> Seq.toArray + let cachedJobs = cache.GetValues() |> Seq.map (fun (_,_,job) -> job) - member this.DebuggerDisplay = - let locked = if this.Locked then " [LOCKED]" else "" + let jobStateName = function + | Initial _ -> nameof Initial + | Running _ -> nameof Running + | Completed _ -> nameof Completed + | Faulted _ -> nameof Faulted - let valueStats = - cache.GetValues() - |> Seq.countBy (function - | _, _, Running _ -> "Running" - | _, _, Completed _ -> "Completed" - | _, _, Job.Canceled _ -> "Canceled" - | _, _, Job.Failed _ -> "Failed") - |> Map + let valueStats = cachedJobs |> Seq.countBy (_.State >> jobStateName) |> 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. [] @@ -640,4 +320,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/Facilities/AsyncMemoize.fsi b/src/Compiler/Facilities/AsyncMemoize.fsi index d86352d9987..37d2d397fe2 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 @@ -83,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/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 5158ac7f25c..d0fb6b63768 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -1821,9 +1821,7 @@ type internal TransparentCompiler Trace.TraceInformation($"Using in-memory project reference: {name}") return assemblyDataResult - with - | TaskCancelled ex -> return raise ex - | 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/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..2c0b6c311be 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs @@ -11,40 +11,58 @@ 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 - let waitForIdle() = SpinWait.SpinUntil(fun () -> not cache.Updating) + let next () = collected.Receive(10_000) - waitForIdle() - cache.Event - |> Event.map (fun (e, (_, k, _)) -> e, k) - |> Event.add events.Enqueue + next + +let rec awaitEvents next condition = + async { + match! next () with + | events when condition events -> return events + | _ -> return! awaitEvents next condition + } - let getEvents () = - waitForIdle() - events |> List.ofSeq |> tap (printfn "events: %A") +let rec eventsWhen next condition = + awaitEvents next condition |> Async.RunSynchronously - getEvents +let waitUntil next condition = + eventsWhen next condition |> ignore -let check getEvents assertFunction = - let actual = getEvents() - assertFunction actual +let expect next (expected: 't list) = + let actual = eventsWhen next (List.length >> (=) expected.Length) + Assert.Equal<'t list>(expected, actual |> List.rev) -let waitUntil getEvents condition = - while getEvents() |> condition |> not do () +let countOf value events = + events |> Seq.filter (fst >> (=) value) |> Seq.length -let recorded (expected: 't list) (actual: 't list) = - Assert.Equal<'t>(expected, actual) +let received event = function (a, _) :: _ when a = event -> true | _ -> false -let countOf value count events = - events |> Seq.filter (fst >> (=) value) |> Seq.length |> (=) count +let internal wrapKey key = + { new ICacheKey<_, _> with + member _.GetKey() = key + member _.GetVersion() = Unchecked.defaultof<_> + member _.GetLabel() = match key.ToString() with | null -> "" | s -> s + } -let received value events = - events |> List.tryLast |> Option.map (fst >> (=) value) |> Option.defaultValue false +let assertTaskCanceled (task: Task<_>) = + Assert.ThrowsAnyAsync(fun () -> task).Result |> ignore [] let ``Basics``() = @@ -54,16 +72,16 @@ let ``Basics``() = } let memoize = AsyncMemoize() - let events = record memoize + let events = observe memoize 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 +90,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 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) [] -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 = observe memoize + + let key = 1 - let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + let task1 = Async.StartAsTask( memoize.Get(wrapKey 1, computation), cancellationToken = cts.Token) - let key = 1 + waitUntil events (received Started) + cts.Cancel() - let _task1 = Async.StartAsTask( memoize.Get'(1, computation), cancellationToken = cts.Token) + assertTaskCanceled task1 - jobStarted.Wait() - cts.Cancel() - ctsCancelled.Set() + canFinish.Set() |> ignore - check events recorded - [ Requested, key - Started, key - Canceled, key ] + expect events + [ Requested, key + Started, key + Finished, key ] + +[] +let ``We can cancel a job`` () = + + let cts = new CancellationTokenSource() + + let computation = async { + while true do + do! Async.Sleep 1000 } + let memoize = AsyncMemoize<_, int, _>() + let events = observe memoize + + let key = 1 + + let task1 = Async.StartAsTask( memoize.Get(wrapKey 1, computation), cancellationToken = cts.Token) + + waitUntil events (received Started) + + cts.Cancel() + + assertTaskCanceled task1 + + expect events + [ 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 { + jobCanComplete.Wait() + return key * 2 + } - let computation key = async { - jobStarted.Release() |> ignore + let memoize = AsyncMemoize<_, int, _>() + let events = observe memoize - jobCanComplete.Wait() - return key * 2 - } + use cts1 = new CancellationTokenSource() - let memoize = AsyncMemoize<_, int, _>() - let events = record memoize + let key = 1 - use cts1 = new CancellationTokenSource() + let _task1 = Async.StartAsTask( memoize.Get(wrapKey key, computation key), cancellationToken = cts1.Token) - let key = 1 + waitUntil events (received Started) + cts1.Cancel() - let _task1 = Async.StartAsTask( memoize.Get'(key, computation key), cancellationToken = cts1.Token) + waitUntil events (received Canceled) - jobStarted.Wait() - let task2 = Async.StartAsTask( memoize.Get'(key, computation key)) - let task3 = Async.StartAsTask( memoize.Get'(key, computation key)) + let task2 = Async.StartAsTask( memoize.Get(wrapKey key, computation key)) - waitUntil events (countOf Requested 3) + waitUntil events (countOf Started >> (=) 2) - cts1.Cancel() + jobCanComplete.Set() |> ignore - jobCanComplete.Set() |> ignore + Assert.Equal(2, task2.Result) - jobStarted.Wait() + expect events + [ Requested, key + Started, key + Canceled, key + Requested, key + Started, key + Finished, key ] - Assert.Equal(2, task2.Result) - Assert.Equal(2, task3.Result) - - 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 = observe 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) + expect events + [ Requested, key + Started, key + Requested, key + Finished, key ] type ExpectedException() = inherit Exception() @@ -290,7 +318,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 +356,56 @@ 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 = observe 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 + let job = cache.Get(key 11, work) |> Async.StartAsTask - job2started.Wait() + // up til now the jobs should have been running unobserved + let current = eventsWhen events (received Requested) + Assert.Equal(0, current |> countOf Canceled) + + // new request should cancel the unobserved jobs + waitUntil events (received Started) jobCanContinue.Set() |> ignore - - job2finished.Wait() - - if not cancelDuplicate then - job1finished.Wait() - Assert.Equal((2, expectFinished), (started, finished)) + job.Wait() + + let events = eventsWhen events (received Finished) + + 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 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index 6dcd6463cd5..4b5987f65e5 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