Skip to content

Commit 225b990

Browse files
dsymelatkin
authored andcommitted
Second half of fix to dotnet/fsharp#106
FSharp.Compiler.Service contained a more complete fix for dotnet/fsharp#106, which relates to the memory leaks that can arise if type provider instance objects are kept live. Historically the memory leaks have happened if a type provider registers itself to listen to file system events (or other notifications), but for some reason doesn't disconnect those listeners when the type provider is disposed. This keeps the type provider instance alive - which is a bug in the type provider - but even worse it can keep '00s MB of data associated with the compiler state alive as well. The missing part of the fix is to disconnect the "TypeProviderConfig" callback when the type providers are disposed, so that even if a type provider instance hangs on to a reference to the TypeProviderConfig, that doesn't cause the entire object graph for the compilation session to be retained in memory. After type provider disposal, any TypeProviderConfig objects provided to the type provider will now raise ObjectDisposedException when the SystemRuntimeContainsType method is called. closes dotnet/fsharp#591 fixes dotnet/fsharp#106 commit d3b2f0e1b7f117de84feadcddc085a3cba307c09 Author: Don Syme <donsyme@fastmail.fm> Date: Fri Aug 14 14:35:53 2015 +0100 testing for 2nd half of 106 commit adebe18e0291935d1b7eebd362abfef4b2dacc49 Author: Don Syme <donsyme@fastmail.fm> Date: Fri Aug 14 14:09:51 2015 +0100 Second half of fix to dotnet/fsharp#106
1 parent 9157d60 commit 225b990

File tree

3 files changed

+25
-2
lines changed

3 files changed

+25
-2
lines changed

src/fsharp/CompileOps.fs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3837,10 +3837,19 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
38373837
referencedAssemblies = [| for r in resolutions.GetAssemblyResolutions() -> r.resolvedPath |]
38383838
temporaryFolder = Path.GetTempPath() }
38393839

3840+
// The type provider should not hold strong references to disposed
3841+
// TcImport objects. So the callbacks provided in the type provider config
3842+
// dispatch via a thunk which gets set to a non-resource-capturing
3843+
// failing function when the object is disposed.
3844+
let systemRuntimeContainsType =
3845+
let systemRuntimeContainsTypeRef = ref tcImports.SystemRuntimeContainsType
3846+
tcImports.AttachDisposeAction(fun () -> systemRuntimeContainsTypeRef := (fun _ -> raise (System.ObjectDisposedException("The type provider has been disposed"))))
3847+
fun arg -> systemRuntimeContainsTypeRef.Value arg
3848+
38403849
let providers =
38413850
[ for assemblyName in providerAssemblies do
38423851
yield ExtensionTyping.GetTypeProvidersOfAssembly(fileNameOfRuntimeAssembly, ilScopeRefOfRuntimeAssembly, assemblyName, typeProviderEnvironment,
3843-
tcConfig.isInvalidationSupported, tcConfig.isInteractive, tcImports.SystemRuntimeContainsType, systemRuntimeAssemblyVersion, m) ]
3852+
tcConfig.isInvalidationSupported, tcConfig.isInteractive, systemRuntimeContainsType, systemRuntimeAssemblyVersion, m) ]
38443853
let providers = providers |> List.concat
38453854

38463855
// Note, type providers are disposable objects. The TcImports owns the provider objects - when/if it is disposed, the providers are disposed.

vsintegration/src/unittests/Resources.MockTypeProviders/DummyProviderForLanguageServiceTesting/DummyProviderForLanguageServiceTesting.fs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,25 @@ module internal TPModule =
115115
module GlobalCounters =
116116
let mutable creations = 0
117117
let mutable disposals = 0
118+
let mutable configs = ([]: TypeProviderConfig list)
118119
let GetTotalCreations() = creations
119120
let GetTotalDisposals() = disposals
121+
let CheckAllConfigsDisposed() =
122+
for c in configs do
123+
try
124+
c.SystemRuntimeContainsType("System.Object") |> ignore
125+
failwith "expected configuration object to be disposed"
126+
with :? System.ObjectDisposedException ->
127+
()
128+
120129

121130

122131
[<TypeProvider>]
123-
type HelloWorldProvider() =
132+
type HelloWorldProvider(config: TypeProviderConfig) =
124133
inherit TypeProviderForNamespaces(TPModule.namespaceName,TPModule.types)
125134
do GlobalCounters.creations <- GlobalCounters.creations + 1
126135
let mutable disposed = false
136+
do GlobalCounters.configs <- config :: GlobalCounters.configs
127137
interface System.IDisposable with
128138
member x.Dispose() =
129139
System.Diagnostics.Debug.Assert(not disposed)

vsintegration/src/unittests/Tests.LanguageService.Script.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,8 @@ type ScriptTests() as this =
16281628
Assert.IsNotNull(totalCreationsMeth, "totalCreationsMeth should not be null")
16291629
let totalDisposalsMeth = providerCounters.GetMethod("GetTotalDisposals")
16301630
Assert.IsNotNull(totalDisposalsMeth, "totalDisposalsMeth should not be null")
1631+
let checkConfigsMeth = providerCounters.GetMethod("CheckAllConfigsDisposed")
1632+
Assert.IsNotNull(checkConfigsMeth, "checkConfigsMeth should not be null")
16311633

16321634
let providerCounters2 = providerAssembly.GetType("Microsoft.FSharp.TypeProvider.Emit.GlobalCountersForInvalidation")
16331635
Assert.IsNotNull(providerCounters2, "provider counters #2 module should not be null")
@@ -1638,6 +1640,7 @@ type ScriptTests() as this =
16381640

16391641
let totalCreations() = totalCreationsMeth.Invoke(null, [| |]) :?> int
16401642
let totalDisposals() = totalDisposalsMeth.Invoke(null, [| |]) :?> int
1643+
let checkConfigsDisposed() = checkConfigsMeth.Invoke(null, [| |]) |> ignore
16411644
let totalInvaldiationHandlersAdded() = totalInvaldiationHandlersAddedMeth.Invoke(null, [| |]) :?> int
16421645
let totalInvaldiationHandlersRemoved() = totalInvaldiationHandlersRemovedMeth.Invoke(null, [| |]) :?> int
16431646

@@ -1693,6 +1696,7 @@ type ScriptTests() as this =
16931696
ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients(this.VS)
16941697
Assert.IsTrue(countDisposals() = 50, "Check6b, at end, countDisposals() = 50 after explicit clearing")
16951698
Assert.IsTrue(countInvaldiationHandlersAdded() - countInvaldiationHandlersRemoved() = 0, "Check6b2, at end, all invalidation handlers removed after explicit cleraring")
1699+
checkConfigsDisposed()
16961700

16971701
[<Test>]
16981702
[<Category("TypeProvider")>]

0 commit comments

Comments
 (0)