Repository: Tochemey/goakt
Pull request #548 (2024-12-05 18:26:48)

refactor: refactor internal engine

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Tochemey/goakt/pull/548?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) Report
Attention: Patch coverage is `78.97727%` with `37 lines` in your changes missing coverage. Please review.
> Project coverage is 86.92%. Comparing base [(`ec64ba0`)](https://app.codecov.io/gh/Tochemey/goakt/commit/ec64ba07fedfbef3795858d11eae30809f39dbcf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) to head [(`14f06ec`)](https://app.codecov.io/gh/Tochemey/goakt/commit/14f06ec8f6d86876c8fd85e403510d255005a9ca?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).

| [Files with missing lines](https://app.codecov.io/gh/Tochemey/goakt/pull/548?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Patch % | Lines |
|---|---|---|
| [actors/actor\_system.go](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&filepath=actors%2Factor_system.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FjdG9yX3N5c3RlbS5nbw==) | 71.23% | [17 Missing and 4 partials :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/janitor.go](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&filepath=actors%2Fjanitor.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2phbml0b3IuZ28=) | 82.85% | [5 Missing and 1 partial :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/system\_guardian.go](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&filepath=actors%2Fsystem_guardian.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3N5c3RlbV9ndWFyZGlhbi5nbw==) | 68.75% | [4 Missing and 1 partial :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/root\_guardian.go](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&filepath=actors%2Froot_guardian.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3Jvb3RfZ3VhcmRpYW4uZ28=) | 81.25% | [3 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/user\_guardian.go](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&filepath=actors%2Fuser_guardian.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3VzZXJfZ3VhcmRpYW4uZ28=) | 88.23% | [2 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/548?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #548      +/-   ##
==========================================
- Coverage   87.03%   86.92%   -0.12%     
==========================================
  Files          50       53       +3     
  Lines        4937     5040     +103     
==========================================
+ Hits         4297     4381      +84     
- Misses        503      521      +18     
- Partials      137      138       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Tochemey/goakt/pull/548?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).


Patch:
diff --git a/actors/actor_system.go b/actors/actor_system.go
index 15d1b52..f7bcf5d 100644
--- a/actors/actor_system.go
+++ b/actors/actor_system.go
@@ -150,8 +150,6 @@ type ActorSystem interface {
 	handleRemoteTell(ctx context.Context, to *PID, message proto.Message) error
 	// setActor sets actor in the actor system actors registry
 	broadcastActor(actor *PID)
-	// supervisor return the system supervisor
-	getSupervisor() *PID
 	// getPeerStateFromCache returns the peer state from the cache
 	getPeerStateFromCache(address string) (*internalpb.PeerState, error)
 	// reservedName returns reserved actor's name
@@ -160,6 +158,11 @@ type ActorSystem interface {
 	getCluster() cluster.Interface
 	// tree returns the actors tree
 	tree() *pidTree
+
+	getRootGuardian() *PID
+	getSystemGuardian() *PID
+	getUserGuardian() *PID
+	getJanitor() *PID
 }
 
 // ActorSystem represent a collection of actors on a given node
@@ -240,8 +243,11 @@ type actorSystem struct {
 	clusterConfig          *ClusterConfig
 	rebalancingChan        chan *cluster.Event
 
-	supervisor *PID
-	rebalancer *PID
+	rebalancer     *PID
+	rootGuardian   *PID
+	userGuardian   *PID
+	systemGuardian *PID
+	janitor        *PID
 }
 
 // enforce compilation error when all methods of the ActorSystem interface are not implemented
@@ -463,8 +469,8 @@ func (x *actorSystem) Spawn(ctx context.Context, name string, actor Actor, opts
 	}
 
 	// add the given actor to the tree and supervise it
-	_ = x.actors.AddNode(NoSender, pid)
-	x.actors.AddWatcher(pid, x.supervisor)
+	_ = x.actors.AddNode(x.userGuardian, pid)
+	x.actors.AddWatcher(pid, x.janitor)
 	x.broadcastActor(pid)
 	return pid, nil
 }
@@ -492,8 +498,8 @@ func (x *actorSystem) SpawnNamedFromFunc(ctx context.Context, name string, recei
 		return nil, err
 	}
 
-	_ = x.actors.AddNode(NoSender, pid)
-	x.actors.AddWatcher(pid, x.supervisor)
+	_ = x.actors.AddNode(x.userGuardian, pid)
+	x.actors.AddWatcher(pid, x.janitor)
 	x.broadcastActor(pid)
 	return pid, nil
 }
@@ -554,7 +560,7 @@ func (x *actorSystem) ReSpawn(ctx context.Context, name string) (*PID, error) {
 		// returns an error if when the parent does not exist which was taken care of in the
 		// lines above
 		_ = x.actors.AddNode(parent, pid)
-		x.actors.AddWatcher(pid, x.supervisor)
+		x.actors.AddWatcher(pid, x.janitor)
 		return pid, nil
 	}
 
@@ -703,8 +709,11 @@ func (x *actorSystem) Start(ctx context.Context) error {
 	x.started.Store(true)
 	if err := errorschain.
 		New(errorschain.ReturnFirst()).
-		AddError(x.spawnSupervisor(ctx)).
+		AddError(x.spawnRootGuardian(ctx)).
+		AddError(x.spawnSystemGuardian(ctx)).
+		AddError(x.spawnUserGuardian(ctx)).
 		AddError(x.spawnRebalancer(ctx)).
+		AddError(x.spawnJanitor(ctx)).
 		AddError(x.enableRemoting(ctx)).
 		AddError(x.enableClustering(ctx)).
 		Error(); err != nil {
@@ -714,7 +723,6 @@ func (x *actorSystem) Start(ctx context.Context) error {
 	}
 
 	x.scheduler.Start(ctx)
-	go x.janitor()
 
 	x.logger.Infof("%s started..:)", x.name)
 	return nil
@@ -767,24 +775,13 @@ func (x *actorSystem) Stop(ctx context.Context) error {
 		close(x.rebalancingChan)
 	}
 
-	// stop the supervisor actor
-	if err := x.getSupervisor().Shutdown(ctx); err != nil {
+	if err := x.getRootGuardian().Shutdown(ctx); err != nil {
 		x.reset()
 		x.logger.Errorf("%s failed to shutdown cleanly: %w", x.name, err)
 		return err
 	}
 
-	// remove the supervisor from the actors list
-	x.actors.DeleteNode(x.supervisor)
-
-	// shutdown all actors in the system
-	for _, actor := range x.Actors() {
-		if err := actor.Shutdown(ctx); err != nil {
-			x.reset()
-			x.logger.Errorf("%s failed to shutdown cleanly: %w", x.name, err)
-			return err
-		}
-	}
+	x.actors.DeleteNode(x.getRootGuardian())
 
 	x.reset()
 	x.logger.Infof("%s shuts down successfully", x.name)
@@ -1116,12 +1113,36 @@ func (x *actorSystem) handleRemoteTell(ctx context.Context, to *PID, message pro
 	return Tell(ctx, to, message)
 }
 
-// getSupervisor return the system supervisor
-func (x *actorSystem) getSupervisor() *PID {
+// getRootGuardian returns the system root guardian
+func (x *actorSystem) getRootGuardian() *PID {
+	x.locker.Lock()
+	rootGuardian := x.rootGuardian
+	x.locker.Unlock()
+	return rootGuardian
+}
+
+// getUserGuardian returns the user guardian
+func (x *actorSystem) getUserGuardian() *PID {
+	x.locker.Lock()
+	userGuardian := x.userGuardian
+	x.locker.Unlock()
+	return userGuardian
+}
+
+// getSystemGuardian returns the system guardian
+func (x *actorSystem) getSystemGuardian() *PID {
+	x.locker.Lock()
+	systemGuardian := x.systemGuardian
+	x.locker.Unlock()
+	return systemGuardian
+}
+
+// getJanitor returns the system janitor
+func (x *actorSystem) getJanitor() *PID {
 	x.locker.Lock()
-	supervisor := x.supervisor
+	janitor := x.janitor
 	x.locker.Unlock()
-	return supervisor
+	return janitor
 }
 
 // getPeerStateFromCache returns the peer state from the cache
@@ -1264,40 +1285,6 @@ func (x *actorSystem) reset() {
 	x.actors.Reset()
 }
 
-// janitor time to time removes dead actors from the system
-// that helps free non-utilized resources
-func (x *actorSystem) janitor() {
-	x.logger.Info("janitor has started...")
-	ticker := time.NewTicker(x.janitorInterval)
-	tickerStopSig := make(chan types.Unit, 1)
-	go func() {
-		for {
-			select {
-			case <-ticker.C:
-				for _, actor := range x.Actors() {
-					if !actor.IsRunning() {
-						x.logger.Infof("janitor removing actor=%s from system", actor.ID())
-						x.actors.DeleteNode(actor)
-						if x.InCluster() {
-							if err := x.cluster.RemoveActor(context.Background(), actor.Address().Name()); err != nil {
-								x.logger.Error(err.Error())
-								// TODO: stop or continue
-							}
-						}
-					}
-				}
-			case <-x.stopGC:
-				tickerStopSig <- types.Unit{}
-				return
-			}
-		}
-	}()
-
-	<-tickerStopSig
-	ticker.Stop()
-	x.logger.Info("janitor has stopped...")
-}
-
 // replicationLoop publishes newly created actor into the cluster when cluster is enabled
 func (x *actorSystem) replicationLoop() {
 	for actor := range x.actorsChan {
@@ -1416,7 +1403,7 @@ func (x *actorSystem) rebalancingLoop() {
 			}
 
 			message := &internalpb.Rebalance{PeerState: peerState}
-			if err := x.supervisor.Tell(ctx, x.rebalancer, message); err != nil {
+			if err := x.systemGuardian.Tell(ctx, x.rebalancer, message); err != nil {
 				x.logger.Error(err)
 			}
 		}
@@ -1594,18 +1581,69 @@ func (x *actorSystem) setHostPort() error {
 	return nil
 }
 
-// spawnSupervisor creates the system supervisor
-func (x *actorSystem) spawnSupervisor(ctx context.Context) error {
+// spawnRootGuardian creates the root guardian
+func (x *actorSystem) spawnRootGuardian(ctx context.Context) error {
+	var err error
+	actorName := x.reservedName(rootGuardianType)
+	x.rootGuardian, err = x.configPID(ctx, actorName, newRootGuardian())
+	if err != nil {
+		return fmt.Errorf("actor=%s failed to start root guardian: %w", actorName, err)
+	}
+
+	// rootGuardian is the root node of the actors tree
+	_ = x.actors.AddNode(NoSender, x.rootGuardian)
+	return nil
+}
+
+// spawnSystemGuardian creates the system guardian
+func (x *actorSystem) spawnSystemGuardian(ctx context.Context) error {
+	var err error
+	actorName := x.reservedName(systemGuardianType)
+	x.systemGuardian, err = x.configPID(ctx,
+		actorName,
+		newSystemGuardian(),
+		WithSupervisor(NewStopDirective()))
+	if err != nil {
+		return fmt.Errorf("actor=%s failed to start system guardian: %w", actorName, err)
+	}
+
+	// systemGuardian is a child actor of the rootGuardian actor
+	_ = x.actors.AddNode(x.rootGuardian, x.systemGuardian)
+	return nil
+}
+
+// spawnUserGuardian creates the user guardian
+func (x *actorSystem) spawnUserGuardian(ctx context.Context) error {
+	var err error
+	actorName := x.reservedName(userGuardianType)
+	x.userGuardian, err = x.configPID(ctx,
+		actorName,
+		newUserGuardian(),
+		WithSupervisor(NewStopDirective()))
+	if err != nil {
+		return fmt.Errorf("actor=%s failed to start user guardian: %w", actorName, err)
+	}
+
+	// userGuardian is a child actor of the rootGuardian actor
+	_ = x.actors.AddNode(x.rootGuardian, x.userGuardian)
+	return nil
+}
+
+// spawnRebalancer creates the cluster rebalancer
+func (x *actorSystem) spawnJanitor(ctx context.Context) error {
 	var err error
-	actorName := x.reservedName(supervisorType)
-	x.supervisor, err = x.configPID(ctx, actorName, newSupervisor())
+	actorName := x.reservedName(janitorType)
+	x.janitor, err = x.configPID(ctx,
+		actorName,
+		newJanitor(),
+		WithSupervisor(NewResumeDirective()),
+	)
 	if err != nil {
-		return fmt.Errorf("actor=%s failed to start system supervisor: %w", actorName, err)
+		return fmt.Errorf("actor=%s failed to start the janitor: %w", actorName, err)
 	}
 
-	// set the supervisor in the tree
-	_ = x.actors.AddNode(NoSender, x.supervisor)
-	x.broadcastActor(x.supervisor)
+	// the janitor is a child actor of the system guardian
+	_ = x.actors.AddNode(x.systemGuardian, x.janitor)
 	return nil
 }
 
@@ -1622,9 +1660,8 @@ func (x *actorSystem) spawnRebalancer(ctx context.Context) error {
 		return fmt.Errorf("actor=%s failed to start cluster rebalancer: %w", actorName, err)
 	}
 
-	// set the balancer in the tree with the supervisor as its parent
-	_ = x.actors.AddNode(x.supervisor, x.rebalancer)
-	x.broadcastActor(x.rebalancer)
+	// the rebalancer is a child actor of the system guardian
+	_ = x.actors.AddNode(x.systemGuardian, x.rebalancer)
 	return nil
 }
 
diff --git a/actors/janitor.go b/actors/janitor.go
new file mode 100644
index 0000000..a1f0a0e
--- /dev/null
+++ b/actors/janitor.go
@@ -0,0 +1,95 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022-2024  Arsene Tochemey Gandote
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package actors
+
+import (
+	"context"
+
+	"github.com/tochemey/goakt/v2/goaktpb"
+	"github.com/tochemey/goakt/v2/log"
+)
+
+// janitor removes dead actors from the system
+// that helps free non-utilized resources
+type janitor struct {
+	pid    *PID
+	logger log.Logger
+}
+
+// enforce compilation error
+var _ Actor = (*janitor)(nil)
+
+// newJanitor creates an instance of the system janitor
+func newJanitor() *janitor {
+	return &janitor{}
+}
+
+// PreStart is the pre-start hook
+func (j *janitor) PreStart(context.Context) error {
+	return nil
+}
+
+// Receive handle message received
+func (j *janitor) Receive(ctx *ReceiveContext) {
+	switch msg := ctx.Message().(type) {
+	case *goaktpb.PostStart:
+		j.pid = ctx.Self()
+		j.logger = ctx.Logger()
+		j.logger.Infof("%s started successfully", j.pid.Name())
+
+	case *goaktpb.Terminated:
+		actorID := msg.GetActorId()
+		system := ctx.ActorSystem()
+		tree := system.tree()
+		clusterEnabled := system.InCluster()
+		cluster := system.getCluster()
+		logger := j.logger
+
+		logger.Infof("%s freeing resource [actor=%s] from system", j.pid.Name(), actorID)
+
+		// remove node from the actors tree
+		if node, ok := tree.GetNode(actorID); ok {
+			tree.DeleteNode(node.GetValue())
+			if clusterEnabled {
+				if err := cluster.RemoveActor(context.WithoutCancel(ctx.Context()), node.GetValue().Name()); err != nil {
+					logger.Errorf("failed to remove [actor=%s] from cluster: %v", actorID, err)
+				}
+			}
+
+			logger.Infof("%s successfully free resource [actor=%s] from system", j.pid.Name(), actorID)
+			return
+		}
+
+		logger.Infof("%s could not locate resource [actor=%s] in system", j.pid.Name(), actorID)
+	default:
+		ctx.Unhandled()
+	}
+}
+
+// PostStop is executed when the actor is shutting down.
+func (j *janitor) PostStop(context.Context) error {
+	j.logger.Infof("%s stopped successfully", j.pid.Name())
+	return nil
+}
diff --git a/actors/pid.go b/actors/pid.go
index 6e8d91c..e20d654 100644
--- a/actors/pid.go
+++ b/actors/pid.go
@@ -483,6 +483,7 @@ func (pid *PID) SpawnChild(ctx context.Context, name string, actor Actor, opts .
 	// no need to handle the error because the given parent exist and running
 	// that check was done in the above lines
 	_ = tree.AddNode(pid, cid)
+	tree.AddWatcher(cid, pid.ActorSystem().getJanitor())
 
 	eventsStream := pid.eventsStream
 	if eventsStream != nil {
@@ -1225,7 +1226,7 @@ func (pid *PID) freeWatchers(ctx context.Context) error {
 		logger.Debugf("%s successfully frees all watcher actors...", pid.Name())
 		return nil
 	}
-	logger.Debugf("%s does not have any watcher actors", pid.Name())
+	logger.Debugf("%s does not have any watcher actors. Maybe already freed.", pid.Name())
 	return nil
 }
 
@@ -1259,7 +1260,7 @@ func (pid *PID) freeWatchees() error {
 		logger.Debugf("%s successfully unwatch all watched actors...", pid.Name())
 		return nil
 	}
-	logger.Debugf("%s does not have any watched actors", pid.Name())
+	logger.Debugf("%s does not have any watched actors. Maybe already freed.", pid.Name())
 	return nil
 }
 
@@ -1275,7 +1276,7 @@ func (pid *PID) freeChildren(ctx context.Context) error {
 		return nil
 	}
 
-	if descendants, ok := tree.Descendants(pid); ok {
+	if descendants, ok := tree.Descendants(pid); ok && len(descendants) > 0 {
 		eg, ctx := errgroup.WithContext(ctx)
 		for index, descendant := range descendants {
 			descendant := descendant
@@ -1304,7 +1305,7 @@ func (pid *PID) freeChildren(ctx context.Context) error {
 		}
 		logger.Debugf("%s successfully free all descendant actors...", pid.Name())
 	}
-	pid.logger.Debugf("%s does not have any children", pid.Name())
+	pid.logger.Debugf("%s does not have any children. Maybe already freed.", pid.Name())
 	return nil
 }
 
@@ -1426,17 +1427,27 @@ func (pid *PID) doStop(ctx context.Context) error {
 		New(errorschain.ReturnFirst()).
 		AddError(pid.freeWatchees()).
 		AddError(pid.freeChildren(ctx)).
-		AddError(pid.freeWatchers(ctx)).
 		Error(); err != nil {
 		pid.started.Store(false)
 		pid.reset()
 		return err
 	}
 
+	// run the PostStop hook and let watchers know
+	// you are terminated
+	if err := errorschain.
+		New(errorschain.ReturnFirst()).
+		AddError(pid.actor.PostStop(ctx)).
+		AddError(pid.freeWatchers(ctx)).Error(); err != nil {
+		pid.started.Store(false)
+		pid.reset()
+		return err
+	}
+
 	pid.started.Store(false)
 	pid.logger.Infof("post shutdown process is on going for actor=%s...", pid.Name())
 	pid.reset()
-	return pid.actor.PostStop(ctx)
+	return nil
 }
 
 // supervisionLoop send error to watchers
diff --git a/actors/rebalancer.go b/actors/rebalancer.go
index 2746293..889154a 100644
--- a/actors/rebalancer.go
+++ b/actors/rebalancer.go
@@ -32,6 +32,7 @@ import (
 	"github.com/tochemey/goakt/v2/goaktpb"
 	"github.com/tochemey/goakt/v2/internal/internalpb"
 	"github.com/tochemey/goakt/v2/internal/slice"
+	"github.com/tochemey/goakt/v2/log"
 )
 
 // rebalancer is a system actor that helps rebalance cluster
@@ -40,6 +41,7 @@ type rebalancer struct {
 	reflection *reflection
 	remoting   *Remoting
 	pid        *PID
+	logger     log.Logger
 }
 
 // enforce compilation error
@@ -53,17 +55,18 @@ func newRebalancer(reflection *reflection) *rebalancer {
 }
 
 // PreStart pre-starts the actor.
-// nolint
-func (r *rebalancer) PreStart(ctx context.Context) error {
+func (r *rebalancer) PreStart(context.Context) error {
 	return nil
 }
 
-// Receive handles messages sent to the router
+// Receive handles messages sent to the rebalancer
 func (r *rebalancer) Receive(ctx *ReceiveContext) {
 	switch ctx.Message().(type) {
 	case *goaktpb.PostStart:
 		r.pid = ctx.Self()
 		r.remoting = NewRemoting()
+		r.logger = ctx.Logger()
+		r.logger.Infof("%s started successfully", r.pid.Name())
 		ctx.Become(r.Rebalance)
 	default:
 		ctx.Unhandled()
@@ -151,9 +154,9 @@ func (r *rebalancer) Rebalance(ctx *ReceiveContext) {
 }
 
 // PostStop is executed when the actor is shutting down.
-// nolint
-func (r *rebalancer) PostStop(ctx context.Context) error {
+func (r *rebalancer) PostStop(context.Context) error {
 	r.remoting.Close()
+	r.logger.Infof("%s stopped successfully", r.pid.Name())
 	return nil
 }
 
diff --git a/actors/root_guardian.go b/actors/root_guardian.go
new file mode 100644
index 0000000..98d85c2
--- /dev/null
+++ b/actors/root_guardian.go
@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022-2024  Arsene Tochemey Gandote
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package actors
+
+import (
+	"context"
+
+	"github.com/tochemey/goakt/v2/goaktpb"
+	"github.com/tochemey/goakt/v2/log"
+)
+
+// rootGuardian defines the system root actor
+// its job is to monitor the userGuardian and the systemGuardian
+// when either of those actors get terminated, the actorSystem is shutdown.
+type rootGuardian struct {
+	pid    *PID
+	logger log.Logger
+}
+
+// enforce compilation error
+var _ Actor = (*rootGuardian)(nil)
+
+// newRootGuardian creates an instance of the rootGuardian
+func newRootGuardian() *rootGuardian {
+	return &rootGuardian{}
+}
+
+// PreStart pre-starts the actor.
+func (r *rootGuardian) PreStart(context.Context) error {
+	return nil
+}
+
+// Receive handles message
+func (r *rootGuardian) Receive(ctx *ReceiveContext) {
+	switch msg := ctx.Message().(type) {
+	case *goaktpb.PostStart:
+		r.pid = ctx.Self()
+		r.logger = ctx.Logger()
+		r.logger.Infof("%s started successfully", r.pid.Name())
+	case *goaktpb.Terminated:
+		r.pid.logger.Debugf("%s terminated", msg.GetActorId())
+		// TODO: decide what to do the actor
+	default:
+		// pass
+	}
+}
+
+// PostStop is executed when the actor is shutting down.
+func (r *rootGuardian) PostStop(context.Context) error {
+	r.logger.Infof("%s stopped successfully", r.pid.Name())
+	return nil
+}
diff --git a/actors/supervisor.go b/actors/system_guardian.go
similarity index 61%
rename from actors/supervisor.go
rename to actors/system_guardian.go
index 0213d73..b4de9b6 100644
--- a/actors/supervisor.go
+++ b/actors/system_guardian.go
@@ -31,49 +31,56 @@ import (
 	"github.com/tochemey/goakt/v2/log"
 )
 
-// supervisor is an actor which roles is to handle
-// escalation failure
-type supervisor struct {
+// systemGuardian defines the main system actor that is the ancestor
+// of system created actors in the system.
+// When the systemGuardian for any reason gets terminated, the actor system will be automatically removed
+// from the actors tree
+type systemGuardian struct {
+	pid    *PID
 	logger log.Logger
-	system ActorSystem
 }
 
 // enforce compilation error
-var _ Actor = (*supervisor)(nil)
+var _ Actor = (*systemGuardian)(nil)
 
-// newSupervisor creates an instance of a supervisorQA
-func newSupervisor() *supervisor {
-	return &supervisor{}
+// newSystemGuardian creates an instance of system guardian
+func newSystemGuardian() *systemGuardian {
+	return &systemGuardian{}
 }
 
-func (s *supervisor) PreStart(context.Context) error {
+// PreStart is the pre-start hook
+func (g *systemGuardian) PreStart(context.Context) error {
 	return nil
 }
 
-func (s *supervisor) Receive(ctx *ReceiveContext) {
+// Receive handle message
+func (g *systemGuardian) Receive(ctx *ReceiveContext) {
 	switch msg := ctx.Message().(type) {
 	case *goaktpb.PostStart:
-		s.system = ctx.ActorSystem()
-		s.logger = ctx.Logger()
-		s.logger.Info("system supervisor successfully started")
+		g.pid = ctx.Self()
+		g.logger = ctx.Logger()
+		g.logger.Infof("%s started successfully", g.pid.Name())
 	case *goaktpb.Terminated:
 		actorID := msg.GetActorId()
-		if actorID == s.system.reservedName(rebalancerType) {
+		system := ctx.ActorSystem()
+		systemName := system.Name()
+		if actorID == system.reservedName(rebalancerType) {
 			// rebalancer is dead which means either there is an issue during the cluster topology changes
 			// log a message error and stop the actor system
-			s.logger.Warn("%s rebalancer is down. %s is going to shutdown. Kindly check logs and fix any potential issue with the cluster",
-				s.system.Name(),
-				s.system.Name())
+			g.logger.Warn("%s rebalancer is down. %s is going to shutdown. Kindly check logs and fix any potential issue with the cluster",
+				systemName,
+				systemName)
 
 			// blindly shutdown the actor system. No need to check any error
-			_ = s.system.Stop(context.WithoutCancel(ctx.Context()))
+			_ = system.Stop(context.WithoutCancel(ctx.Context()))
 		}
 	default:
 		ctx.Unhandled()
 	}
 }
 
-func (s *supervisor) PostStop(context.Context) error {
-	s.logger.Info("system supervisor stopped")
+// PostStop is the post-stop hook
+func (g *systemGuardian) PostStop(context.Context) error {
+	g.logger.Infof("%s stopped successfully", g.pid.Name())
 	return nil
 }
diff --git a/actors/user_guardian.go b/actors/user_guardian.go
new file mode 100644
index 0000000..7b0cf39
--- /dev/null
+++ b/actors/user_guardian.go
@@ -0,0 +1,75 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2022-2024  Arsene Tochemey Gandote
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package actors
+
+import (
+	"context"
+
+	"github.com/tochemey/goakt/v2/goaktpb"
+	"github.com/tochemey/goakt/v2/log"
+)
+
+// userGuardian defines the main system actor that is the ancestor
+// of user created actors in the system.
+// When the userGuardian for any reason gets terminated, the actor system will be automatically removed
+// from the actors tree
+type userGuardian struct {
+	pid    *PID
+	logger log.Logger
+}
+
+// enforce compilation error
+var _ Actor = (*userGuardian)(nil)
+
+// newUserGuardian creates an instance of userGuardian
+func newUserGuardian() *userGuardian {
+	return &userGuardian{}
+}
+
+// PreStart is the pre-start hook
+func (g *userGuardian) PreStart(context.Context) error {
+	return nil
+}
+
+// Receive handles messages
+func (g *userGuardian) Receive(ctx *ReceiveContext) {
+	switch msg := ctx.Message().(type) {
+	case *goaktpb.PostStart:
+		g.pid = ctx.Self()
+		g.logger = ctx.Logger()
+		g.logger.Infof("%s started successfully", g.pid.Name())
+	case *goaktpb.Terminated:
+		g.logger.Debugf("%s received a terminated actor=(%s)", g.pid.Name(), msg.GetActorId())
+		// pass
+	default:
+		ctx.Unhandled()
+	}
+}
+
+// PostStop is the post-stop hook
+func (g *userGuardian) PostStop(context.Context) error {
+	g.logger.Infof("%s stopped successfully", g.pid.Name())
+	return nil
+}
diff --git a/actors/types.go b/actors/util.go
similarity index 87%
rename from actors/types.go
rename to actors/util.go
index 144ef3c..46c8e86 100644
--- a/actors/types.go
+++ b/actors/util.go
@@ -53,9 +53,12 @@ const (
 type nameType int
 
 const (
-	supervisorType nameType = iota
-	routerType
+	routerType nameType = iota
 	rebalancerType
+	rootGuardianType
+	userGuardianType
+	systemGuardianType
+	janitorType
 )
 
 var (
@@ -65,8 +68,11 @@ var (
 	DefaultSupervisoryStrategy = NewStopDirective()
 
 	systemNames = map[nameType]string{
-		supervisorType: "GoAktSupervisor",
-		routerType:     "GoAktRouter",
-		rebalancerType: "GoAktRebalancer",
+		routerType:         "GoAktRouter",
+		rebalancerType:     "GoAktRebalancer",
+		rootGuardianType:   "GoAktRootGuardian",
+		userGuardianType:   "GoAktUserGuardian",
+		systemGuardianType: "GoAktSystemGuardian",
+		janitorType:        "GoAktJanitor",
 	}
 )
diff --git a/go.mod b/go.mod
index 8c63b00..158d741 100644
--- a/go.mod
+++ b/go.mod
@@ -21,8 +21,8 @@ require (
 	go.uber.org/atomic v1.11.0
 	go.uber.org/multierr v1.11.0
 	go.uber.org/zap v1.27.0
-	golang.org/x/net v0.31.0
-	golang.org/x/sync v0.9.0
+	golang.org/x/net v0.32.0
+	golang.org/x/sync v0.10.0
 	google.golang.org/protobuf v1.35.2
 	k8s.io/api v0.31.3
 	k8s.io/apimachinery v0.31.3
@@ -81,12 +81,12 @@ require (
 	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/crypto v0.29.0 // indirect
+	golang.org/x/crypto v0.30.0 // indirect
 	golang.org/x/mod v0.22.0 // indirect
 	golang.org/x/oauth2 v0.23.0 // indirect
-	golang.org/x/sys v0.27.0 // indirect
-	golang.org/x/term v0.26.0 // indirect
-	golang.org/x/text v0.20.0 // indirect
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/term v0.27.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	golang.org/x/time v0.8.0 // indirect
 	golang.org/x/tools v0.27.0 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
diff --git a/go.sum b/go.sum
index 4121539..ab7593a 100644
--- a/go.sum
+++ b/go.sum
@@ -306,8 +306,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
-golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
-golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
+golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
+golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -342,8 +342,8 @@ golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
-golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
-golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
+golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
+golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
 golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
 golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -359,8 +359,8 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
-golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -387,8 +387,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
-golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -400,8 +400,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
 golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
-golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
-golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
+golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -414,8 +414,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
-golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
-golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
 golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=


Test patch:
diff --git a/actors/helper_test.go b/actors/helper_test.go
index 857b552..fddbd13 100644
--- a/actors/helper_test.go
+++ b/actors/helper_test.go
@@ -115,6 +115,8 @@ func (p *supervisorQA) Receive(ctx *ReceiveContext) {
 	switch ctx.Message().(type) {
 	case *goaktpb.PostStart:
 	case *testspb.TestSend:
+	case *goaktpb.Terminated:
+		// pass
 	default:
 		panic(ErrUnhandled)
 	}
diff --git a/actors/pid_test.go b/actors/pid_test.go
index 4be8ed6..6891bd3 100644
--- a/actors/pid_test.go
+++ b/actors/pid_test.go
@@ -1298,7 +1298,7 @@ func TestSpawnChild(t *testing.T) {
 			WithRemoting(host, int32(ports[0])),
 			WithPassivationDisabled(),
 			WithJanitorInterval(100*time.Millisecond),
-			WithLogger(log.DefaultLogger))
+			WithLogger(log.DiscardLogger))
 
 		require.NoError(t, err)
 		require.NotNil(t, actorSystem)