Skip to content

Commit 6fbfa44

Browse files
committed
use lock free mb in tests
1 parent e1acf39 commit 6fbfa44

File tree

3 files changed

+73
-62
lines changed

3 files changed

+73
-62
lines changed

src/Compiler/Facilities/AsyncMemoize.fs

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,17 @@ type AsyncLazy<'t>(computation: Async<'t>, ?cancelUnawaited: bool) =
4444
let cts = new CancellationTokenSource()
4545
let work = Async.StartAsTask(computation, cancellationToken = cts.Token)
4646
state <- Created (work, cts, 1)
47-
work, true
47+
work
4848
| Created (work, cts, count) ->
4949
state <- Created (work, cts, count + 1)
50-
work, (count = 0)
50+
work
5151

5252
member _.Request =
5353
async {
54-
let work, firstRequest = lock stateUpdateSync request
54+
let work = lock stateUpdateSync request
5555
try
5656
let! ct = Async.CancellationToken
57-
let options = if firstRequest then TaskContinuationOptions.ExecuteSynchronously else TaskContinuationOptions.None
57+
let options = TaskContinuationOptions.ExecuteSynchronously
5858
try
5959
return!
6060
// Using ContinueWith with a CancellationToken allows detaching from the running 'work' task.
@@ -132,7 +132,7 @@ type private KeyData<'TKey, 'TVersion> =
132132
Version: 'TVersion
133133
}
134134

135-
type Job<'t> = AsyncLazy<Result<'t * CapturingDiagnosticsLogger, exn * CapturingDiagnosticsLogger>>
135+
type Job<'t> = AsyncLazy<Result<'t, exn> * CapturingDiagnosticsLogger>
136136

137137
[<DebuggerDisplay("{DebuggerDisplay}")>]
138138
type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'TVersion: equality
@@ -152,6 +152,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T
152152
let eventCounts = [for j in JobEvent.AllEvents -> j, ref 0] |> dict
153153
let mutable hits = 0
154154
let mutable duration = 0L
155+
let mutable events_in_flight = 0
155156

156157
let keyTuple (keyData: KeyData<_, _>) = keyData.Label, keyData.Key, keyData.Version
157158

@@ -189,16 +190,15 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T
189190
let logger = CapturingDiagnosticsLogger "cache"
190191
SetThreadDiagnosticsLoggerNoUnwind logger
191192

192-
match! Async.Catch computation with
193-
194-
| Choice1Of2 result ->
193+
try
194+
let! result = computation
195195
log Finished key
196196
Interlocked.Add(&duration, sw.ElapsedMilliseconds) |> ignore
197-
return Result.Ok(result, logger)
198-
199-
| Choice2Of2 ex ->
197+
return Result.Ok result, logger
198+
with
199+
| ex ->
200200
log Failed key
201-
return Result.Error(ex, logger)
201+
return Result.Error ex, logger
202202
}
203203

204204
let getOrAdd () =
@@ -226,14 +226,12 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T
226226

227227
use _ = new CompilationGlobalsScope()
228228

229-
match! job.Request with
230-
231-
| Result.Ok(result, logger) ->
232-
logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger
229+
let! result, logger = job.Request
230+
logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger
231+
match result with
232+
| Ok result ->
233233
return result
234-
235-
| Result.Error(ex, logger) ->
236-
logger.CommitDelayedDiagnostics DiagnosticsThreadStatics.DiagnosticsLogger
234+
| Error ex ->
237235
return raise ex
238236
}
239237

@@ -244,7 +242,7 @@ type internal AsyncMemoize<'TKey, 'TVersion, 'TValue when 'TKey: equality and 'T
244242
versionsAndJobs
245243
|> Seq.tryPick (fun (version, job) ->
246244
match predicate version, job.Result with
247-
| true, Some(Result.Ok(result, _)) -> Some result
245+
| true, Some(Ok result, _) -> Some result
248246
| _ -> None)
249247

250248
member _.Clear() = cache.Clear()
@@ -313,4 +311,4 @@ type internal AsyncMemoizeDisabled<'TKey, 'TVersion, 'TValue when 'TKey: equalit
313311
Interlocked.Increment &requests |> ignore
314312
computation
315313

316-
member _.DebuggerDisplay = $"(disabled) requests: {requests}"
314+
member _.DebuggerDisplay = $"(disabled) requests: {requests}"

src/Compiler/Service/TransparentCompiler.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,8 +1821,7 @@ type internal TransparentCompiler
18211821
Trace.TraceInformation($"Using in-memory project reference: {name}")
18221822

18231823
return assemblyDataResult
1824-
with
1825-
| ex ->
1824+
with ex ->
18261825
errorR (exn ($"Error while computing assembly data for project {projectSnapshot.Label}: {ex}"))
18271826
return ProjectAssemblyDataResult.Unavailable true
18281827
}

tests/FSharp.Compiler.ComponentTests/CompilerService/AsyncMemoize.fs

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,48 @@ open FSharp.Compiler.Diagnostics
1111

1212
open Xunit
1313

14-
let tap f x = f x; x
14+
let internal observe (cache: AsyncMemoize<_,_,_>) =
1515

16-
let internal record (cache: AsyncMemoize<_,_,_>) =
16+
let collected = new MailboxProcessor<_>(fun _ -> async {})
1717

18-
let events = Collections.Concurrent.ConcurrentQueue()
18+
let arrivals = MailboxProcessor.Start(fun inbox ->
19+
let rec loop events = async {
20+
let! (e, (_, k, _)) = inbox.Receive()
21+
let events = (e, k) :: events
22+
printfn $"{k}: {e}"
23+
collected.Post events
24+
do! loop events
25+
}
26+
loop []
27+
)
28+
29+
cache.Event.Add arrivals.Post
1930

20-
cache.Event
21-
|> Event.map (fun (e, (_, k, _)) -> e, k)
22-
|> Event.add events.Enqueue
31+
let next () = collected.Receive(10_000)
2332

24-
let getEvents () = events |> List.ofSeq
33+
next
2534

26-
getEvents
35+
let rec awaitEvents next condition =
36+
async {
37+
match! next () with
38+
| events when condition events -> return events
39+
| _ -> return! awaitEvents next condition
40+
}
2741

28-
let check getEvents assertFunction expected =
29-
let actual = getEvents()
30-
assertFunction expected actual
42+
let rec eventsWhen next condition =
43+
awaitEvents next condition |> Async.RunSynchronously
3144

32-
let waitUntil getEvents condition =
33-
SpinWait.SpinUntil(fun () -> getEvents() |> condition)
45+
let waitUntil next condition =
46+
eventsWhen next condition |> ignore
3447

35-
let recorded (expected: 't list) (actual: 't list) =
36-
Assert.Equal<'t>(expected, actual)
48+
let expect next (expected: 't list) =
49+
let actual = eventsWhen next (List.length >> (=) expected.Length)
50+
Assert.Equal<'t list>(expected, actual |> List.rev)
3751

3852
let countOf value events =
3953
events |> Seq.filter (fst >> (=) value) |> Seq.length
4054

41-
let received value events =
42-
events |> List.tryLast |> Option.map (fst >> (=) value) |> Option.defaultValue false
55+
let received event = function (a, _) :: _ when a = event -> true | _ -> false
4356

4457
let internal wrapKey key =
4558
{ new ICacheKey<_, _> with
@@ -59,7 +72,7 @@ let ``Basics``() =
5972
}
6073

6174
let memoize = AsyncMemoize<int, int, int>()
62-
let events = record memoize
75+
let events = observe memoize
6376

6477
let result =
6578
seq {
@@ -77,7 +90,9 @@ let ``Basics``() =
7790

7891
Assert.Equal<int array>(expected, result)
7992

80-
let groups = events() |> Seq.groupBy snd |> Seq.toList
93+
let events = eventsWhen events (countOf Finished >> (=) 3)
94+
95+
let groups = events |> Seq.groupBy snd |> Seq.toList
8196
Assert.Equal(3, groups.Length)
8297
for key, events in groups do
8398
Assert.Equal<Set<(JobEvent * int)>>(Set [ Requested, key; Started, key; Finished, key ], Set events)
@@ -93,7 +108,7 @@ let ``We can disconnect a request from a running job`` () =
93108
}
94109

95110
let memoize = AsyncMemoize<_, int, _>(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true)
96-
let events = record memoize
111+
let events = observe memoize
97112

98113
let key = 1
99114

@@ -106,9 +121,7 @@ let ``We can disconnect a request from a running job`` () =
106121

107122
canFinish.Set() |> ignore
108123

109-
waitUntil events (received Finished)
110-
111-
check events recorded
124+
expect events
112125
[ Requested, key
113126
Started, key
114127
Finished, key ]
@@ -124,7 +137,7 @@ let ``We can cancel a job`` () =
124137
}
125138

126139
let memoize = AsyncMemoize<_, int, _>()
127-
let events = record memoize
140+
let events = observe memoize
128141

129142
let key = 1
130143

@@ -136,7 +149,7 @@ let ``We can cancel a job`` () =
136149

137150
assertTaskCanceled task1
138151

139-
check events recorded
152+
expect events
140153
[ Requested, key
141154
Started, key
142155
Canceled, key ]
@@ -151,7 +164,7 @@ let ``Job is restarted if first requestor cancels`` () =
151164
}
152165

153166
let memoize = AsyncMemoize<_, int, _>()
154-
let events = record memoize
167+
let events = observe memoize
155168

156169
use cts1 = new CancellationTokenSource()
157170

@@ -172,7 +185,7 @@ let ``Job is restarted if first requestor cancels`` () =
172185

173186
Assert.Equal(2, task2.Result)
174187

175-
check events recorded
188+
expect events
176189
[ Requested, key
177190
Started, key
178191
Canceled, key
@@ -192,7 +205,7 @@ let ``Job keeps running if only one requestor cancels`` () =
192205
}
193206

194207
let memoize = AsyncMemoize<_, int, _>()
195-
let events = record memoize
208+
let events = observe memoize
196209

197210
use cts = new CancellationTokenSource()
198211

@@ -214,7 +227,7 @@ let ``Job keeps running if only one requestor cancels`` () =
214227

215228
Assert.Equal(2, task1.Result)
216229

217-
check events recorded
230+
expect events
218231
[ Requested, key
219232
Started, key
220233
Requested, key
@@ -347,7 +360,7 @@ let ``Stress test`` () =
347360
let ``Cancel running jobs with the same key`` () =
348361
let cache = AsyncMemoize(cancelUnawaitedJobs = false, cancelDuplicateRunningJobs = true)
349362

350-
let events = record cache
363+
let events = observe cache
351364

352365
let jobCanContinue = new ManualResetEventSlim(false)
353366

@@ -373,25 +386,26 @@ let ``Cancel running jobs with the same key`` () =
373386

374387
for job in jobsToCancel do assertTaskCanceled job
375388

376-
// now the jobs should continue running unobserved
377-
Assert.Equal(0, events() |> countOf Canceled)
378-
379-
// new request should cancel the unobserved jobs
380389
let job = cache.Get(key 11, work) |> Async.StartAsTask
381390

382-
waitUntil events (countOf Canceled >> (=) 10)
391+
// up til now the jobs should have been running unobserved
392+
let current = eventsWhen events (received Requested)
393+
Assert.Equal(0, current |> countOf Canceled)
383394

384-
waitUntil events (countOf Started >> (=) 11)
395+
// new request should cancel the unobserved jobs
396+
waitUntil events (received Started)
385397

386398
jobCanContinue.Set() |> ignore
387399

388400
job.Wait()
389401

390-
Assert.Equal(0, events() |> countOf Failed)
402+
let events = eventsWhen events (received Finished)
403+
404+
Assert.Equal(0, events |> countOf Failed)
391405

392-
Assert.Equal(10, events() |> countOf Canceled)
406+
Assert.Equal(10, events |> countOf Canceled)
393407

394-
Assert.Equal(1, events() |> countOf Finished)
408+
Assert.Equal(1, events |> countOf Finished)
395409

396410
type DummyException(msg) =
397411
inherit Exception(msg)

0 commit comments

Comments
 (0)