FsToolkit.ErrorHandling
FsToolkit.ErrorHandling copied to clipboard
Task to AsyncResult transition wraps `Exception` into the `AggregateException`
Describe the bug
member this.AsyncAcquireTokenInteractively (uiThread, correlationId) = asyncResult {
do! Async.SwitchToContext uiThread
let builder =
pca.AcquireTokenInteractive(options.Scopes)
.WithCorrelationId(correlationId)
if not (isNull configureInteractive) then configureInteractive.Invoke(builder)
try return! builder.ExecuteAsync() // FSharp.Control.FusionTasks is used not to write |> Async.AwaitTask
with :? MsalClientException as ex -> return! Error ex.ErrorCode
}
Matching by the MsalClientException does not happen as it is wrapped by the AggregateException.
To Reproduce Steps to reproduce the behavior:
- Call Task returning method via
return!in anasyncResultCE - Catch exception
- You will see that it is always the
AggregateException.
Expected behavior A clear and concise description of what you expected to happen.
Desktop (please complete the following information):
- Version 2.10.0
I don't think this is a problem with FsToolkit as this is how exceptions are thrown with Tasks via .NET https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library
Is it how async CE works itself?
It does not happen with await in C#
Ah, this is the issue https://github.com/fsharp/fslang-suggestions/issues/840
As long as it is a FSharp.Core issue I propose to use a corrected way of awaiting tasks
https://github.com/jet/FsKafka/blob/6c6186f822a221adf0ad06e8a44a392aa55aa8ec/src/FsKafka/Infrastructure.fs#L14 instead of Async.AwaitTask
Hey @NinoFloris see any downsides to doing this?
I have this helper in various code-bases:
let rec unpackException (exn : Exception) =
match exn with
| :? AggregateException as agg ->
match Seq.tryExactlyOne agg.InnerExceptions with
| Some x -> unpackException x
| None -> exn
| _ -> exn
As long as it is a FSharp.Core issue I propose to use a corrected way of awaiting tasks https://github.com/jet/FsKafka/blob/6c6186f822a221adf0ad06e8a44a392aa55aa8ec/src/FsKafka/Infrastructure.fs#L14 instead of
Async.AwaitTask
Note the edition therein in being synced with the canonical one in https://github.com/jet/FsKafka/pull/92 (similarly https://github.com/fsprojects/FSharp.AWS.DynamoDB/pull/49)
The key specific diff in there is that cancellation will be guaranteed yield an exception, but the more important effect is that people debugging systems can do that based on being able to assume-but-verify identical behavior vs the needless variation having slight variations can cause (especially if we do manage to converge in the end).
@TheAngryByrd please excuse the atting....
what would you say about a breaking change regarding this?
I recently flipped some taskResult code to asyncResult and fell prey to the silent/implicit change in semantic (i.e. with no longer Doing What I Mean when awaiting something that returns Task<'T>
Possible resolutions are one or more of:
- make
asyncResultnot magically await tasks (force usage ofAsync.AwaitTaskor something likeAwaitTaskCorrectof your choice) - make the awaits of tasks in this lib do the right thing (inhibit the unhelpful AggregateException wrapping as ATC does)
- put AwaitTaskCorrect into this library and use it internally (though the real answer is to solve it upstream - there's a placeholder issue here: https://github.com/fsprojects/FSharp.Control.TaskSeq/issues/141)
I guess the simplest thing is to remove implicit awaiting? (Checking if there's any way I can inhibit this as a consumer without changing the lib...)
@bartelink
I did create an “asyncEx” in IcedTasks with the unwrapping aggregate semantics. Could you give that a once over and make sure it’s working the way you think it should be?
If it does then I think it might be worth making another CE that can polyfill the current one.
Breaking API changes are less scary usually because there’s obvious compile errors but runtime changes does kind of scare me.
Oh yeah, I remember that now 🤦 Will have a peep (there's another semantic in ATC about honoring cancellation too) You're not wrong about compile vs runtime semantic changes being a very different level of concern
Added a comment https://github.com/TheAngryByrd/IcedTasks/pull/24/files#r1556182882
TL;DR I don't think any deviations whatsoever from AwaitTaskCorrect are a good idea.
I don't understand enough about what forces AwaitAwaiter is balancing to be able to comment on the specifics of its implementation - e.g. I don't understand the roles of the various exception mappings etc.
Overall it boils down to two concerns:
- having an implicit await behavior at all
- how many flavours there are in the world of that
For me, there can only be implicit behavior if there's a canonical expectation and/or spec for it. The canonical impl and the associated test snippets on fsssnip provide specific behavior under cancellation too, and that's pretty critical for me
I guess this tells us we need to get this resolved in core because having TaskSeq, IcedTasks and/or here all have stuff that's not AwaitTaskCorrect and/or some equivalent with targeted improvements is bad news. I'd so love to stop having to cart it around github.com/jet and FsAwsDdb and/or ever have to compare implementations ever again!
(Ironically, for my work project, having FsToolkit host AwaitTaskCorrect would be the perfect solution, but obviously having the world take a dependency on this entire lib for a correct awaittask impl is a step too far....)
I think my conclusion/proposal is thus that the thing I'd do is remove the pieces that are bracketed with #if !FABLE_COMPILER for V5:
https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/dccdbbc629a15dbb1ab8c795ca515364cc81bfc7/src/FsToolkit.ErrorHandling/AsyncResultCE.fs#L201-L217 https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/dccdbbc629a15dbb1ab8c795ca515364cc81bfc7/src/FsToolkit.ErrorHandling/AsyncResultCE.fs#L135-L142
Luckily I'm not the maintainer of the library though, as I'd anticipate people immediately asking me: OK, so where it this wonderful AwaitTaskCorrect thing of which you speak so?
There's also the question of what to replace the usage of Async.AwaitTask in the TryFinallyAsync impl https://github.com/demystifyfp/FsToolkit.ErrorHandling/blob/dccdbbc629a15dbb1ab8c795ca515364cc81bfc7/src/FsToolkit.ErrorHandling/AsyncResultCE.fs#L57-L93
On the other hand, having a free-standing net6.0 lib with an asyncEx including neat support for starting cancellableTasks (and awaiting Task/Task<'T>) would be very nice indeed....
And then it begins... you add an asyncExResult eh - this stuff keeps giving eh ;) @TheAngryByrd