diff --git a/docs/best_practices.md b/docs/best_practices.md index 956d7d0..0f51b1f 100644 --- a/docs/best_practices.md +++ b/docs/best_practices.md @@ -24,18 +24,66 @@ While the general rule is to avoid `sync` package usage in actor-based code, the Workers should always respond to `Context.Done()` channel and return `actor.WorkerEnd` status in order to end it's actor. As a rule of thumb it's advised to always list this case first since it should be included in every `select` statement. ```go -func (w *worker) DoWork(ctx actor.Context) actor.WorkerStatus { +func (w *fooWorker) DoWork(ctx actor.Context) actor.WorkerStatus { + select { + case <-ctx.Done(): // <----------------------- handle ctx.Done() first + return actor.WorkerEnd + + case msg := <-w.mbx.ReceiveC(): + handleFoo(msg) + } +} +``` + +## Check channel closed indicator in `DoWork` + +Every case statement in `DoWork` should handle case when channel is closed. In these cases worker should end execution; or it can perform any other logic that is necessery. + +```go +func (w *fooWorker) DoWork(ctx actor.Context) actor.WorkerStatus { select { case <-ctx.Done(): return actor.WorkerEnd - case <-w.fooMbx.ReceiveC(): - handleFoo() - case <-w.barMbx.ReceiveC(): - handleBar() + + case msg, ok := <-w.mbx.ReceiveC(): + if !ok { // <----------------------- handle channel close (mailbox stop) case + return actor.WorkerEnd + } + + handleFoo(msg) } } ``` - + +## Combine multiple actors to singe actor + +`actor.Combine(...)` is vary handy to combine multiple actors to single actor. + +```go +type fooActor struct { + actor.Actor + mbx actor.Mailbox[any] + ... +} + +func NewFooActor() *fooActor { + mbx := actor.NewMailbox[any]() + + a1 := actor.New(&fooWorker{mbx: mbx}) + a2 := actor.New(&fooWorker{mbx: mbx}) + + return &fooActor{ + mbx: mbx, + Actor: actor.Combine(mbx, a1, a2).Build() // <------- combine all actors to single actor and initialize embeded actor of fooActor struct. + } // when calling fooActor.Start() it will start all actors at once. +} + +func (f *fooActor) OnMessage(ctx context.Context, msg any) error { + return f.mbx.Send(ctx, msg) +} + +``` + --- -`// page is not yet complete` \ No newline at end of file +This page is not yet complete. \ No newline at end of file diff --git a/docs/common_hurdles.md b/docs/common_hurdles.md index b818acd..4ec9d87 100644 --- a/docs/common_hurdles.md +++ b/docs/common_hurdles.md @@ -8,6 +8,34 @@ One of the most common hurdles is the case where actors are not started. This is Never forget that `actor.Mailbox` is also an actor, and it needs to be started. +**Embeded Actor interface is overriden** + +When embeding `actor.Actor` interface make sure not to override methods of this interface in structre that has embeded it. Otherwise make sure to call embeded actor's Start() and Stop() methods. + +```go +type fooActor struct { + actor.Actor + ... +} + +func NewFooActor() *fooActor { + return &fooActor{ + Actor: actor.New(&fooWorker{...}) + ... + } +} + +func(f *fooActor) Start() { // <--- warning: calling fooActor.Start() will override fooActor.Actor.Start() method. + ... // therfore calling this method will not execute worker that was itended +} // to be excuted with fooActor. + // if this method is necessery then make sure to call `f.Actor.Start()` manually here. + +func(f *fooActor) Stop() { // <---- similar problem as described above. + ... +} + +``` + ## Default case is undesirable Workers should always block when there isn't anything to work on; therefore, their `select` statements shouldn't have a `default` case. If workers do not block, they will simply waste computation cycles. @@ -32,4 +60,4 @@ func (w *consumeWorker) DoWork(ctx actor.Context) actor.WorkerStatus { --- -// Your contribution is valuable; if you have encountered any challenges, please share your experiences. \ No newline at end of file +Your contribution is valuable; if you have encountered any challenges, please share your experiences. \ No newline at end of file