asynkron / protoactor-go

Proto Actor - Ultra fast distributed actors for Go, C# and Java/Kotlin
http://proto.actor
Apache License 2.0
5.08k stars 522 forks source link

The framework needs to determine the nil structure implementation of actor.Actor #168

Open alex023 opened 7 years ago

alex023 commented 7 years ago

Problem Description:

The go interface structure involves two basic properties: (type, data). At run time, it may be (nil, nil), and mayby be (actor.Actor, nil). Once the latter situation, it is likely that the system will keep sending *actor.Stopping message , even if we add SUPERVISION.

Code example:

type MessageCreate struct{}
type ChildActor struct {
    name string
}

func (da *ChildActor) Receive(ctx actor.Context) {
    fmt.Printf("childactor receive message [%T], [%+v]. \n", ctx.Message(), ctx.Message())

    //cause panic if childactor is nil
    //because it'will receive *actor.Stoping
    fmt.Printf("it's maby panic.because i want visit my field name=%v /n", da.name)
}
func NewChildActor(name string) *ChildActor {
    //return nil for test
    return nil
}

type FatherActor struct {
}

func NewFatherActor() actor.Actor {
    return &FatherActor{}
}
func (da *FatherActor) Receive(ctx actor.Context) {
    switch ctx.Message().(type) {
    case *actor.Started:
        fmt.Println("Starting, initialize actor here")
    case *actor.Stopping:
        fmt.Println("Stopping, actor is about to shut down")
    case *actor.Stopped:
        fmt.Println("Stopped, actor and its children are stopped")
    case *actor.Restarting:
        fmt.Println("Restarting, actor is about to restart")
    case *MessageCreate:
        fmt.Println("create child actor begin!")

        //In this scenario, the Supervisor's maxNrOfRetries can not take effect
        decider := func(reason interface{}) actor.Directive {
            fmt.Println("handling failure for child")
            return actor.StopDirective
        }
        supervisor := actor.NewOneForOneStrategy(3, time.Second, decider)
        porps := actor.FromInstance(NewChildActor("alex_023")).WithSupervisor(supervisor)
        //use actor.spawn,because want to use supervisor
        actor.Spawn(porps)

        fmt.Println("create child actor over!")
    }
}
func main() {
    props := actor.FromProducer(NewFatherActor)
    pid := actor.Spawn(props)
    time.Sleep(time.Second)
    pid.Tell(&MessageCreate{})

    console.ReadLine()
}

we will get output,and soon, the log data will become very large

2017/08/31 03:48:24 [MAILBOX] [ACTOR] Recovering actor="nonhost/$2" reason="runtime error: invalid memory address or nil pointer dereference" stacktrace="main.(*ChildActor).Receive:21" 
2017/08/31 03:48:24 [ACTOR] [SUPERVISION] actor="nonhost/$2" directive="StopDirective" reason="runtime error: invalid memory address or nil pointer dereference" 
2017/08/31 03:48:24 [MAILBOX] [ACTOR] Recovering actor="nonhost/$2" reason="runtime error: invalid memory address or nil pointer dereference" stacktrace="main.(*ChildActor).Receive:21" 
2017/08/31 03:48:24 [ACTOR] [SUPERVISION] actor="nonhost/$2" directive="StopDirective" reason="runtime error: invalid memory address or nil pointer dereference" 
2017/08/31 03:48:24 [MAILBOX] [ACTOR] Recovering actor="nonhost/$2" reason="runtime error: invalid memory address or nil pointer dereference" stacktrace="main.(*ChildActor).Receive:21" 
2017/08/31 03:48:24 [ACTOR] [SUPERVISION] actor="nonhost/$2" directive="StopDirective" reason="runtime error: invalid memory address or nil pointer dereference" 
2017/08/31 03:48:24 [MAILBOX] [ACTOR] Recovering actor="nonhost/$2" reason="runtime error: invalid memory address or nil pointer dereference" stacktrace="main.(*ChildActor).Receive:21" 
2017/08/31 03:48:24 [ACTOR] [SUPERVISION] actor="nonhost/$2" directive="StopDirective" reason="runtime error: invalid memory address or nil pointer dereference" 
......

Suggest:

In business development, it is difficult to avoid creating a null pointer actor object, which in turn causes the system to fail.(without kotlin's syntax). Therefore, it is necessary to add judgment and improve robustness in the framework. Perhaps, refactor incarnateActor in the local_context.go is a good choice:

func (ctx *localContext) incarnateActor() {
    pid := ctx.producer()
    //add actor interface not nil judgement
    if pid == nil || reflect.ValueOf(pid).IsNil() {
        panic("pid is nil")
    }
    ctx.restarting = false
    ctx.stopping = false
    ctx.actor = pid
    ctx.receive = pid.Receive
}
rogeralsing commented 7 years ago

reflect.ValueOf(pid).IsNil()

How much overhead will this add? I am all for such change, but we should be careful with how much this kind of changes affect spawning performance

alex023 commented 7 years ago

There are several ways to create an object instance, like FromProducer , FromInstance , WithProducer . At present, my proposal to intercept the occurrence of the error, but also does bring the performance overhead, which will make my MPB (CPU i5) performance overhead of about 7ns each time. Look for a better solution!

bryanpkc commented 7 years ago

I think this proposal is missing the point. incarnateActor shouldn't care whether what the actor is, whether it is nil, a pointer, a struct value, or a function. The problem described above can happen in any non-nil actor that insists on panicking. I think the real solution is to make the supervisor give up trying to stop the child.