The framework needs to determine the nil structure implementation of actor.Actor
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
}
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
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!
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.