Discussion on async/cancellation and exceptions
See original GitHub issue@TIHan and I have been discussing using FSharp.Data.Adaptive in future work on making parts of the F# compiler and FSharp.Compiler.Service more incremental.
We’ve some questions about async, cancellation and exceptions.
As context, in our current incremental model the operations to evaluate final nodes in the dependency graph are always cancellable. In the language of FSharp.Data.Adaptive I believe this means as follows. Consider these operations:
ASet.map: ('T -> 'U) -> aset<'T> -> aset<'T>
Aset.contains: aval<'T> -> aset<'T> -> aval<'T>
Aset.elements: aset<'T> -> aval<Set<'T>>
Aval.force: aval<'T> -> 'T
In these cases, the 'T -> 'U
functions are not cancellable and might throw exceptions. My understanding is that
-
if any such function throws an exception then FSharp.Data.Adaptive never “saves” the exception as a result, it is just always propagated all the way up to the routine that requested the result.
-
some parts of the dependency graph calculation may have been committed, and this is done in an atomic way (there is a related question about concurrent update requests)
-
there is no special treatment if OperationCancelledException is thrown, and so throwing that exception is a valid way to represent cancellation.
-
however there is no cancellation token propagated, so these functions have to capture an exterior cancellation token.
Now one could imagine a variation on FSharp.Data.Adaptive that either
-
propagates a cancellation token
-
uses a computation expression like
cancellable { ... }
to represent synchronous cancellable code -
uses an existing computation like
async { ... }
that has cancellation built in (but is richer, as it represents asynchronous code)
Which would lead to signatures like this (option 1)
ASet.mapCancellable: (CancellationToken -> 'T -> 'U) -> aset<'T> -> aset<'T>
Aval.force: aval<'T> * ?cancellationToken: CancellationToken -> 'T
or this (option 2)
ASet.mapCancellable: ('T -> Cancellable<'U>) -> aset<'T> -> aset<'T>
Aval.force: aval<'T> -> Cancellable<'T>
Cancellable.run: Cancellable<'T> * ?cancellationToken: CancellationToken -> 'T
or this (option 3)
ASet.mapAsync: ('T -> Async<'U>) -> aset<'T> -> aset<'T>
Aval.force: aval<'T> -> Async<'T>
or this (combo of option 2 and 3 for tasks)
ASet.mapAsync: (CancellationToken -> 'T -> Task<'U>) -> aset<'T> -> aset<'T>
Aval.force: aval<'T> * ?cancellationToken: CancellationToken -> Task<'T>
I’m curious about your thinking about this. Obviously complicates things.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:10
Top GitHub Comments
FYI @dsyme
I just implemented
AdaptiveFile
andAdaptiveDirectory
and a little example using them HEREHope that helps with future directory-watchers 😁 Cheers
Cool 👍
Actually I didn’t check nested deletions, so i guess that means it will have the locking problem. Semantically the nested removal should work fine via
ASet.collect
We should certainly look into the locking things…