akkadotnet / akka.net

Canonical actor model implementation for .NET with local + distributed actors in C# and F#.
http://getakka.net
Other
4.66k stars 1.04k forks source link

The IActorRef of a child dies or restarts could not be retrieve by mailbox.Sender() in parent #6272

Open ingted opened 1 year ago

ingted commented 1 year ago

======================================== Version of Akka.NET? 1.5.0-Alpha Which Akka.NET Modules? Akka or Akka.FSharp

The IActorRef of a child dies or restarts could not be retrieve by mailbox.Sender() in parent

======================================== Reproduce

Example Code File Location

#if INTERACTIVE
#r @"nuget: Akka"
#r @"nuget: Akka.FSharp"
#endif

open System
open Akka.Actor
open Akka.Configuration
open Akka.FSharp

type CustomException() =
    inherit Exception()

type Message =
    | Echo of string
    | Crash

let system = System.create "system" (Configuration.defaultConfig())
    // create parent actor to watch over jobs delegated to it's child
let parent = 
    spawnOpt system "parent" 
        <| fun parentMailbox ->
            // define child actor
            let child = 
                    spawn parentMailbox "child" <| fun childMailbox ->
                        childMailbox.Defer (fun () -> printfn "Child stopping")
                        printfn "Child started"
                        let rec childLoop() = 
                            actor {
                                let! msg = childMailbox.Receive()
                                match msg with
                                | Echo info -> 
                                    // respond to original sender
                                    let response = "Child " + (childMailbox.Self.Path.ToStringWithAddress()) + " received: "  + info
                                    childMailbox.Sender() <! response
                                | Crash -> 
                                    // log crash request and crash
                                    //printfn "Child %A received crash order" (childMailbox.Self.Path)
                                    raise (CustomException())
                                return! childLoop()
                            }
                        childLoop()
            // define parent behavior
            let rec parentLoop() =
                actor {
                    let! (msg: Message) = parentMailbox.Receive()
                    child.Forward(msg)  // forward all messages through

                    //###########################################
                    //###### Notice this sender()

                    let sender = parentMailbox.Sender()
                    if sender <> null then
                        printfn "sender: %A" sender

                    //###########################################
                    return! parentLoop()
                }
            parentLoop()
        // define supervision strategy
        <| [ SpawnOption.SupervisorStrategy (
                // restart on Custom Exception, default behavior on all other exception types
                Strategy.OneForOne(fun e ->
                match e with
                | :? CustomException -> Directive.Restart 
                | _ -> SupervisorStrategy.DefaultDecider.Decide(e)
                //Directive.Restart
                )
        )  ]

parent <! Crash

=> See "Notice this sender()"

Expected behavior

The parentMailbox.Sender() should give us the IActorRef of child

Actual behavior

The parentMailbox.Sender() is null

Environment

Windows Server 2019 + .NET 7.0

ingted commented 1 year ago

According to fault-tolerance

If the strategy is declared inside the supervising actor (as opposed to within a companion object) its decider has access to all internal state of the actor in a thread-safe fashion, including obtaining a reference to the currently failed child (available as the Sender of the failure message).

ingted commented 1 year ago

Workaround:

derive the SupervisorStrategy class and put the error handler inside it...

type SS () =
    inherit SupervisorStrategy ()   
    override u.Decider :IDecider = 
        //Unchecked.defaultof<IDecider>
        SupervisorStrategy.DefaultDecider
    override u.Handle(child:IActorRef, exn:Exception) =
        //Unchecked.defaultof<Directive>
        Directive.Restart
    override u.ProcessFailure(
        context:IActorContext, 
        restart:bool, 
        child:IActorRef, 
        cause:Exception, 
        stats:ChildRestartStats, 
        children:IReadOnlyCollection<ChildRestartStats>) =
            printfn "====> %A" child
    override u.HandleChildTerminated(
        actorContext:IActorContext,  
        child:IActorRef, 
        children:IEnumerable<IInternalActorRef>) = 
            printfn "====> %A" child
    override u.ToSurrogate(system:ActorSystem) =
        Unchecked.defaultof<ISurrogate>
<| [ SpawnOption.SupervisorStrategy (SS ()) ]
ingted commented 1 year ago

It seems like we can override the method to send the IActorRef back to parent's mailbox... But this is not convinient like the Sender() method... (illustrated in the document)