Skip to content

Commit fe2973e

Browse files
TIHanbaronfel
authored andcommitted
Removed reference counting from IncrementalBuilder; now relies on GC. (#6863)
* TcImports doesn't explicitly have to be disposed, relies on GC. Removed reference counting in incremental builder. Builders can only be invalidated by a type provider, not anything else. * Removing any notion of being incremental builder reference counting and being alive * Added ITypeProviderThread * Fixed declaration list item * Changed EnqueueWorkAndWait to just EnqueueWork that's not blocking * Minor cleanup * Forgot to check disposed * Quick update on comment * Update FCS project * Renamed ITypeProviderThread to ICompilationThread * Changes from feedback and added a test
1 parent 07fe340 commit fe2973e

File tree

11 files changed

+213
-251
lines changed

11 files changed

+213
-251
lines changed

fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<PropertyGroup>
33
<FSharpSourcesRoot>$(MSBuildProjectDirectory)\..\..\src</FSharpSourcesRoot>
44
</PropertyGroup>
5+
<Project Sdk="Microsoft.NET.Sdk">
56
<Import Project="..\netfx.props" />
67
<Import Project="..\..\src\buildtools\buildtools.targets" />
78
<PropertyGroup>
@@ -546,18 +547,18 @@
546547
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fs">
547548
<Link>Symbols/SymbolPatterns.fs</Link>
548549
</Compile>
549-
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\IncrementalBuild.fsi">
550-
<Link>Service/IncrementalBuild.fsi</Link>
551-
</Compile>
552-
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\IncrementalBuild.fs">
553-
<Link>Service/IncrementalBuild.fs</Link>
554-
</Compile>
555550
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\Reactor.fsi">
556551
<Link>Service/Reactor.fsi</Link>
557552
</Compile>
558553
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\Reactor.fs">
559554
<Link>Service/Reactor.fs</Link>
560555
</Compile>
556+
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\IncrementalBuild.fsi">
557+
<Link>Service/IncrementalBuild.fsi</Link>
558+
</Compile>
559+
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\IncrementalBuild.fs">
560+
<Link>Service/IncrementalBuild.fs</Link>
561+
</Compile>
561562
<Compile Include="$(FSharpSourcesRoot)\fsharp\service\ServiceConstants.fs">
562563
<Link>Service/ServiceConstants.fs</Link>
563564
</Compile>

src/fsharp/CompileOps.fs

Lines changed: 105 additions & 37 deletions
Large diffs are not rendered by default.

src/fsharp/CompileOps.fsi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,14 @@ type UnresolvedAssemblyReference = UnresolvedAssemblyReference of string * Assem
209209
type ResolvedExtensionReference = ResolvedExtensionReference of string * AssemblyReference list * Tainted<ITypeProvider> list
210210
#endif
211211

212+
/// The thread in which compilation calls will be enqueued and done work on.
213+
/// Note: This is currently only used when disposing of type providers and will be extended to all the other type provider calls when compilations can be done in parallel.
214+
/// Right now all calls in FCS to type providers are single-threaded through use of the reactor thread.
215+
type ICompilationThread =
216+
217+
/// Enqueue work to be done on a compilation thread.
218+
abstract EnqueueWork: (CompilationThreadToken -> unit) -> unit
219+
212220
[<RequireQualifiedAccess>]
213221
type CompilerTarget =
214222
| WinExe
@@ -351,6 +359,7 @@ type TcConfigBuilder =
351359
#if !NO_EXTENSIONTYPING
352360
mutable showExtensionTypeMessages: bool
353361
#endif
362+
mutable compilationThread: ICompilationThread
354363
mutable pause: bool
355364
mutable alwaysCallVirt: bool
356365
mutable noDebugData: bool
@@ -510,6 +519,7 @@ type TcConfig =
510519
#if !NO_EXTENSIONTYPING
511520
member showExtensionTypeMessages: bool
512521
#endif
522+
member compilationThread: ICompilationThread
513523
member pause: bool
514524
member alwaysCallVirt: bool
515525
member noDebugData: bool
@@ -591,6 +601,8 @@ type TcAssemblyResolutions =
591601
static member BuildFromPriorResolutions : CompilationThreadToken * TcConfig * AssemblyResolution list * UnresolvedAssemblyReference list -> TcAssemblyResolutions
592602

593603
/// Represents a table of imported assemblies with their resolutions.
604+
/// Is a disposable object, but it is recommended not to explicitly call Dispose unless you absolutely know nothing will be using its contents after the disposal.
605+
/// Otherwise, simply allow the GC to collect this and it will properly call Dispose from the finalizer.
594606
[<Sealed>]
595607
type TcImports =
596608
interface System.IDisposable

src/fsharp/service/IncrementalBuild.fs

Lines changed: 21 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,11 +1211,14 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
12111211
keepAssemblyContents, keepAllBackgroundResolutions, maxTimeShareMilliseconds) =
12121212

12131213
let tcConfigP = TcConfigProvider.Constant tcConfig
1214-
let importsInvalidated = new Event<string>()
12151214
let fileParsed = new Event<string>()
12161215
let beforeFileChecked = new Event<string>()
12171216
let fileChecked = new Event<string>()
12181217
let projectChecked = new Event<unit>()
1218+
#if !NO_EXTENSIONTYPING
1219+
let importsInvalidatedByTypeProvider = new Event<string>()
1220+
#endif
1221+
let mutable currentTcImportsOpt = None
12191222

12201223
// Check for the existence of loaded sources and prepend them to the sources list if present.
12211224
let sourceFiles = tcConfig.GetAvailableLoadedSources() @ (sourceFiles |>List.map (fun s -> rangeStartup, s))
@@ -1242,42 +1245,19 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
12421245
for (_, f, _) in sourceFiles do
12431246
yield f |]
12441247

1245-
// The IncrementalBuilder needs to hold up to one item that needs to be disposed, which is the tcImports for the incremental
1246-
// build.
1247-
let mutable cleanupItem = None: TcImports option
1248-
let disposeCleanupItem() =
1249-
match cleanupItem with
1250-
| None -> ()
1251-
| Some item ->
1252-
cleanupItem <- None
1253-
dispose item
1254-
1255-
let setCleanupItem x =
1256-
assert cleanupItem.IsNone
1257-
cleanupItem <- Some x
1258-
1259-
let mutable disposed = false
1260-
let assertNotDisposed() =
1261-
if disposed then
1262-
System.Diagnostics.Debug.Assert(false, "IncrementalBuild object has already been disposed!")
1263-
1264-
let mutable referenceCount = 0
1265-
12661248
//----------------------------------------------------
12671249
// START OF BUILD TASK FUNCTIONS
12681250

12691251
/// This is a build task function that gets placed into the build rules as the computation for a VectorStamp
12701252
///
12711253
/// Get the timestamp of the given file name.
12721254
let StampFileNameTask (cache: TimeStampCache) _ctok (_m: range, filename: string, _isLastCompiland) =
1273-
assertNotDisposed()
12741255
cache.GetFileTimeStamp filename
12751256

12761257
/// This is a build task function that gets placed into the build rules as the computation for a VectorMap
12771258
///
12781259
/// Parse the given file and return the given input.
12791260
let ParseTask ctok (sourceRange: range, filename: string, isLastCompiland) =
1280-
assertNotDisposed()
12811261
DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok
12821262

12831263
let errorLogger = CompilationErrorLogger("ParseTask", tcConfig.errorSeverityOptions)
@@ -1300,7 +1280,6 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
13001280
///
13011281
/// Timestamps of referenced assemblies are taken from the file's timestamp.
13021282
let StampReferencedAssemblyTask (cache: TimeStampCache) ctok (_ref, timeStamper) =
1303-
assertNotDisposed()
13041283
timeStamper cache ctok
13051284

13061285

@@ -1309,18 +1288,13 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
13091288
// Link all the assemblies together and produce the input typecheck accumulator
13101289
let CombineImportedAssembliesTask ctok _ : Cancellable<TypeCheckAccumulator> =
13111290
cancellable {
1312-
assertNotDisposed()
13131291
let errorLogger = CompilationErrorLogger("CombineImportedAssembliesTask", tcConfig.errorSeverityOptions)
13141292
// Return the disposable object that cleans up
13151293
use _holder = new CompilationGlobalsScope(errorLogger, BuildPhase.Parameter)
13161294

13171295
let! tcImports =
13181296
cancellable {
13191297
try
1320-
// We dispose any previous tcImports, for the case where a dependency changed which caused this part
1321-
// of the partial build to be re-evaluated.
1322-
disposeCleanupItem()
1323-
13241298
let! tcImports = TcImports.BuildNonFrameworkTcImports(ctok, tcConfigP, tcGlobals, frameworkTcImports, nonFrameworkResolutions, unresolvedReferences)
13251299
#if !NO_EXTENSIONTYPING
13261300
tcImports.GetCcusExcludingBase() |> Seq.iter (fun ccu ->
@@ -1338,19 +1312,13 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
13381312
//
13391313
// In the invalidation handler we use a weak reference to allow the IncrementalBuilder to
13401314
// be collected if, for some reason, a TP instance is not disposed or not GC'd.
1341-
let capturedImportsInvalidated = WeakReference<_>(importsInvalidated)
1315+
let capturedImportsInvalidated = WeakReference<_>(importsInvalidatedByTypeProvider)
13421316
ccu.Deref.InvalidateEvent.Add(fun msg ->
13431317
match capturedImportsInvalidated.TryGetTarget() with
13441318
| true, tg -> tg.Trigger msg
1345-
| _ -> ()))
1319+
| _ -> ()))
13461320
#endif
1347-
1348-
1349-
// The tcImports must be cleaned up if this builder ever gets disposed. We also dispose any previous
1350-
// tcImports should we be re-creating an entry because a dependency changed which caused this part
1351-
// of the partial build to be re-evaluated.
1352-
setCleanupItem tcImports
1353-
1321+
currentTcImportsOpt <- Some tcImports
13541322
return tcImports
13551323
with e ->
13561324
System.Diagnostics.Debug.Assert(false, sprintf "Could not BuildAllReferencedDllTcImports %A" e)
@@ -1390,7 +1358,6 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
13901358
///
13911359
/// Type check all files.
13921360
let TypeCheckTask ctok (tcAcc: TypeCheckAccumulator) input: Eventually<TypeCheckAccumulator> =
1393-
assertNotDisposed()
13941361
match input with
13951362
| Some input, _sourceRange, filename, parseErrors->
13961363
IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBETypechecked filename)
@@ -1462,7 +1429,6 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
14621429
/// Finish up the typechecking to produce outputs for the rest of the compilation process
14631430
let FinalizeTypeCheckTask ctok (tcStates: TypeCheckAccumulator[]) =
14641431
cancellable {
1465-
assertNotDisposed()
14661432
DoesNotRequireCompilerThreadTokenAndCouldPossiblyBeMadeConcurrent ctok
14671433

14681434
let errorLogger = CompilationErrorLogger("CombineImportedAssembliesTask", tcConfig.errorSeverityOptions)
@@ -1581,21 +1547,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
15811547
partialBuild <- b
15821548

15831549
let MaxTimeStampInDependencies cache (ctok: CompilationThreadToken) (output: INode) =
1584-
IncrementalBuild.MaxTimeStampInDependencies cache ctok output.Name partialBuild
1585-
1586-
member this.IncrementUsageCount() =
1587-
assertNotDisposed()
1588-
System.Threading.Interlocked.Increment(&referenceCount) |> ignore
1589-
{ new System.IDisposable with member __.Dispose() = this.DecrementUsageCount() }
1590-
1591-
member __.DecrementUsageCount() =
1592-
assertNotDisposed()
1593-
let currentValue = System.Threading.Interlocked.Decrement(&referenceCount)
1594-
if currentValue = 0 then
1595-
disposed <- true
1596-
disposeCleanupItem()
1597-
1598-
member __.IsAlive = referenceCount > 0
1550+
IncrementalBuild.MaxTimeStampInDependencies cache ctok output.Name partialBuild
15991551

16001552
member __.TcConfig = tcConfig
16011553

@@ -1607,21 +1559,14 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
16071559

16081560
member __.ProjectChecked = projectChecked.Publish
16091561

1610-
member __.ImportedCcusInvalidated = importsInvalidated.Publish
1611-
1612-
member __.AllDependenciesDeprecated = allDependencies
1613-
16141562
#if !NO_EXTENSIONTYPING
1615-
member __.ThereAreLiveTypeProviders =
1616-
let liveTPs =
1617-
match cleanupItem with
1618-
| None -> []
1619-
| Some tcImports -> [for ia in tcImports.GetImportedAssemblies() do yield! ia.TypeProviders]
1620-
match liveTPs with
1621-
| [] -> false
1622-
| _ -> true
1563+
member __.ImportsInvalidatedByTypeProvider = importsInvalidatedByTypeProvider.Publish
16231564
#endif
16241565

1566+
member __.TryGetCurrentTcImports () = currentTcImportsOpt
1567+
1568+
member __.AllDependenciesDeprecated = allDependencies
1569+
16251570
member __.Step (ctok: CompilationThreadToken) =
16261571
cancellable {
16271572
let cache = TimeStampCache defaultTimeStamp // One per step
@@ -1808,6 +1753,14 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
18081753
// Never open PDB files for the language service, even if --standalone is specified
18091754
tcConfigB.openDebugInformationForLaterStaticLinking <- false
18101755

1756+
tcConfigB.compilationThread <-
1757+
{ new ICompilationThread with
1758+
member __.EnqueueWork work =
1759+
Reactor.Singleton.EnqueueOp ("Unknown", "ICompilationThread.EnqueueWork", "work", fun ctok ->
1760+
work ctok
1761+
)
1762+
}
1763+
18111764
tcConfigB, sourceFilesNew
18121765

18131766
match loadClosureOpt with
@@ -1887,11 +1840,3 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
18871840

18881841
return builderOpt, diagnostics
18891842
}
1890-
1891-
static member KeepBuilderAlive (builderOpt: IncrementalBuilder option) =
1892-
match builderOpt with
1893-
| Some builder -> builder.IncrementUsageCount()
1894-
| None -> { new System.IDisposable with member __.Dispose() = () }
1895-
1896-
member __.IsBeingKeptAliveApartFromCacheEntry = (referenceCount >= 2)
1897-

src/fsharp/service/IncrementalBuild.fsi

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@ type internal PartialCheckResults =
8080
[<Class>]
8181
type internal IncrementalBuilder =
8282

83-
/// Check if the builder is not disposed
84-
member IsAlive : bool
85-
8683
/// The TcConfig passed in to the builder creation.
8784
member TcConfig : TcConfig
8885

@@ -104,15 +101,17 @@ type internal IncrementalBuilder =
104101
/// overall analysis results for the project will be quick.
105102
member ProjectChecked : IEvent<unit>
106103

104+
#if !NO_EXTENSIONTYPING
107105
/// Raised when a type provider invalidates the build.
108-
member ImportedCcusInvalidated : IEvent<string>
106+
member ImportsInvalidatedByTypeProvider : IEvent<string>
107+
#endif
108+
109+
/// Tries to get the current successful TcImports. This is only used in testing. Do not use it for other stuff.
110+
member TryGetCurrentTcImports : unit -> TcImports option
109111

110112
/// The list of files the build depends on
111113
member AllDependenciesDeprecated : string[]
112-
#if !NO_EXTENSIONTYPING
113-
/// Whether there are any 'live' type providers that may need a refresh when a project is Cleaned
114-
member ThereAreLiveTypeProviders : bool
115-
#endif
114+
116115
/// Perform one step in the F# build. Return true if the background work is finished.
117116
member Step : CompilationThreadToken -> Cancellable<bool>
118117

@@ -164,13 +163,6 @@ type internal IncrementalBuilder =
164163

165164
static member TryCreateBackgroundBuilderForProjectOptions : CompilationThreadToken * ReferenceResolver.Resolver * defaultFSharpBinariesDir: string * FrameworkImportsCache * scriptClosureOptions:LoadClosure option * sourceFiles:string list * commandLineArgs:string list * projectReferences: IProjectReference list * projectDirectory:string * useScriptResolutionRules:bool * keepAssemblyContents: bool * keepAllBackgroundResolutions: bool * maxTimeShareMilliseconds: int64 * tryGetMetadataSnapshot: ILBinaryReader.ILReaderTryGetMetadataSnapshot * suggestNamesForErrors: bool -> Cancellable<IncrementalBuilder option * FSharpErrorInfo[]>
166165

167-
/// Increment the usage count on the IncrementalBuilder by 1. This initial usage count is 0 so immediately after creation
168-
/// a call to KeepBuilderAlive should be made. The returns an IDisposable which will
169-
/// decrement the usage count and dispose if the usage count goes to zero
170-
static member KeepBuilderAlive : IncrementalBuilder option -> IDisposable
171-
172-
member IsBeingKeptAliveApartFromCacheEntry : bool
173-
174166
/// Generalized Incremental Builder. This is exposed only for unittesting purposes.
175167
module internal IncrementalBuild =
176168
type INode =

src/fsharp/service/ServiceDeclarationLists.fs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -492,19 +492,12 @@ type FSharpDeclarationListItem(name: string, nameInCode: string, fullName: strin
492492
member __.StructuredDescriptionTextAsync =
493493
let userOpName = "ToolTip"
494494
match info with
495-
| Choice1Of2 (items: CompletionItem list, infoReader, m, denv, reactor:IReactorOperations, checkAlive) ->
495+
| Choice1Of2 (items: CompletionItem list, infoReader, m, denv, reactor:IReactorOperations) ->
496496
// reactor causes the lambda to execute on the background compiler thread, through the Reactor
497497
reactor.EnqueueAndAwaitOpAsync (userOpName, "StructuredDescriptionTextAsync", name, fun ctok ->
498498
RequireCompilationThread ctok
499-
// This is where we do some work which may touch TAST data structures owned by the IncrementalBuilder - infoReader, item etc.
500-
// It is written to be robust to a disposal of an IncrementalBuilder, in which case it will just return the empty string.
501-
// It is best to think of this as a "weak reference" to the IncrementalBuilder, i.e. this code is written to be robust to its
502-
// disposal. Yes, you are right to scratch your head here, but this is ok.
503-
cancellable.Return(
504-
if checkAlive() then
505-
FSharpToolTipText(items |> List.map (fun x -> SymbolHelpers.FormatStructuredDescriptionOfItem true infoReader m denv x.ItemWithInst))
506-
else
507-
FSharpToolTipText [ FSharpStructuredToolTipElement.Single(wordL (tagText (FSComp.SR.descriptionUnavailable())), FSharpXmlDoc.None) ]))
499+
cancellable.Return(FSharpToolTipText(items |> List.map (fun x -> SymbolHelpers.FormatStructuredDescriptionOfItem true infoReader m denv x.ItemWithInst)))
500+
)
508501
| Choice2Of2 result ->
509502
async.Return result
510503

@@ -559,7 +552,7 @@ type FSharpDeclarationListInfo(declarations: FSharpDeclarationListItem[], isForT
559552
member __.IsError = isError
560553

561554
// Make a 'Declarations' object for a set of selected items
562-
static member Create(infoReader:InfoReader, m, denv, getAccessibility, items: CompletionItem list, reactor, currentNamespaceOrModule: string[] option, isAttributeApplicationContext: bool, checkAlive) =
555+
static member Create(infoReader:InfoReader, m: range, denv, getAccessibility, items: CompletionItem list, reactor, currentNamespaceOrModule: string[] option, isAttributeApplicationContext: bool) =
563556
let g = infoReader.g
564557
let isForType = items |> List.exists (fun x -> x.Type.IsSome)
565558
let items = items |> SymbolHelpers.RemoveExplicitlySuppressedCompletionItems g
@@ -697,7 +690,7 @@ type FSharpDeclarationListInfo(declarations: FSharpDeclarationListItem[], isForT
697690
| ns -> Some (System.String.Join(".", ns)))
698691

699692
FSharpDeclarationListItem(
700-
name, nameInCode, fullName, glyph, Choice1Of2 (items, infoReader, m, denv, reactor, checkAlive), getAccessibility item.Item,
693+
name, nameInCode, fullName, glyph, Choice1Of2 (items, infoReader, m, denv, reactor), getAccessibility item.Item,
701694
item.Kind, item.IsOwnMember, item.MinorPriority, item.Unresolved.IsNone, namespaceToOpen))
702695

703696
new FSharpDeclarationListInfo(Array.ofList decls, isForType, false)

src/fsharp/service/ServiceDeclarationLists.fsi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type public FSharpDeclarationListInfo =
6767
member IsError : bool
6868

6969
// Implementation details used by other code in the compiler
70-
static member internal Create : infoReader:InfoReader * m:range * denv:DisplayEnv * getAccessibility:(Item -> FSharpAccessibility option) * items:CompletionItem list * reactor:IReactorOperations * currentNamespace:string[] option * isAttributeApplicationContex:bool * checkAlive:(unit -> bool) -> FSharpDeclarationListInfo
70+
static member internal Create : infoReader:InfoReader * m:range * denv:DisplayEnv * getAccessibility:(Item -> FSharpAccessibility option) * items:CompletionItem list * reactor:IReactorOperations * currentNamespace:string[] option * isAttributeApplicationContex:bool -> FSharpDeclarationListInfo
7171

7272
static member internal Error : message:string -> FSharpDeclarationListInfo
7373

0 commit comments

Comments
 (0)