Skip to content

Commit 1fd23c6

Browse files
committed
propogate cancellation tokens to reactor queue
1 parent 536dab5 commit 1fd23c6

File tree

3 files changed

+68
-50
lines changed

3 files changed

+68
-50
lines changed

src/fsharp/vs/Reactor.fs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
namespace Microsoft.FSharp.Compiler.SourceCodeServices
44
open System
55
open System.Diagnostics
6+
open System.Threading
67
open Microsoft.FSharp.Control
78
open Microsoft.FSharp.Compiler.Lib
89

910
// For internal use only
1011
type internal IReactorOperations =
11-
abstract EnqueueAndAwaitOpAsync : string * (unit -> 'T) -> Async<'T>
12+
abstract EnqueueAndAwaitOpAsync : string * (CancellationToken -> 'T) -> Async<'T>
1213
abstract EnqueueOp: string * (unit -> unit) -> unit
1314

1415
module internal Reactor =
@@ -24,7 +25,7 @@ module internal Reactor =
2425
/// Do a bit of work on the given build.
2526
| Step
2627
/// Do some work not synchronized in the mailbox.
27-
| Op of string * (unit -> unit)
28+
| Op of string * CancellationToken * (unit -> unit) * (unit -> unit)
2829
/// Stop building after finishing the current unit of work.
2930
| StopBackgroundOp of AsyncReplyChannel<ResultOrException<unit>>
3031
/// Finish building.
@@ -178,15 +179,15 @@ module internal Reactor =
178179
//if span.TotalMilliseconds > 100.0 then
179180
Debug.WriteLine("Reactor: <-- background step, remaining {0}, took {1}ms", inbox.CurrentQueueLength, span.TotalMilliseconds)
180181
res
181-
| Op (desc, op) ->
182+
| Op (desc, ct, op, ccont) ->
183+
if ct.IsCancellationRequested then ccont(); state else
182184
Debug.WriteLine("Reactor: --> {0}, remaining {1}, mem {2}, gc2 {3}", desc, inbox.CurrentQueueLength, GC.GetTotalMemory(false)/1000000L, GC.CollectionCount(2))
183185
let time = System.DateTime.Now
184186
let res = HandleOp op state
185187
let span = System.DateTime.Now - time
186188
//if span.TotalMilliseconds > 100.0 then
187189
Debug.WriteLine("Reactor: <-- {0}, remaining {1}, took {2}ms", desc, inbox.CurrentQueueLength, span.TotalMilliseconds)
188190
res
189-
190191
| StopBackgroundOp channel ->
191192
Debug.WriteLine("Reactor: --> stop background, remaining {0}, mem {1}, gc2 {2}", inbox.CurrentQueueLength, GC.GetTotalMemory(false)/1000000L, GC.CollectionCount(2))
192193
HandleStopBackgroundOp channel state
@@ -213,7 +214,11 @@ module internal Reactor =
213214

214215
member r.EnqueueOp(desc, op) =
215216
Debug.WriteLine("Reactor: enqueue {0}, length {1}", desc, builder.CurrentQueueLength)
216-
builder.Post(Op(desc, op))
217+
builder.Post(Op(desc, CancellationToken.None, op, (fun () -> ())))
218+
219+
member r.EnqueueOpPrim(desc, ct, op, ccont) =
220+
Debug.WriteLine("Reactor: enqueue {0}, length {1}", desc, builder.CurrentQueueLength)
221+
builder.Post(Op(desc, ct, op, ccont))
217222

218223
member r.CurrentQueueLength =
219224
builder.CurrentQueueLength
@@ -226,17 +231,22 @@ module internal Reactor =
226231
| Exception excn->raise excn
227232

228233
member r.EnqueueAndAwaitOpAsync (desc, f) =
229-
let resultCell = AsyncUtil.AsyncResultCell<_>()
230-
r.EnqueueOp(desc,
231-
fun () ->
232-
let result =
233-
try
234-
f () |> AsyncUtil.AsyncOk
235-
with
236-
| e -> e |> AsyncUtil.AsyncException
237-
resultCell.RegisterResult(result)
238-
)
239-
resultCell.AsyncResult
234+
async {
235+
let! ct = Async.CancellationToken
236+
let resultCell = AsyncUtil.AsyncResultCell<_>()
237+
r.EnqueueOpPrim(desc, ct,
238+
(fun () ->
239+
let result =
240+
try
241+
f ct |> AsyncUtil.AsyncOk
242+
with
243+
| e -> e |> AsyncUtil.AsyncException
244+
resultCell.RegisterResult(result)),
245+
(fun () -> resultCell.RegisterResult (AsyncUtil.AsyncCanceled(OperationCanceledException())) )
246+
247+
)
248+
return! resultCell.AsyncResult
249+
}
240250

241251
let theReactor = Reactor()
242252
let Reactor() = theReactor

src/fsharp/vs/Reactor.fsi

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
namespace Microsoft.FSharp.Compiler.SourceCodeServices
44

5+
open System.Threading
6+
57
// For internal use only
68
type internal IReactorOperations =
79

810
/// Put the operation in thq queue, and return an async handle to its result.
9-
abstract EnqueueAndAwaitOpAsync : description: string * action: (unit -> 'T) -> Async<'T>
11+
abstract EnqueueAndAwaitOpAsync : description: string * action: (CancellationToken -> 'T) -> Async<'T>
1012

1113
/// Enqueue an operation and return immediately.
1214
abstract EnqueueOp: description: string * action: (unit -> unit) -> unit
@@ -32,7 +34,7 @@ module internal Reactor =
3234
/// Block until the current implicit background build is complete.
3335
member WaitForBackgroundOpCompletion : unit -> unit
3436

35-
/// Enqueue an operation and return immediately.
37+
/// Enqueue an uncancellable operation and return immediately.
3638
member EnqueueOp : description: string * op:(unit -> unit) -> unit
3739

3840
/// For debug purposes
@@ -43,7 +45,7 @@ module internal Reactor =
4345
// operation which can be halted part way through.
4446

4547
/// Put the operation in thq queue, and return an async handle to its result.
46-
member EnqueueAndAwaitOpAsync : description: string * (unit -> 'T) -> Async<'T>
48+
member EnqueueAndAwaitOpAsync : description: string * (CancellationToken -> 'T) -> Async<'T>
4749

4850
/// Get the reactor for FSharp.Compiler.dll
4951
val Reactor : unit -> Reactor

src/fsharp/vs/service.fs

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1911,7 +1911,7 @@ type FSharpCheckProjectResults(keepAssemblyContents, errors: FSharpErrorInfo[],
19111911
member info.GetUsesOfSymbol(symbol:FSharpSymbol) =
19121912
let (tcGlobals, _tcImports, _thisCcu, _ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr) = getDetails()
19131913
// This probably doesn't need to be run on the reactor since all data touched by GetUsesOfSymbol is immutable.
1914-
reactorOps.EnqueueAndAwaitOpAsync("GetUsesOfSymbol", fun () ->
1914+
reactorOps.EnqueueAndAwaitOpAsync("GetUsesOfSymbol", fun _ct ->
19151915
[| for r in tcSymbolUses do yield! r.GetUsesOfSymbol(symbol.Item) |]
19161916
|> Seq.distinctBy (fun (itemOcc,_denv,m) -> itemOcc, m)
19171917
|> Seq.map (fun (itemOcc,denv,m) -> FSharpSymbolUse(tcGlobals, denv, symbol, itemOcc, m))
@@ -1921,7 +1921,7 @@ type FSharpCheckProjectResults(keepAssemblyContents, errors: FSharpErrorInfo[],
19211921
member info.GetAllUsesOfAllSymbols() =
19221922
let (tcGlobals, tcImports, thisCcu, _ccuSig, tcSymbolUses, _topAttribs, _tcAssemblyData, _ilAssemRef, _ad, _tcAssemblyExpr) = getDetails()
19231923
// This probably doesn't need to be run on the reactor since all data touched by GetAllUsesOfSymbols is immutable.
1924-
reactorOps.EnqueueAndAwaitOpAsync("GetAllUsesOfAllSymbols", fun () ->
1924+
reactorOps.EnqueueAndAwaitOpAsync("GetAllUsesOfAllSymbols", fun _ct ->
19251925
[| for r in tcSymbolUses do
19261926
for (item,itemOcc,denv,m) in r.GetAllUsesOfSymbols() do
19271927
let symbol = FSharpSymbol.Create(tcGlobals, thisCcu, tcImports, item)
@@ -1984,7 +1984,7 @@ type FSharpCheckFileResults(errors: FSharpErrorInfo[], scopeOptX: TypeCheckInfo
19841984
| Some (scope, builderOpt, reactor) ->
19851985
// Ensure the builder doesn't get released while running operations asynchronously.
19861986
use _unwind = match builderOpt with Some builder -> builder.IncrementUsageCount() | None -> { new System.IDisposable with member __.Dispose() = () }
1987-
let! res = reactor.EnqueueAndAwaitOpAsync(desc, fun () -> f scope)
1987+
let! res = reactor.EnqueueAndAwaitOpAsync(desc, fun _ct -> f scope)
19881988
return res
19891989
}
19901990

@@ -2206,8 +2206,8 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
22062206

22072207
let reactorOps =
22082208
{ new IReactorOperations with
2209-
member __.EnqueueAndAwaitOpAsync (desc,op) = reactor.EnqueueAndAwaitOpAsync (desc,op)
2210-
member __.EnqueueOp (desc,op) = reactor.EnqueueOp (desc,op) }
2209+
member __.EnqueueAndAwaitOpAsync (desc, op) = reactor.EnqueueAndAwaitOpAsync (desc, op)
2210+
member __.EnqueueOp (desc, op) = reactor.EnqueueOp (desc, op) }
22112211

22122212
// STATIC ROOT: LanguageServiceState.FSharpChecker.backgroundCompiler.scriptClosureCache
22132213
/// Information about the derived script closure.
@@ -2350,20 +2350,26 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
23502350
foregroundTypeCheckCount <- foregroundTypeCheckCount + 1
23512351
locked (fun () ->
23522352
parseAndCheckFileInProjectCachePossiblyStale.Set((filename,options),(parseResults,typedResults,fileVersion))
2353-
parseAndCheckFileInProjectCache.Set((filename,source,options),(parseResults,typedResults,fileVersion)))
2353+
parseAndCheckFileInProjectCache.Set((filename,source,options),(parseResults,typedResults,fileVersion))
2354+
parseFileInProjectCache.Set((filename,source,options),parseResults))
23542355

23552356
/// Parses the source file and returns untyped AST
23562357
member bc.ParseFileInProject(filename:string, source,options:FSharpProjectOptions) =
23572358
match locked (fun () -> parseFileInProjectCache.TryGet (filename, source, options)) with
23582359
| Some res -> async.Return res
23592360
| None ->
2360-
reactor.EnqueueAndAwaitOpAsync("ParseFileInProject", fun () ->
2361+
// Try this cache too (which might contain different entries)
2362+
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
2363+
match cachedResults with
2364+
| Some (parseResults, _checkResults,_) -> async.Return parseResults
2365+
| _ ->
2366+
reactor.EnqueueAndAwaitOpAsync("ParseFileInProject", fun _ct ->
23612367

2368+
// Try the caches again - it may have been filled by the time this operation runs
23622369
match locked (fun () -> parseFileInProjectCache.TryGet (filename, source, options)) with
23632370
| Some res -> res
23642371
| None ->
23652372
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
2366-
// We can use cached results when there is no work to do to bring the background builder up-to-date
23672373
match cachedResults with
23682374
| Some (parseResults, _checkResults,_) -> parseResults
23692375
| _ ->
@@ -2383,8 +2389,8 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
23832389

23842390
/// Fetch the parse information from the background compiler (which checks w.r.t. the FileSystem API)
23852391
member bc.GetBackgroundParseResultsForFileInProject(filename, options) =
2386-
reactor.EnqueueAndAwaitOpAsync("GetBackgroundParseResultsForFileInProject", fun () ->
2387-
let builderOpt, creationErrors, _= getOrCreateBuilder options
2392+
reactor.EnqueueAndAwaitOpAsync("GetBackgroundParseResultsForFileInProject", fun _ct ->
2393+
let builderOpt, creationErrors, _ = getOrCreateBuilder options
23882394
match builderOpt with
23892395
| None -> FSharpParseFileResults(List.toArray creationErrors, None, true, [])
23902396
| Some builder ->
@@ -2396,7 +2402,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
23962402
)
23972403

23982404
member bc.MatchBraces(filename:string, source, options)=
2399-
reactor.EnqueueAndAwaitOpAsync("MatchBraces", fun () ->
2405+
reactor.EnqueueAndAwaitOpAsync("MatchBraces", fun _ct ->
24002406
let builderOpt,_,_ = getOrCreateBuilder options
24012407
match builderOpt with
24022408
| None -> [| |]
@@ -2409,17 +2415,17 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24092415

24102416
/// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
24112417
member bc.CheckFileInProjectIfReady(parseResults:FSharpParseFileResults,filename,fileVersion,source,options,isResultObsolete,textSnapshotInfo:obj option) =
2412-
reactor.EnqueueAndAwaitOpAsync("CheckFileInProjectIfReady", fun () ->
2418+
reactor.EnqueueAndAwaitOpAsync("CheckFileInProjectIfReady", fun _ct ->
24132419
let checkAnswer =
24142420
match incrementalBuildersCache.TryGetAny options with
24152421
| Some(Some builder, creationErrors, _) ->
24162422

2417-
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
2418-
// We can use cached results when there is no work to do to bring the background builder up-to-date
2419-
match cachedResults with
2420-
| Some (_parseResults, checkResults,_) when builder.AreCheckResultsBeforeFileInProjectReady(filename) ->
2421-
Some (FSharpCheckFileAnswer.Succeeded checkResults)
2422-
| _ ->
2423+
// Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
2424+
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
2425+
match cachedResults with
2426+
| Some (_parseResults, checkResults,_) when builder.AreCheckResultsBeforeFileInProjectReady(filename) -> Some (FSharpCheckFileAnswer.Succeeded checkResults)
2427+
| _ ->
2428+
24232429
match builder.GetCheckResultsBeforeFileInProjectIfReady filename with
24242430
| Some(tcPrior) ->
24252431

@@ -2441,18 +2447,18 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24412447

24422448
/// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
24432449
member bc.CheckFileInProject(parseResults:FSharpParseFileResults,filename,fileVersion,source,options,isResultObsolete,textSnapshotInfo) =
2444-
reactor.EnqueueAndAwaitOpAsync("CheckFileInProject", fun () ->
2450+
reactor.EnqueueAndAwaitOpAsync("CheckFileInProject", fun _ct ->
24452451
let builderOpt,creationErrors,_ = getOrCreateBuilder options
24462452
match builderOpt with
24472453
| None -> FSharpCheckFileAnswer.Succeeded (MakeCheckFileResultsEmpty(creationErrors))
24482454
| Some builder ->
2449-
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
24502455

2451-
// We can use cached results when there is no work to do to bring the background builder up-to-date
2456+
// Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
2457+
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
24522458
match cachedResults with
2453-
| Some (_parseResults, checkResults,_) when builder.AreCheckResultsBeforeFileInProjectReady(filename) ->
2454-
FSharpCheckFileAnswer.Succeeded checkResults
2459+
| Some (_parseResults, checkResults,_) when builder.AreCheckResultsBeforeFileInProjectReady(filename) -> FSharpCheckFileAnswer.Succeeded checkResults
24552460
| _ ->
2461+
24562462
let tcPrior = builder.GetCheckResultsBeforeFileInProject filename
24572463
let loadClosure = scriptClosureCache.TryGet options
24582464
let tcErrors, tcFileResult =
@@ -2465,16 +2471,16 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24652471

24662472
/// Parses the source file and returns untyped AST
24672473
member bc.ParseAndCheckFileInProject(filename:string, fileVersion, source, options:FSharpProjectOptions,isResultObsolete,textSnapshotInfo) =
2468-
reactor.EnqueueAndAwaitOpAsync("ParseAndCheckFileInProjectPartB", fun () ->
2474+
reactor.EnqueueAndAwaitOpAsync("ParseAndCheckFileInProject", fun _ct ->
24692475
let builderOpt,creationErrors,_ = getOrCreateBuilder options // Q: Whis it it ok to ignore creationErrors in the build cache? A: These errors will be appended into the typecheck results
24702476
match builderOpt with
24712477
| None ->
24722478
let parseResults = FSharpParseFileResults(List.toArray creationErrors, None, true, [])
24732479
(parseResults, FSharpCheckFileAnswer.Aborted)
24742480
| Some builder ->
2475-
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
24762481

2477-
// We can use cached results when there is no work to do to bring the background builder up-to-date
2482+
// Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
2483+
let cachedResults = locked (fun () -> parseAndCheckFileInProjectCache.TryGet((filename,source,options)))
24782484
match cachedResults with
24792485
| Some (parseResults, checkResults,_) when builder.AreCheckResultsBeforeFileInProjectReady(filename) ->
24802486
parseResults, FSharpCheckFileAnswer.Succeeded checkResults
@@ -2496,7 +2502,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
24962502

24972503
/// Fetch the check information from the background compiler (which checks w.r.t. the FileSystem API)
24982504
member bc.GetBackgroundCheckResultsForFileInProject(filename,options) =
2499-
reactor.EnqueueAndAwaitOpAsync("GetBackgroundCheckResultsForFileInProject", fun () ->
2505+
reactor.EnqueueAndAwaitOpAsync("GetBackgroundCheckResultsForFileInProject", fun _ct ->
25002506
let (builderOpt, creationErrors, _) = getOrCreateBuilder options
25012507
match builderOpt with
25022508
| None ->
@@ -2550,10 +2556,10 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
25502556

25512557
/// Parse and typecheck the whole project.
25522558
member bc.ParseAndCheckProject(options) =
2553-
reactor.EnqueueAndAwaitOpAsync("ParseAndCheckProject", fun () -> bc.ParseAndCheckProjectImpl(options))
2559+
reactor.EnqueueAndAwaitOpAsync("ParseAndCheckProject", fun _ct -> bc.ParseAndCheckProjectImpl(options))
25542560

25552561
member bc.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib) =
2556-
reactor.EnqueueAndAwaitOpAsync ("GetProjectOptionsFromScript", fun () ->
2562+
reactor.EnqueueAndAwaitOpAsync ("GetProjectOptionsFromScript", fun _ct ->
25572563
// Do we add a reference to FSharp.Compiler.Interactive.Settings by default?
25582564
let useFsiAuxLib = defaultArg useFsiAuxLib true
25592565
// Do we use a "FSharp.Core, 4.3.0.0" reference by default?
@@ -2630,7 +2636,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
26302636
member bc.CurrentQueueLength = reactor.CurrentQueueLength
26312637

26322638
member bc.ClearCaches() =
2633-
reactor.EnqueueAndAwaitOpAsync ("ClearCaches", fun () ->
2639+
reactor.EnqueueAndAwaitOpAsync ("ClearCaches", fun _ct ->
26342640
locked (fun () ->
26352641
parseAndCheckFileInProjectCachePossiblyStale.Clear()
26362642
parseAndCheckFileInProjectCache.Clear()
@@ -2640,7 +2646,7 @@ type BackgroundCompiler(projectCacheSize, keepAssemblyContents, keepAllBackgroun
26402646
scriptClosureCache.Clear())
26412647

26422648
member bc.DownsizeCaches() =
2643-
reactor.EnqueueAndAwaitOpAsync ("DownsizeCaches", fun () ->
2649+
reactor.EnqueueAndAwaitOpAsync ("DownsizeCaches", fun _ct ->
26442650
locked (fun () ->
26452651
parseAndCheckFileInProjectCachePossiblyStale.Resize(keepStrongly=1)
26462652
parseAndCheckFileInProjectCache.Resize(keepStrongly=1)

0 commit comments

Comments
 (0)