akka.net icon indicating copy to clipboard operation
akka.net copied to clipboard

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

Open ingted opened this issue 1 year ago • 3 comments

======================================== 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 avatar Nov 28 '22 08:11 ingted

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 avatar Nov 28 '22 08:11 ingted

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 avatar Nov 28 '22 08:11 ingted

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)

ingted avatar Nov 28 '22 09:11 ingted