Skip to content

Commit bcaabe9

Browse files
committed
Fixing snapshots/caching
1 parent 8004136 commit bcaabe9

11 files changed

Lines changed: 598 additions & 364 deletions

src/FsAutoComplete.Core/AdaptiveExtensions.fs

Lines changed: 122 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,18 @@ module AdaptiveExtensions =
1313

1414
type CancellationTokenSource with
1515

16-
/// Communicates a request for cancellation. Ignores ObjectDisposedException
1716
member cts.TryCancel() =
1817
try
1918
cts.Cancel()
20-
with :? ObjectDisposedException ->
21-
()
19+
with
20+
| :? ObjectDisposedException
21+
| :? NullReferenceException -> ()
2222

23-
/// Releases all resources used by the current instance of the System.Threading.CancellationTokenSource class.
2423
member cts.TryDispose() =
25-
try
26-
cts.Dispose()
27-
with _ ->
28-
()
24+
// try
25+
cts.Dispose()
26+
// with _ -> ()
27+
2928

3029
type TaskCompletionSource<'a> with
3130

@@ -148,7 +147,7 @@ module AVal =
148147
/// Creates an observable with the given object and will be executed whenever the object gets marked out-of-date. Note that it does not trigger when the object is currently out-of-date.
149148
/// </summary>
150149
/// <param name="aval">The aval to get out-of-date information from.</param>
151-
let onOutOfDateWeak (aval: #aval<_>) =
150+
let onOutOfDateWeak (aval: #IAdaptiveObject) =
152151
Observable.Create(fun (obs: IObserver<_>) -> aval.AddWeakMarkingCallback(fun _ -> obs.OnNext aval))
153152

154153

@@ -531,53 +530,90 @@ module AsyncAVal =
531530
let ofTask (value: Task<'a>) = ConstantVal(value) :> asyncaval<_>
532531

533532
let ofCancellableTask (value: CancellableTask<'a>) =
534-
let mutable cache: Option<AdaptiveCancellableTask<'a>> = None
535533

536534
{ new AbstractVal<'a>() with
537-
member x.Compute t =
538-
if x.OutOfDate || Option.isNone cache then
539-
let cts = new CancellationTokenSource()
540-
541-
let cancel () =
542-
cts.TryCancel()
543-
cts.TryDispose()
535+
member x.Compute _ =
536+
let cts = new CancellationTokenSource()
537+
538+
let cancel () =
539+
cts.TryCancel()
540+
cts.TryDispose()
541+
542+
let real =
543+
task {
544+
try
545+
return! value cts.Token
546+
finally
547+
cts.TryDispose()
548+
}
549+
550+
AdaptiveCancellableTask(cancel, real) }
551+
:> asyncaval<_>
544552

545-
let real =
546-
task {
547-
try
548-
return! value cts.Token
549-
finally
550-
cts.TryDispose()
551-
}
552553

553-
cache <- Some(AdaptiveCancellableTask(cancel, real))
554+
let ofCancellableValueTask (value: CancellableValueTask<'a>) =
554555

555-
cache.Value }
556+
{ new AbstractVal<'a>() with
557+
member x.Compute _ =
558+
let cts = new CancellationTokenSource()
559+
560+
let cancel () =
561+
cts.TryCancel()
562+
cts.TryDispose()
563+
564+
let real =
565+
task {
566+
try
567+
return! value cts.Token
568+
finally
569+
cts.TryDispose()
570+
}
571+
572+
AdaptiveCancellableTask(cancel, real) }
556573
:> asyncaval<_>
557574

558-
let ofAsync (value: Async<'a>) =
559-
let mutable cache: Option<AdaptiveCancellableTask<'a>> = None
560575

561-
{ new AbstractVal<'a>() with
562-
member x.Compute t =
563-
if x.OutOfDate || Option.isNone cache then
564-
let cts = new CancellationTokenSource()
565576

566-
let cancel () =
567-
cts.TryCancel()
568-
cts.TryDispose()
577+
let _ofAsyncAValSeq (maxDegreeOfParallelism: int) (input: #seq<#asyncaval<'a>>) =
578+
let mutable cache: option<RefCountingTaskCreator<'a array>> = None
569579

570-
let real =
571-
task {
572-
try
573-
return! Async.StartImmediateAsTask(value, cts.Token)
574-
finally
575-
cts.TryDispose()
576-
}
580+
{ new AbstractVal<_>() with
581+
member x.Compute t =
582+
if x.OutOfDate || Option.isNone cache then
583+
let ref =
584+
RefCountingTaskCreator(
585+
cancellableTask {
586+
return!
587+
input
588+
|> Seq.map (fun v -> cancellableTask { return! v.GetValue t })
589+
|> CancellableTask.whenAllThrottled maxDegreeOfParallelism
590+
}
591+
)
577592

578-
cache <- Some(AdaptiveCancellableTask(cancel, real))
593+
cache <- Some ref
594+
ref.New()
595+
else
596+
cache.Value.New() }
597+
:> asyncaval<_>
579598

580-
cache.Value }
599+
let ofAsync (value: Async<'a>) =
600+
{ new AbstractVal<'a>() with
601+
member x.Compute _ =
602+
let cts = new CancellationTokenSource()
603+
604+
let cancel () =
605+
cts.TryCancel()
606+
cts.TryDispose()
607+
608+
let real =
609+
task {
610+
try
611+
return! Async.StartImmediateAsTask(value, cts.Token)
612+
finally
613+
cts.TryDispose()
614+
}
615+
616+
AdaptiveCancellableTask(cancel, real) }
581617
:> asyncaval<_>
582618

583619
/// <summary>
@@ -589,7 +625,11 @@ module AsyncAVal =
589625
else
590626
{ new AbstractVal<'a>() with
591627
member x.Compute t =
592-
let real = Task.FromResult(value.GetValue t)
628+
let real =
629+
// if out of date, assume it needs to run on the threadpool and not the current thread
630+
Task.Run(fun () -> value.GetValue t)
631+
632+
593633
AdaptiveCancellableTask(id, real) }
594634
:> asyncaval<_>
595635

@@ -600,6 +640,7 @@ module AsyncAVal =
600640
/// </summary>
601641
let map (mapping: 'a -> CancellationToken -> Task<'b>) (input: asyncaval<'a>) =
602642
let mutable cache: option<RefCountingTaskCreator<'b>> = None
643+
let mutable dataCache = ValueNone
603644

604645
{ new AbstractVal<'b>() with
605646
member x.Compute t =
@@ -608,7 +649,13 @@ module AsyncAVal =
608649
RefCountingTaskCreator(
609650
cancellableTask {
610651
let! i = input.GetValue t
611-
return! mapping i
652+
653+
match dataCache with
654+
| ValueSome(struct (oa, ob)) when Utils.cheapEqual oa i -> return ob
655+
| _ ->
656+
let! b = mapping i
657+
dataCache <- ValueSome(struct (i, b))
658+
return b
612659
}
613660
)
614661

@@ -631,20 +678,15 @@ module AsyncAVal =
631678
/// adaptive inputs.
632679
/// </summary>
633680
let mapSync (mapping: 'a -> CancellationToken -> 'b) (input: asyncaval<'a>) =
634-
map
635-
(fun a ct ->
636-
if ct.IsCancellationRequested then
637-
Task.FromCanceled<_>(ct)
638-
else
639-
Task.FromResult(mapping a ct))
640-
input
681+
map (fun a ct -> Task.FromResult(mapping a ct)) input
641682

642683
/// <summary>
643684
/// Returns a new async adaptive value that adaptively applies the mapping function to the given
644685
/// adaptive inputs.
645686
/// </summary>
646687
let map2 (mapping: 'a -> 'b -> CancellationToken -> Task<'c>) (ca: asyncaval<'a>) (cb: asyncaval<'b>) =
647688
let mutable cache: option<RefCountingTaskCreator<'c>> = None
689+
let mutable dataCache = ValueNone
648690

649691
{ new AbstractVal<'c>() with
650692
member x.Compute t =
@@ -662,9 +704,15 @@ module AsyncAVal =
662704
ta.Cancel()
663705
tb.Cancel())
664706

665-
let! va = ta.Task
666-
let! vb = tb.Task
667-
return! mapping va vb
707+
let! ia = ta.Task
708+
let! ib = tb.Task
709+
710+
match dataCache with
711+
| ValueSome(struct (va, vb, vc)) when Utils.cheapEqual va ia && Utils.cheapEqual vb ib -> return vc
712+
| _ ->
713+
let! vc = mapping ia ib ct
714+
dataCache <- ValueSome(struct (ia, ib, vc))
715+
return vc
668716
}
669717
)
670718

@@ -680,6 +728,7 @@ module AsyncAVal =
680728
let bind (mapping: 'a -> CancellationToken -> asyncaval<'b>) (value: asyncaval<'a>) =
681729
let mutable cache: option<_> = None
682730
let mutable innerCache: option<_> = None
731+
let mutable outerDataCache: option<_> = None
683732
let mutable inputChanged = 0
684733
let inners: ref<HashSet<asyncaval<'b>>> = ref HashSet.empty
685734

@@ -702,9 +751,14 @@ module AsyncAVal =
702751
RefCountingTaskCreator(
703752
cancellableTask {
704753
let! i = value.GetValue t
705-
let! ct = CancellableTask.getCancellationToken ()
706-
let inner = mapping i ct
707-
return inner
754+
755+
match outerDataCache with
756+
| Some(struct (oa, ob)) when Utils.cheapEqual oa i -> return ob
757+
| _ ->
758+
let! ct = CancellableTask.getCancellationToken ()
759+
let inner = mapping i ct
760+
outerDataCache <- Some(i, inner)
761+
return inner
708762

709763
}
710764
)
@@ -800,7 +854,8 @@ module AsyncAValBuilderExtensions =
800854
member inline x.Source(value: aval<'T>) = AsyncAVal.ofAVal value
801855
member inline x.Source(value: Task<'T>) = AsyncAVal.ofTask value
802856
member inline x.Source(value: Async<'T>) = AsyncAVal.ofAsync value
803-
member inline x.Source(value: CancellableTask<'T>) = AsyncAVal.ofCancellableTask value
857+
member inline x.Source([<InlineIfLambda>] value: CancellableTask<'T>) = AsyncAVal.ofCancellableTask value
858+
member inline x.Source([<InlineIfLambda>] value: CancellableValueTask<'T>) = AsyncAVal.ofCancellableValueTask value
804859

805860
member inline x.BindReturn(value: asyncaval<'T1>, [<InlineIfLambda>] mapping: 'T1 -> CancellationToken -> 'T2) =
806861
AsyncAVal.mapSync (fun data ctok -> mapping data ctok) value
@@ -854,3 +909,10 @@ module AMapAsync =
854909
| Some x -> return! x
855910
| None -> return Error reason
856911
}
912+
913+
914+
let _filterValuesByKey (key: 'Key) (map: amap<'Key, #asyncaval<'Value>>) =
915+
asyncAVal {
916+
let! values = map |> AMap.filter (fun k _ -> k = key) |> AMap.toASetValues |> ASet.toAVal
917+
return! AsyncAVal._ofAsyncAValSeq 1 values
918+
}

src/FsAutoComplete.Core/AdaptiveExtensions.fsi

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ namespace FsAutoComplete.Adaptive
22

33
[<AutoOpen>]
44
module AdaptiveExtensions =
5+
6+
type System.Threading.CancellationTokenSource with
7+
8+
/// Communicates a request for cancellation. Ignores ObjectDisposedException
9+
member TryCancel: unit -> unit
10+
/// Releases all resources used by the current instance of the System.Threading.CancellationTokenSource class.
11+
member TryDispose: unit -> unit
12+
513
type FSharp.Data.Adaptive.ChangeableHashMap<'Key, 'Value> with
614

715
/// <summary>
@@ -63,7 +71,7 @@ module AVal =
6371
/// Creates an observable with the given object and will be executed whenever the object gets marked out-of-date. Note that it does not trigger when the object is currently out-of-date.
6472
/// </summary>
6573
/// <param name="aval">The aval to get out-of-date information from.</param>
66-
val onOutOfDateWeak: aval: 'a -> System.IObservable<'a> when 'a :> FSharp.Data.Adaptive.aval<'b>
74+
val onOutOfDateWeak: aval: 'a -> System.IObservable<'a> when 'a :> FSharp.Data.Adaptive.IAdaptiveObject
6775

6876
/// <summary>Creates an observable on the aval that will be executed whenever the avals value changed.</summary>
6977
/// <param name="aval">The aval to get out-of-date information from.</param>
@@ -268,6 +276,7 @@ module AsyncAVal =
268276
val ofTask: value: System.Threading.Tasks.Task<'a> -> asyncaval<'a>
269277

270278
val ofCancellableTask: value: IcedTasks.CancellableTasks.CancellableTask<'a> -> asyncaval<'a>
279+
val ofCancellableValueTask: value: IcedTasks.CancellableValueTasks.CancellableValueTask<'a> -> asyncaval<'a>
271280

272281
val ofAsync: value: Async<'a> -> asyncaval<'a>
273282

@@ -355,6 +364,7 @@ module AsyncAValBuilderExtensions =
355364
member inline Source: value: System.Threading.Tasks.Task<'T> -> asyncaval<'T>
356365
member inline Source: value: Async<'T> -> asyncaval<'T>
357366
member inline Source: value: CancellableTask<'T> -> asyncaval<'T>
367+
member inline Source: value: CancellableValueTask<'T> -> asyncaval<'T>
358368

359369
member inline BindReturn:
360370
value: asyncaval<'T1> * mapping: ('T1 -> System.Threading.CancellationToken -> 'T2) -> asyncaval<'T2>

0 commit comments

Comments
 (0)