Horusiath / Akkling

Experimental F# typed API for Akka.NET
Apache License 2.0
225 stars 45 forks source link

An actor doesn't stop after return Stop effect #135

Closed ingted closed 3 years ago

ingted commented 3 years ago
open Akka.FSharp
open Akka.Actor

let actorSystem = System.create "NotificationWorker" (Configuration.defaultConfig())

let pnm3 =
    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let rec loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    if msg = "stop" then
                        printfn "stop"
                        return Akkling.Actors.Stop
                    printfn "123"   
                    }
                loop0 ())

pnm3.Tell("stop", ActorRefs.Nobody)
pnm3.Tell("", ActorRefs.Nobody)

I would expect after telling "stop", the actor would be stopped, but it still accepts messages.

> 
stop
123
val it : unit = ()

> 
123
val it : unit = ()

The second 123 should not print, should it?

ingted commented 3 years ago

According to the wiki,

Stop will cause current actor to immediately stop. It will send Terminated message to all monitoring actors. All messages waiting in actor mailbox or stash will be lost.
ingted commented 3 years ago

PS. What's the difference between

    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    if msg = "stop" then
                        printfn "stop"
                        return Akkling.Actors.Stop
                    printfn "123"                

                    })

and

    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let rec loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    if msg = "stop" then
                        printfn "stop"
                        return Akkling.Actors.Stop
                    printfn "123"                

                    }
                loop0 ())

or

    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let rec loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    if msg = "stop" then
                        printfn "stop"
                        return Akkling.Actors.Stop
                    printfn "123"                
                    return! loop0 ()                  
                    }
                loop0 ())

All of them could tell again and again...?!

ingted commented 3 years ago

But for Akka.FSharp

let pnm =
    spawn actorSystem "procs-node-mon" <| 
        fun (mb:Actor<string>) ->
            let rec loop0 () = actor {
                let! _ = mb.Receive ()

                printfn "123"

                return! loop0 ()                            
                }
            loop0 ()

could tell again and agin (every tell would print 123) but

let pnm =
    spawn actorSystem "procs-node-mon" <| 
        fun (mb:Actor<string>) ->
            let rec loop0 () = actor {
                let! _ = mb.Receive ()

                printfn "123"

                }
            loop0 ()

could only tell once (only one 123 would be printed)

ingted commented 3 years ago

For the first question, I found that

    Akkling.Spawn.spawn actorSystem ("procs-node-mon3" + System.Guid.NewGuid().ToString()) (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    printfn "s: %s" msg 
                    if msg = "stop" then
                        printfn "stop"
                        //return Akkling.Actors.ActorEffect.Stop
                        return Akkling.Actors.Stop
                    //else
                    printfn "123"                

                    }
                loop0 ())

This would make stop unable to work

    Akkling.Spawn.spawn actorSystem ("procs-node-mon3" + System.Guid.NewGuid().ToString()) (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    printfn "s: %s" msg 
                    if msg = "stop" then
                        printfn "stop"
                        //return Akkling.Actors.ActorEffect.Stop
                        return Akkling.Actors.Stop
                    else
                        printfn "123"                

                    }
                loop0 ())

This make stop works...

ingted commented 3 years ago

Seems like expressions following "return" offset the termination message of ActorEffect.Stop

Horusiath commented 3 years ago

@ingted When comparing Akka.FSharp and Akkling, your computation expressions are not equivalent:

// Akka.FSharp
let pnm =
    spawn actorSystem "procs-node-mon" <|  fun (mb:Actor<string>) ->
        let rec loop0 () = actor {
           let! _ = mb.Receive ()
           printfn "123" }
        loop0 ()

Here, there's no loop, it's just catching a message and finishing (no tail recursion occurs).

// Akkling
spawn actorSystem ("procs-node-mon3" + System.Guid.NewGuid().ToString()) (props <|  fun (mb:Actor<string>) ->
  let loop0 () = actor {
    let! msg = mb.Receive ()
    printfn "s: %s" msg 
    if msg = "stop" then
      printfn "stop"
      return Stop
    //else
      printfn "123"  }
  loop0 ())

Here, since else is commented out, it means, that if is not expression but the statement - which means that it doesn't return any value. And since F# doesn't have early returns, it means that Stop effect will be implicitly ignored. Why return is even allowed in this scope, I have no idea, looks like the issue with F# compiler.

ingted commented 3 years ago

Hi @Horusiath,

Thanks for the answers! (I learned it!)

The return! question is still not answered, would you mind talk a little more?

    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let rec loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    printfn "123"                
                    return! loop0 ()                  
                    }
                loop0 ())

    Akkling.Spawn.spawn actorSystem "procs-node-mon3" (
        Akkling.Props.props <| 
            fun (mb:Akkling.Actors.Actor<string>) ->
                let rec loop0 () = Akkling.ComputationExpressions.actor {
                    let! msg = mb.Receive ()
                    printfn "123"                
                    //return! loop0 ()                  
                    }
                loop0 ())

Both these two could tell again and again(always print)... (even the return! commented)

. . . . . .

========================================================

    spawn actorSystem "procs-node-mon" <|  fun (mb:Actor<string>) ->
        let rec loop0 () = actor {
           let! _ = mb.Receive ()
           printfn "123" }
        loop0 ()

And with Akka.FSharp spawn, without return! loop0(), it could only tell once (the tell after the first one will not print)

Horusiath commented 3 years ago

If I remember correctly actor {..} computation expression in Akka.FSharp either has not Zero member defined, so it must always return something at some point, or uses Zero as default returned value, in which case loop will just stop executing.

Unlike Akka.FSharp, Akkling operates on effects as return types, and Akkling's actor computation expression defines Zero member as ignored effect - meaning, nothing will change and the last loop roll will be used for the next received message.

ingted commented 3 years ago

Totally understood!! Thank you!