diff --git a/src/fsharp/CompileOps.fs b/src/fsharp/CompileOps.fs index 12b2d09f6d5..b7a49c8326e 100644 --- a/src/fsharp/CompileOps.fs +++ b/src/fsharp/CompileOps.fs @@ -3837,10 +3837,19 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti referencedAssemblies = [| for r in resolutions.GetAssemblyResolutions() -> r.resolvedPath |] temporaryFolder = Path.GetTempPath() } + // The type provider should not hold strong references to disposed + // TcImport objects. So the callbacks provided in the type provider config + // dispatch via a thunk which gets set to a non-resource-capturing + // failing function when the object is disposed. + let systemRuntimeContainsType = + let systemRuntimeContainsTypeRef = ref tcImports.SystemRuntimeContainsType + tcImports.AttachDisposeAction(fun () -> systemRuntimeContainsTypeRef := (fun _ -> raise (System.ObjectDisposedException("The type provider has been disposed")))) + fun arg -> systemRuntimeContainsTypeRef.Value arg + let providers = [ for assemblyName in providerAssemblies do yield ExtensionTyping.GetTypeProvidersOfAssembly(fileNameOfRuntimeAssembly, ilScopeRefOfRuntimeAssembly, assemblyName, typeProviderEnvironment, - tcConfig.isInvalidationSupported, tcConfig.isInteractive, tcImports.SystemRuntimeContainsType, systemRuntimeAssemblyVersion, m) ] + tcConfig.isInvalidationSupported, tcConfig.isInteractive, systemRuntimeContainsType, systemRuntimeAssemblyVersion, m) ] let providers = providers |> List.concat // Note, type providers are disposable objects. The TcImports owns the provider objects - when/if it is disposed, the providers are disposed. diff --git a/vsintegration/src/unittests/Resources.MockTypeProviders/DummyProviderForLanguageServiceTesting/DummyProviderForLanguageServiceTesting.fs b/vsintegration/src/unittests/Resources.MockTypeProviders/DummyProviderForLanguageServiceTesting/DummyProviderForLanguageServiceTesting.fs index 306444e2cac..32d7b667def 100644 --- a/vsintegration/src/unittests/Resources.MockTypeProviders/DummyProviderForLanguageServiceTesting/DummyProviderForLanguageServiceTesting.fs +++ b/vsintegration/src/unittests/Resources.MockTypeProviders/DummyProviderForLanguageServiceTesting/DummyProviderForLanguageServiceTesting.fs @@ -115,15 +115,25 @@ module internal TPModule = module GlobalCounters = let mutable creations = 0 let mutable disposals = 0 + let mutable configs = ([]: TypeProviderConfig list) let GetTotalCreations() = creations let GetTotalDisposals() = disposals + let CheckAllConfigsDisposed() = + for c in configs do + try + c.SystemRuntimeContainsType("System.Object") |> ignore + failwith "expected configuration object to be disposed" + with :? System.ObjectDisposedException -> + () + [] -type HelloWorldProvider() = +type HelloWorldProvider(config: TypeProviderConfig) = inherit TypeProviderForNamespaces(TPModule.namespaceName,TPModule.types) do GlobalCounters.creations <- GlobalCounters.creations + 1 let mutable disposed = false + do GlobalCounters.configs <- config :: GlobalCounters.configs interface System.IDisposable with member x.Dispose() = System.Diagnostics.Debug.Assert(not disposed) diff --git a/vsintegration/src/unittests/Tests.LanguageService.Script.fs b/vsintegration/src/unittests/Tests.LanguageService.Script.fs index ae7cdaaa73f..63089465ded 100644 --- a/vsintegration/src/unittests/Tests.LanguageService.Script.fs +++ b/vsintegration/src/unittests/Tests.LanguageService.Script.fs @@ -1628,6 +1628,8 @@ type ScriptTests() as this = Assert.IsNotNull(totalCreationsMeth, "totalCreationsMeth should not be null") let totalDisposalsMeth = providerCounters.GetMethod("GetTotalDisposals") Assert.IsNotNull(totalDisposalsMeth, "totalDisposalsMeth should not be null") + let checkConfigsMeth = providerCounters.GetMethod("CheckAllConfigsDisposed") + Assert.IsNotNull(checkConfigsMeth, "checkConfigsMeth should not be null") let providerCounters2 = providerAssembly.GetType("Microsoft.FSharp.TypeProvider.Emit.GlobalCountersForInvalidation") Assert.IsNotNull(providerCounters2, "provider counters #2 module should not be null") @@ -1638,6 +1640,7 @@ type ScriptTests() as this = let totalCreations() = totalCreationsMeth.Invoke(null, [| |]) :?> int let totalDisposals() = totalDisposalsMeth.Invoke(null, [| |]) :?> int + let checkConfigsDisposed() = checkConfigsMeth.Invoke(null, [| |]) |> ignore let totalInvaldiationHandlersAdded() = totalInvaldiationHandlersAddedMeth.Invoke(null, [| |]) :?> int let totalInvaldiationHandlersRemoved() = totalInvaldiationHandlersRemovedMeth.Invoke(null, [| |]) :?> int @@ -1693,6 +1696,7 @@ type ScriptTests() as this = ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients(this.VS) Assert.IsTrue(countDisposals() = 50, "Check6b, at end, countDisposals() = 50 after explicit clearing") Assert.IsTrue(countInvaldiationHandlersAdded() - countInvaldiationHandlersRemoved() = 0, "Check6b2, at end, all invalidation handlers removed after explicit cleraring") + checkConfigsDisposed() [] []