Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MailboxProcessor strange behavior #18045

Open
flarelee opened this issue Nov 22, 2024 · 2 comments
Open

MailboxProcessor strange behavior #18045

flarelee opened this issue Nov 22, 2024 · 2 comments

Comments

@flarelee
Copy link

Please provide a succinct description of the issue.

MailboxProcessor agent did not receive message and run forever!

Expected behavior

After dispose agent, agent should stop.

Actual behavior

agent run forever.

Known workarounds

add some sleep time, it may be work.

Related information

Provide any related information (optional):

  • Windows 10
  • .NET Framework 4.7
  • Visual Studio 2022
// print text to console
let printText = 
    let agent = MailboxProcessor.Start(fun inbox -> async {
        while true do
            let! msg = inbox.Receive()
            printfn "%s: %s" (System.DateTime.Now.ToLongTimeString()) msg

    })
    agent.Post

let teskMailboxLeak(withSleep) =
    use agent = new MailboxProcessor<int>(fun inbox -> async {
        let mutable i = 0
        while i >= 0 do
            let! (msg: int option) = inbox.TryReceive(2000)

            match msg with
            | None ->  
                sprintf "idle %d" i |> printText
                i <- i + 1
            | Some x -> 
                sprintf "receive %d" x |> printText

            let! cts = Async.CancellationToken
            if cts.IsCancellationRequested then 
                i <- -1
        printText "exit"
    })

    agent.Start()
    agent.Post(3) 
    agent.Post(4)
    agent.Post(5)
    
    if withSleep then
        System.Threading.Thread.Sleep(4000)
    //! with sleep, everything works as expected.

    //! without sleep
    //! the agent run forever! 
    //! f# 4.7.2 can receive numbers, f# 8 or 9 can not!
    //! Is it normal?

[<EntryPoint>]
let main argv =
    let withSleep = false
    printText ("start " + if withSleep then "with sleep" else "without sleep")
    teskMailboxLeak(withSleep)

    //System.Threading.Thread.Sleep(5000)
    // force GC
    for i = 1 to 5 do
        sprintf "GC %d" i |> printText
        System.GC.Collect(System.GC.MaxGeneration) |> ignore
        System.GC.WaitForFullGCComplete() |> ignore
        System.Threading.Thread.Sleep(1000)

    System.Console.ReadLine() |> ignore
    0 // return an integer exit code

log without sleep

08:50:48: start without sleep
08:50:48: GC 1
08:50:49: GC 2
08:50:50: idle 0
08:50:50: GC 3
08:50:51: GC 4
08:50:52: idle 1
08:50:52: GC 5
08:50:54: idle 2
08:50:56: idle 3
08:50:58: idle 4
08:51:00: idle 5
08:51:02: idle 6
08:51:04: idle 7
08:51:06: idle 8
08:51:08: idle 9
08:51:10: idle 10
08:51:12: idle 11
08:51:14: idle 12
08:51:16: idle 13
08:51:18: idle 14
08:51:20: idle 15
08:51:23: idle 16
08:51:25: idle 17
08:51:27: idle 18
08:51:29: idle 19
...

log with sleep

09:18:30: start with sleep
09:18:30: receive 3
09:18:30: receive 4
09:18:30: receive 5
09:18:32: idle 0
09:18:34: GC 1
09:18:34: idle 1
09:18:35: GC 2
09:18:36: GC 3
09:18:37: GC 4
09:18:38: GC 5
@majocha
Copy link
Contributor

majocha commented Nov 22, 2024

Yes. this is counterintuitive. MailboxProcessor does not hold any internal CancellationTokenSource that it can cancel on Dispose.
Internally it just does a Async.Start. I guess if there is no stopping condition in the body, it will run even after disposal, or wait forever on a disposed handle.

We could probably add cancellation on Dispose utilizing a linked token source. Would it be a breaking change?

@Martin521
Copy link
Contributor

#6285, #17849, #11282

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: New
Development

No branches or pull requests

3 participants