Skip to content

Commit b532344

Browse files
committed
add memory threshold
1 parent fba477a commit b532344

File tree

8 files changed

+103
-31
lines changed

8 files changed

+103
-31
lines changed

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2362,10 +2362,11 @@ type FsiEvaluationSession (fsi: FsiEvaluationSessionHostConfig, argv:string[], i
23622362

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

2365+
let frameworkImportsCache = IncrementalFSharpBuild.FrameworkImportsCache(1)
23652366
let (tcGlobals,frameworkTcImports,nonFrameworkResolutions,unresolvedReferences) =
23662367
try
23672368
let tcConfig = tcConfigP.Get()
2368-
IncrementalFSharpBuild.GetFrameworkTcImports tcConfig
2369+
frameworkImportsCache.Get tcConfig
23692370
with e ->
23702371
stopProcessingRecovery e range0; failwithf "Error creating evaluation session: %A" e
23712372

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

src/fsharp/vs/service.fs

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ module EnvMisc =
6161
let incrementalTypeCheckCacheSize = GetEnvInteger "mFSharp_IncrementalTypeCheckCacheSize" 5
6262

6363
let projectCacheSizeDefault = GetEnvInteger "mFSharp_ProjectCacheSizeDefault" 3
64+
let frameworkTcImportsCacheStrongSize = GetEnvInteger "mFSharp_frameworkTcImportsCacheStrongSizeDefault" 8
65+
let maxMBDefault = GetEnvInteger "mFSharp_maxMB" 2000
6466

6567
//----------------------------------------------------------------------------
6668
// Methods
@@ -2184,6 +2186,8 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
21842186
areSame=FSharpProjectOptions.AreSameForChecking,
21852187
areSameForSubsumption=FSharpProjectOptions.AreSubsumable)
21862188

2189+
let frameworkTcImportsCache = IncrementalFSharpBuild.FrameworkImportsCache(frameworkTcImportsCacheStrongSize)
2190+
21872191
/// CreateOneIncrementalBuilder (for background type checking). Note that fsc.fs also
21882192
/// creates an incremental builder used by the command line compiler.
21892193
let CreateOneIncrementalBuilder (options:FSharpProjectOptions) =
@@ -2200,7 +2204,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
22002204

22012205
let builderOpt, errorsAndWarnings =
22022206
IncrementalFSharpBuild.IncrementalBuilder.TryCreateBackgroundBuilderForProjectOptions
2203-
(scriptClosureCache.TryGet options, Array.toList options.ProjectFileNames,
2207+
(frameworkTcImportsCache, scriptClosureCache.TryGet options, Array.toList options.ProjectFileNames,
22042208
Array.toList options.OtherOptions, projectReferences, options.ProjectDirectory,
22052209
options.UseScriptResolutionRules, options.IsIncompleteTypeCheckEnvironment, keepAssemblyContents, keepAllBackgroundResolutions)
22062210

@@ -2468,7 +2472,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24682472
// including by SetAlternate.
24692473
let builderB, errorsB, decrementB = CreateOneIncrementalBuilder options
24702474
incrementalBuildersCache.Set(options, (builderB, errorsB, decrementB))
2471-
bc.StartBackgroundCompile(options)
2475+
//bc.StartBackgroundCompile(options)
24722476

24732477
member bc.NotifyProjectCleaned(options : FSharpProjectOptions) =
24742478
match incrementalBuildersCache.TryGetAny options with
@@ -2482,9 +2486,6 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24822486
()
24832487
#endif
24842488

2485-
member bc.InvalidateAll() =
2486-
reactor.EnqueueOp (fun () -> incrementalBuildersCache.Clear())
2487-
24882489
member bc.StartBackgroundCompile(options) =
24892490
reactor.StartBackgroundOp(fun () ->
24902491
let builderOpt,_,_ = getOrCreateBuilder options
@@ -2506,6 +2507,18 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
25062507

25072508
member bc.CurrentQueueLength = reactor.CurrentQueueLength
25082509

2510+
member bc.ClearCaches() =
2511+
reactor.EnqueueOp (fun () ->
2512+
incrementalBuildersCache.Clear()
2513+
frameworkTcImportsCache.Clear()
2514+
scriptClosureCache.Clear())
2515+
2516+
member bc.DownsizeCaches() =
2517+
reactor.EnqueueOp (fun () ->
2518+
incrementalBuildersCache.Resize(keepStrongly=1, keepMax=1)
2519+
frameworkTcImportsCache.Downsize()
2520+
scriptClosureCache.Resize(keepStrongly=1, keepMax=1))
2521+
25092522

25102523
#if SILVERLIGHT
25112524
#else
@@ -2937,6 +2950,11 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
29372950
areSame=AreSameForChecking3,
29382951
areSameForSubsumption=AreSubsumable3)
29392952

2953+
let mutable downsizedCaches = false
2954+
let mutable maxMB = maxMBDefault
2955+
let maxMemEvent = new Event<unit>()
2956+
2957+
29402958
static member Create() =
29412959
new FSharpChecker(projectCacheSizeDefault,false,true)
29422960

@@ -2959,6 +2977,7 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
29592977

29602978
member ic.ParseFileInProject(filename, source, options) =
29612979
async {
2980+
ic.CheckDownsizeCaches()
29622981
match parseFileInProjectCache.TryGet (filename, source, options) with
29632982
| Some res -> return res
29642983
| None ->
@@ -2983,23 +3002,36 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
29833002
/// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation.
29843003
/// For example, the type provider approvals file may have changed.
29853004
member ic.InvalidateAll() =
2986-
backgroundCompiler.InvalidateAll()
3005+
ic.ClearCaches()
29873006

3007+
member ic.ClearCaches() =
3008+
parseAndCheckFileInProjectCachePossiblyStale.Clear()
3009+
parseAndCheckFileInProjectCache.Clear()
3010+
braceMatchCache.Clear()
3011+
parseFileInProjectCache.Clear()
3012+
backgroundCompiler.ClearCaches()
3013+
3014+
member ic.CheckDownsizeCaches() =
3015+
if not downsizedCaches && System.GC.GetTotalMemory(false) > int64 maxMB * 1024L * 1024L then
3016+
// If the maxMB limit is reached, drastic action is taken
3017+
// - reduce strong cache sizes to a minimum
3018+
downsizedCaches <- true
3019+
parseAndCheckFileInProjectCachePossiblyStale.Resize(keepStrongly=1)
3020+
parseAndCheckFileInProjectCache.Resize(keepStrongly=1)
3021+
braceMatchCache.Resize(keepStrongly=1)
3022+
parseFileInProjectCache.Resize(keepStrongly=1)
3023+
backgroundCompiler.DownsizeCaches()
3024+
maxMemEvent.Trigger( () )
3025+
29883026
/// This function is called when the entire environment is known to have changed for reasons not encoded in the ProjectOptions of any project/compilation.
29893027
/// For example, the type provider approvals file may have changed.
29903028
//
29913029
// This is for unit testing only
29923030
member ic.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() =
2993-
ic.InvalidateAll()
2994-
parseAndCheckFileInProjectCachePossiblyStale.Clear()
2995-
parseAndCheckFileInProjectCache.Clear()
2996-
braceMatchCache.Clear()
2997-
parseFileInProjectCache.Clear()
2998-
IncrementalFSharpBuild.ClearFrameworkTcImportsCache()
2999-
for i in 0 .. 2 do
3000-
System.GC.Collect()
3001-
System.GC.WaitForPendingFinalizers()
3002-
backgroundCompiler.WaitForBackgroundCompile() // flush AsyncOp
3031+
ic.ClearCaches()
3032+
System.GC.Collect()
3033+
System.GC.WaitForPendingFinalizers()
3034+
backgroundCompiler.WaitForBackgroundCompile() // flush AsyncOp
30033035

30043036
/// This function is called when the configuration is known to have changed for reasons not encoded in the ProjectOptions.
30053037
/// For example, dependent references may have been deleted or created.
@@ -3014,7 +3046,8 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
30143046
match checkAnswer with
30153047
| None
30163048
| Some FSharpCheckFileAnswer.Aborted ->
3017-
backgroundCompiler.StartBackgroundCompile(options)
3049+
//backgroundCompiler.StartBackgroundCompile(options)
3050+
()
30183051
| Some (FSharpCheckFileAnswer.Succeeded typedResults) ->
30193052
foregroundTypeCheckCount <- foregroundTypeCheckCount + 1
30203053
parseAndCheckFileInProjectCachePossiblyStale.Set((filename,options),(parseResults,typedResults,fileVersion))
@@ -3035,6 +3068,7 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
30353068
member ic.CheckFileInProject(parseResults:FSharpParseFileResults, filename:string, fileVersion:int, source:string, options:FSharpProjectOptions, ?isResultObsolete, ?textSnapshotInfo:obj) =
30363069
let (IsResultObsolete(isResultObsolete)) = defaultArg isResultObsolete (IsResultObsolete(fun _ -> false))
30373070
async {
3071+
ic.CheckDownsizeCaches()
30383072
let! checkAnswer = backgroundCompiler.CheckFileInProject(parseResults,filename,source,options,isResultObsolete,textSnapshotInfo)
30393073
ic.RecordTypeCheckFileInProjectResults(filename,options,parseResults,fileVersion,Some checkAnswer,source)
30403074
return checkAnswer
@@ -3046,13 +3080,15 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
30463080
let cachedResults = parseAndCheckFileInProjectCache.TryGet((filename,source,options))
30473081
let (IsResultObsolete(isResultObsolete)) = defaultArg isResultObsolete (IsResultObsolete(fun _ -> false))
30483082
async {
3083+
ic.CheckDownsizeCaches()
30493084
let! parseResults, checkAnswer, usedCachedResults = backgroundCompiler.ParseAndCheckFileInProject(filename,source,options,isResultObsolete,textSnapshotInfo,cachedResults)
30503085
if not usedCachedResults then
30513086
ic.RecordTypeCheckFileInProjectResults(filename,options,parseResults,fileVersion,Some checkAnswer,source)
30523087
return (parseResults, checkAnswer)
30533088
}
30543089

30553090
member ic.ParseAndCheckProject(options) =
3091+
ic.CheckDownsizeCaches()
30563092
backgroundCompiler.ParseAndCheckProject(options)
30573093

30583094
/// For a given script file, get the ProjectOptions implied by the #load closure
@@ -3141,6 +3177,9 @@ type FSharpChecker(projectCacheSize, keepAssemblyContents, keepAllBackgroundReso
31413177

31423178
member ic.FileTypeCheckStateIsDirty = backgroundCompiler.BeforeBackgroundFileCheck
31433179

3180+
member ic.MaxMemoryReached = maxMemEvent.Publish
3181+
member ic.MaxMemory with get() = maxMB and set v = maxMB <- v
3182+
31443183
static member Instance = globalInstance
31453184

31463185
type FsiInteractiveChecker(reactorOps: IReactorOperations, tcConfig, tcGlobals, tcImports, tcState, loadClosure) =

0 commit comments

Comments
 (0)