Skip to content

Commit 6a5a393

Browse files
committed
Merge pull request #443 from dsyme/fix-mem1
Add ability to downsize caches in response to a max-memory signal
2 parents bf56100 + e9c2f83 commit 6a5a393

File tree

10 files changed

+154
-35
lines changed

10 files changed

+154
-35
lines changed

RELEASE_NOTES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#### 1.4.0.8 -
2+
* FSharpType.Format now prettifies type variables. If necessary, FSharpType.Prettify can also be called
3+
* Add maximum-memory trigger to downsize FCS caches. Defaults to 1.7GB of allocaed memory in the system
4+
process for a 32-bit process, and 2x this for a 64-bit process
5+
16
#### 1.4.0.7 -
27
* fix 427 - Make event information available for properties which represent first-class uses of F#-declared events
38
* fix 410 - Symbols for C# fields (and especially enum fields)

docs/content/caches.fsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Compiler Services: Notes on the FSharpChecker caches
66
77
This is a design note on the FSharpChecker component and its caches. See also the notes on the [FSharpChecker operations queue](queue.html)
88
9-
FSharpChecker maintains a set of caches. These are
9+
Each FSharpChecker object maintains a set of caches. These are
1010
1111
* ``scriptClosureCache`` - an MRU cache of default size ``projectCacheSize`` that caches the
1212
computation of GetProjectOptionsFromScript. This computation can be lengthy as it can involve processing the transative closure
@@ -55,6 +55,25 @@ The sizes of some of these caches can be adjusted by giving parameters to FSharp
5555
the cache sizes above indicate the "strong" size of the cache, where memory is held regardless of the memory
5656
pressure on the system. Some of the caches can also hold "weak" references which can be collected at will by the GC.
5757
58+
> Note: Because of these caches, uou should generally use one global, shared FSharpChecker for everything in an IDE application.
59+
60+
61+
Low-Memory Condition
62+
-------
63+
64+
Version 1.4.0.8 added a "maximum memory" limit specified by the `MaxMemory` property on FSharpChecker (in MB). If an FCS project operation
65+
is performed (see `CheckMaxMemoryReached` in `service.fs`) and `System.GC.GetTotalMemory(false)` reports a figure greater than this, then
66+
the strong sizes of all FCS caches are reduced to either 0 or 1. This happens for the remainder of the lifetime of the FSharpChecker object.
67+
In practice this will still make tools like the Visual Studio F# Power Tools usable, but some operations like renaming across multiple
68+
projects may take substantially longer.
69+
70+
For a 32-bit process the default threshold is 1.7GB of allocated managed memory in a process, see `maxMBDefault` in `service.fs`. For a 64-bit process
71+
it is twice this.
72+
73+
Reducing the FCS strong cache sizes does not guarantee there will be enough memory to continue operations - even holding one project
74+
strongly may exceed a process memory budget. It just means FCS may hold less memory strongly.
75+
76+
If you do not want the maximum memory limit to apply then set MaxMemory to System.Int32.MaxValue.
5877
5978
Summary
6079
-------

src/fsharp/InternalCollections.fs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type internal AgedLookup<'TKey,'TValue when 'TValue : not struct>(keepStrongly:i
2020
/// The choice of order is somewhat arbitrary. If the other way then adding
2121
/// items would be O(1) and removing O(N).
2222
let mutable refs:('TKey*ValueStrength<'TValue>) list = []
23+
let mutable keepStrongly = keepStrongly
2324

2425
// Only set a strong discard function if keepMax is explicitly set to keepStrongly, i.e. there are no weak entries in this lookup.
2526
do assert (onStrongDiscard.IsNone || Some keepStrongly = keepMax)
@@ -30,7 +31,7 @@ type internal AgedLookup<'TKey,'TValue when 'TValue : not struct>(keepStrongly:i
3031
// references. Some operations are O(N) and we don't want to let things get out of
3132
// hand.
3233
let keepMax = defaultArg keepMax 75
33-
let keepMax = max keepStrongly keepMax
34+
let mutable keepMax = max keepStrongly keepMax
3435

3536
/// Look up a the given key, return None if not found.
3637
let TryPeekKeyValueImpl(data,key) =
@@ -140,6 +141,14 @@ type internal AgedLookup<'TKey,'TValue when 'TValue : not struct>(keepStrongly:i
140141
let discards = FilterAndHold()
141142
AssignWithStrength([], discards)
142143

144+
member al.Resize(newKeepStrongly, ?newKeepMax) =
145+
let newKeepMax = defaultArg newKeepMax 75
146+
keepStrongly <- newKeepStrongly
147+
keepMax <- max newKeepStrongly newKeepMax
148+
do assert (onStrongDiscard.IsNone || keepStrongly = keepMax)
149+
let keep = FilterAndHold()
150+
AssignWithStrength(keep, [])
151+
143152

144153

145154
type internal MruCache<'TKey,'TValue when 'TValue : not struct>(keepStrongly, areSame, ?isStillValid : 'TKey*'TValue->bool, ?areSameForSubsumption, ?onStrongDiscard, ?keepMax) =
@@ -178,6 +187,9 @@ type internal MruCache<'TKey,'TValue when 'TValue : not struct>(keepStrongly, ar
178187
member bc.Clear() =
179188
cache.Clear()
180189

190+
member bc.Resize(newKeepStrongly, ?newKeepMax) =
191+
cache.Resize(newKeepStrongly, ?newKeepMax=newKeepMax)
192+
181193
/// List helpers
182194
[<Sealed>]
183195
type internal List =

src/fsharp/InternalCollections.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ namespace Internal.Utilities.Collections
2727
member Remove : key:'TKey -> unit
2828
/// Remove all elements.
2929
member Clear : unit -> unit
30+
/// Resize
31+
member Resize : keepStrongly: int * ?keepMax : int -> unit
3032
3133
/// Simple priority caching for a small number of key\value associations.
3234
/// This cache may age-out results that have been Set by the caller.
@@ -50,6 +52,8 @@ namespace Internal.Utilities.Collections
5052
member Remove : key:'TKey -> unit
5153
/// Set the given key.
5254
member Set : key:'TKey * value:'TValue -> unit
55+
/// Resize
56+
member Resize : keepStrongly: int * ?keepMax : int -> unit
5357

5458
[<Sealed>]
5559
type internal List =

src/fsharp/fsi/fsi.fs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2362,10 +2362,14 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
23622362

23632363
let fsiConsoleInput = FsiConsoleInput(fsi, fsiOptions, inReader, outWriter)
23642364

2365+
/// The single, global interactive checker that can be safely used in conjunction with other operations
2366+
/// on the FsiEvaluationSession.
2367+
let checker = FSharpChecker.Create()
2368+
23652369
let (tcGlobals,frameworkTcImports,nonFrameworkResolutions,unresolvedReferences) =
23662370
try
23672371
let tcConfig = tcConfigP.Get()
2368-
IncrementalFSharpBuild.GetFrameworkTcImports tcConfig
2372+
checker.FrameworkImportsCache.Get tcConfig
23692373
with e ->
23702374
stopProcessingRecovery e range0; failwithf "Error creating evaluation session: %A" e
23712375

@@ -2409,9 +2413,6 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
24092413

24102414
let fsiInteractionProcessor = FsiInteractionProcessor(fsi, tcConfigB, errorLogger, fsiOptions, fsiDynamicCompiler, fsiConsolePrompt, fsiConsoleOutput, fsiInterruptController, fsiStdinLexerProvider, lexResourceManager, initialInteractiveState)
24112415

2412-
/// The single, global interactive checker that can be safely used in conjunction with other operations
2413-
/// on the FsiEvaluationSession.
2414-
let checker = InteractiveChecker.Create()
24152416

24162417
interface IDisposable with
24172418
member x.Dispose() =

src/fsharp/vs/IncrementalBuild.fs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,10 +1087,14 @@ module internal IncrementalFSharpBuild =
10871087

10881088
/// Global service state
10891089
type FrameworkImportsCacheKey = (*resolvedpath*)string list * string * (*ClrRoot*)string list* (*fsharpBinaries*)string
1090-
let private frameworkTcImportsCache = AgedLookup<FrameworkImportsCacheKey,(TcGlobals * TcImports)>(8, areSame=(fun (x,y) -> x = y))
1091-
let ClearFrameworkTcImportsCache() = frameworkTcImportsCache.Clear()
1092-
/// This function strips the "System" assemblies from the tcConfig and returns a age-cached TcImports for them.
1093-
let GetFrameworkTcImports(tcConfig:TcConfig) =
1090+
1091+
type FrameworkImportsCache(keepStrongly) =
1092+
let frameworkTcImportsCache = AgedLookup<FrameworkImportsCacheKey,(TcGlobals * TcImports)>(keepStrongly, areSame=(fun (x,y) -> x = y))
1093+
member __.Downsize() = frameworkTcImportsCache.Resize(keepStrongly=0)
1094+
member __.Clear() = frameworkTcImportsCache.Clear()
1095+
1096+
/// This function strips the "System" assemblies from the tcConfig and returns a age-cached TcImports for them.
1097+
member __.Get(tcConfig:TcConfig) =
10941098
// Split into installed and not installed.
10951099
let frameworkDLLs,nonFrameworkResolutions,unresolved = TcAssemblyResolutions.SplitNonFoundationalResolutions(tcConfig)
10961100
let frameworkDLLsKey =
@@ -1188,7 +1192,7 @@ module internal IncrementalFSharpBuild =
11881192
TimeStamp = timestamp }
11891193

11901194

1191-
type IncrementalBuilder(tcConfig : TcConfig, projectDirectory, outfile, assemblyName, niceNameGen : Ast.NiceNameGenerator, lexResourceManager,
1195+
type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig : TcConfig, projectDirectory, outfile, assemblyName, niceNameGen : Ast.NiceNameGenerator, lexResourceManager,
11921196
sourceFiles, projectReferences: IProjectReference list, ensureReactive,
11931197
keepAssemblyContents, keepAllBackgroundResolutions) =
11941198

@@ -1202,7 +1206,7 @@ module internal IncrementalFSharpBuild =
12021206
// Resolve assemblies and create the framework TcImports. This is done when constructing the
12031207
// builder itself, rather than as an incremental task. This caches a level of "system" references. No type providers are
12041208
// included in these references.
1205-
let (tcGlobals,frameworkTcImports,nonFrameworkResolutions,unresolvedReferences) = GetFrameworkTcImports tcConfig
1209+
let (tcGlobals,frameworkTcImports,nonFrameworkResolutions,unresolvedReferences) = frameworkTcImportsCache.Get tcConfig
12061210

12071211
// Check for the existence of loaded sources and prepend them to the sources list if present.
12081212
let sourceFiles = tcConfig.GetAvailableLoadedSources() @ (sourceFiles |>List.map(fun s -> rangeStartup,s))
@@ -1723,7 +1727,7 @@ module internal IncrementalFSharpBuild =
17231727

17241728
/// CreateIncrementalBuilder (for background type checking). Note that fsc.fs also
17251729
/// creates an incremental builder used by the command line compiler.
1726-
static member TryCreateBackgroundBuilderForProjectOptions (scriptClosureOptions:LoadClosure option, sourceFiles:string list, commandLineArgs:string list, projectReferences, projectDirectory, useScriptResolutionRules, isIncompleteTypeCheckEnvironment, keepAssemblyContents, keepAllBackgroundResolutions) =
1730+
static member TryCreateBackgroundBuilderForProjectOptions (frameworkTcImportsCache, scriptClosureOptions:LoadClosure option, sourceFiles:string list, commandLineArgs:string list, projectReferences, projectDirectory, useScriptResolutionRules, isIncompleteTypeCheckEnvironment, keepAssemblyContents, keepAllBackgroundResolutions) =
17271731

17281732
// Trap and report warnings and errors from creation.
17291733
use errorScope = new ErrorScope()
@@ -1800,7 +1804,8 @@ module internal IncrementalFSharpBuild =
18001804
let outfile, _, assemblyName = tcConfigB.DecideNames sourceFilesNew
18011805

18021806
let builder =
1803-
new IncrementalBuilder(tcConfig, projectDirectory, outfile, assemblyName, niceNameGen,
1807+
new IncrementalBuilder(frameworkTcImportsCache,
1808+
tcConfig, projectDirectory, outfile, assemblyName, niceNameGen,
18041809
resourceManager, sourceFilesNew, projectReferences, ensureReactive=true,
18051810
keepAssemblyContents=keepAssemblyContents,
18061811
keepAllBackgroundResolutions=keepAllBackgroundResolutions)

src/fsharp/vs/IncrementalBuild.fsi

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ type internal ErrorScope =
4949
module internal IncrementalFSharpBuild =
5050

5151
/// Lookup the global static cache for building the FrameworkTcImports
52-
val GetFrameworkTcImports : TcConfig -> TcGlobals * TcImports * AssemblyResolution list * UnresolvedAssemblyReference list
53-
val ClearFrameworkTcImportsCache: unit -> unit
52+
type FrameworkImportsCache =
53+
new : size: int -> FrameworkImportsCache
54+
member Get : TcConfig -> TcGlobals * TcImports * AssemblyResolution list * UnresolvedAssemblyReference list
55+
member Clear: unit -> unit
56+
member Downsize: unit -> unit
5457
5558
type PartialCheckResults =
5659
{ TcState : TcState
@@ -149,7 +152,7 @@ module internal IncrementalFSharpBuild =
149152
/// This may be a marginally long-running operation (parses are relatively quick, only one file needs to be parsed)
150153
member GetParseResultsForFile : filename:string -> Ast.ParsedInput option * Range.range * string * (PhasedError * FSharpErrorSeverity) list
151154

152-
static member TryCreateBackgroundBuilderForProjectOptions : scriptClosureOptions:LoadClosure option * sourceFiles:string list * commandLineArgs:string list * projectReferences: IProjectReference list * projectDirectory:string * useScriptResolutionRules:bool * isIncompleteTypeCheckEnvironment : bool * keepAssemblyContents: bool * keepAllBackgroundResolutions: bool -> IncrementalBuilder option * FSharpErrorInfo list
155+
static member TryCreateBackgroundBuilderForProjectOptions : FrameworkImportsCache * scriptClosureOptions:LoadClosure option * sourceFiles:string list * commandLineArgs:string list * projectReferences: IProjectReference list * projectDirectory:string * useScriptResolutionRules:bool * isIncompleteTypeCheckEnvironment : bool * keepAssemblyContents: bool * keepAllBackgroundResolutions: bool -> IncrementalBuilder option * FSharpErrorInfo list
153156

154157
[<Obsolete("This type has been renamed to FSharpErrorInfo")>]
155158
/// Renamed to FSharpErrorInfo

0 commit comments

Comments
 (0)