protoactor-go icon indicating copy to clipboard operation
protoactor-go copied to clipboard

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

Open alex023 opened this issue 8 years ago • 3 comments

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
}

alex023 avatar Aug 28 '17 07:08 alex023

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

rogeralsing avatar Sep 02 '17 13:09 rogeralsing

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!

alex023 avatar Sep 02 '17 17:09 alex023

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.

bryanpkc avatar Sep 22 '17 13:09 bryanpkc