dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.93k stars 787 forks source link

Passing a CancellationToken into a MailboxProcessor breaks them when they are initialized in a Blazor component. #15272

Open mrakgr opened 1 year ago

mrakgr commented 1 year ago

I have a Blazor component which looks like this:

type ViewComponent() as this =
    inherit ComponentBase()

    let token_source = new CancellationTokenSource()
    let cancellation_token = token_source.Token

    let mb init update =
        let mb =
            new MailboxProcessor<_>(fun mb -> async {
                let mutable model = init
                while true do
                    let! msg = mb.Receive()
                    model <- update msg model mb.Post
                },cancellation_token) // Removing the cancellation token makes it work.
        mb.Start()
        mb

    let srv = mb () (fun x () _ -> printfn "%s" x)

    do srv.Post("Hello from view")
    do this.Say("123456789")

    member _.Say(x) = srv.Post x

And I have a Blazor index page with a button. It inherits from it.

@page "/"

@inherits Fun.ViewComponent

<button @onclick="Hello" style="font-size: xxx-large">
    Click me.
</button>

@code
{
    private void Hello()
    {
        Say("qwe");
    }
}

Repro steps

Run the project and click on the button.

Expected behavior

Look in the browser console. Hello from view will get printed, but qwe won't when you click on it. Neither will 123456789

Actual behavior

I expected all of them would be printed.

Known workarounds

Remove the cancellation token. Then the mailbox works as expected.

Related information

Provide any related information (optional):

Min repro: https://github.com/mrakgr/CFR-In-Fable/tree/mailbox_bug_dotnet7 Min repro: https://github.com/mrakgr/CFR-In-Fable/tree/mailbox_bug_dotnet8_preview3

I've verified that this issue is present in the latest .NET 7 and .NET 8 Preview 3.

mrakgr commented 1 year ago

Let me just note, this error is not due to the cancellation token being cancelled.

mrakgr commented 1 year ago

Also, I am running some heavy computation on the mailbox and observing that the UI is frozen. Is it all running on a single thread, in Blazor WASM?

Edit: Yeah, it is all on a single thread and multithreading support for Blazor is experimental. I'll try enabling it, and am wondering if it would be possible to make use of it with the MailboxProcessor.

vzarytovskii commented 1 year ago

Not entirely sure how blazor works or what specifics its scheduler has. F# compiler, when building a library will just ouput MSIL, like iit normally does. I presume WASM is then compiled (transpiled?) from it?

Maybe @maraf @SteveSandersonMS or @mkArtakMSFT can help with it or suggest someone who can?

maraf commented 1 year ago

I'm reallly not sure about F#, but as far as I can read the mailbox implementation, it does different thing based on whether the cancellation token is passed or not https://github.com/dotnet/fsharp/blob/main/src/FSharp.Core/mailbox.fs#L86 (not that even passing the default value of Async.DefaultCancellationToken uses the else branch)

And this different thing than touches ThreadPool https://github.com/dotnet/fsharp/blob/main/src/FSharp.Core/async.fs#L1887.

@lambdageek Will this code work with multithreaded runtime?