Repository: Tochemey/goakt
Pull request #302 (2024-04-19 17:04:37)

fix: application should exit when cluster engine fails to start
closes https://github.com/Tochemey/goakt/issues/301

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Tochemey/goakt/pull/302?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 `3.97554%` with `314 lines` in your changes are missing coverage. Please review.
> Project coverage is 70.62%. Comparing base [(`408564c`)](https://app.codecov.io/gh/Tochemey/goakt/commit/408564c13e4bdd2634debed48f05631bc1a8e2a7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) to head [(`8df975a`)](https://app.codecov.io/gh/Tochemey/goakt/pull/302?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).

| [Files](https://app.codecov.io/gh/Tochemey/goakt/pull/302?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Patch % | Lines |
|---|---|---|
| [mocks/cluster/interface.go](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&filepath=mocks%2Fcluster%2Finterface.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9ja3MvY2x1c3Rlci9pbnRlcmZhY2UuZ28=) | 2.54% | [268 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [mocks/cluster/option.go](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&filepath=mocks%2Fcluster%2Foption.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9ja3MvY2x1c3Rlci9vcHRpb24uZ28=) | 0.00% | [22 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [mocks/cluster/option\_func.go](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&filepath=mocks%2Fcluster%2Foption_func.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-bW9ja3MvY2x1c3Rlci9vcHRpb25fZnVuYy5nbw==) | 0.00% | [22 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/302?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) |
| [actors/actor\_system.go](https://app.codecov.io/gh/Tochemey/goakt/pull/302?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==) | 75.00% | [2 Missing :warning: ](https://app.codecov.io/gh/Tochemey/goakt/pull/302?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     #302      +/-   ##
==========================================
- Coverage   76.30%   70.62%   -5.68%     
==========================================
  Files          34       37       +3     
  Lines        3807     4130     +323     
==========================================
+ Hits         2905     2917      +12     
- Misses        769     1081     +312     
+ Partials      133      132       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Tochemey/goakt/pull/302?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/.github/workflows/build.yml b/.github/workflows/build.yml
index 54b7165..3e1fad0 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -44,4 +44,3 @@ jobs:
           files: ./coverage.out
           fail_ci_if_error: false
           verbose: true
-          exclude: goaktpb/
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 3480185..58df93b 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -48,4 +48,3 @@ jobs:
           files: ./coverage.out
           fail_ci_if_error: false
           verbose: true
-          exclude: goaktpb/
diff --git a/Earthfile b/Earthfile
index c0714e1..be97514 100644
--- a/Earthfile
+++ b/Earthfile
@@ -6,6 +6,7 @@ FROM tochemey/docker-go:1.21.0-1.0.0
 RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
 RUN go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
 RUN apk --no-cache add git ca-certificates gcc musl-dev libc-dev binutils-gold
+RUN go install github.com/ory/go-acc@latest
 
 
 test:
@@ -38,6 +39,7 @@ mock:
     # generate the mocks
     RUN mockery  --all --dir hash --keeptree --exported=true --with-expecter=true --inpackage=true --output ./mocks/hash --case snake
     RUN mockery  --all --dir discovery --keeptree --exported=true --with-expecter=true --inpackage=true --output ./mocks/discovery --case snake
+    RUN mockery  --all --dir internal/cluster --keeptree --exported=true --with-expecter=true --inpackage=true --output ./mocks/cluster --case snake
 
     SAVE ARTIFACT ./mocks mocks AS LOCAL mocks
 
@@ -54,7 +56,7 @@ lint:
 local-test:
     FROM +vendor
 
-    RUN go test -mod=vendor `go list ./... | grep -v ./goaktpb | grep -v ./examples | grep -v ./mocks` -race -v -coverprofile=coverage.out -covermode=atomic -coverpkg=./...
+    RUN go-acc ./... -o coverage.out --ignore goaktpb,examples,mocks,internal/internalpb -- -mod=vendor -race -v
 
     SAVE ARTIFACT coverage.out AS LOCAL coverage.out
 
diff --git a/actors/actor_system.go b/actors/actor_system.go
index 5cc5756..d51efda 100644
--- a/actors/actor_system.go
+++ b/actors/actor_system.go
@@ -738,7 +738,9 @@ func (x *actorSystem) Start(ctx context.Context) error {
 	x.started.Store(true)
 
 	if x.clusterEnabled.Load() {
-		x.enableClustering(spanCtx)
+		if err := x.enableClustering(spanCtx); err != nil {
+			return err
+		}
 	}
 
 	if x.remotingEnabled.Load() {
@@ -1047,7 +1049,7 @@ func (x *actorSystem) handleRemoteTell(ctx context.Context, to PID, message prot
 
 // enableClustering enables clustering. When clustering is enabled remoting is also enabled to facilitate remote
 // communication
-func (x *actorSystem) enableClustering(ctx context.Context) {
+func (x *actorSystem) enableClustering(ctx context.Context) error {
 	x.logger.Info("enabling clustering...")
 
 	cluster, err := cluster.NewNode(x.Name(),
@@ -1057,12 +1059,14 @@ func (x *actorSystem) enableClustering(ctx context.Context) {
 		cluster.WithHasher(x.partitionHasher),
 	)
 	if err != nil {
-		x.logger.Panic(errors.Wrap(err, "failed to initialize cluster engine"))
+		x.logger.Error(errors.Wrap(err, "failed to initialize cluster engine"))
+		return err
 	}
 
 	x.logger.Info("starting cluster engine...")
 	if err := cluster.Start(ctx); err != nil {
-		x.logger.Panic(errors.Wrap(err, "failed to start cluster engine"))
+		x.logger.Error(errors.Wrap(err, "failed to start cluster engine"))
+		return err
 	}
 
 	bootstrapChan := make(chan struct{}, 1)
@@ -1085,6 +1089,7 @@ func (x *actorSystem) enableClustering(ctx context.Context) {
 	go x.broadcast(ctx)
 
 	x.logger.Info("clustering enabled...:)")
+	return nil
 }
 
 // enableRemoting enables the remoting service to handle remote messaging
diff --git a/codecov.yml b/codecov.yml
index 8bc8acf..5ffd66f 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -14,4 +14,4 @@ ignore:
   - "examples"
   - "internal/internalpb"
   - "testkit"
-  - "**/mocks"
+  - "mocks"
diff --git a/internal/cluster/node.go b/internal/cluster/node.go
index d91a57e..93f795e 100644
--- a/internal/cluster/node.go
+++ b/internal/cluster/node.go
@@ -205,7 +205,6 @@ func (n *Node) Start(ctx context.Context) error {
 		logger.Infof("GoAkt cluster Node=(%s) successfully started. 🎉", n.name)
 	}
 
-	// let us create an instance of the Node engine
 	eng, err := olric.New(conf)
 	if err != nil {
 		logger.Error(errors.Wrapf(err, "failed to start the cluster Node=(%s).💥", n.name))
@@ -216,10 +215,11 @@ func (n *Node) Start(ctx context.Context) error {
 	n.server = eng
 	go func() {
 		if err = n.server.Start(); err != nil {
-			logger.Error(errors.Wrapf(err, "failed to start the cluster Node=(%s).💥", n.name))
 			if e := n.server.Shutdown(ctx); e != nil {
 				logger.Panic(e)
 			}
+			// the expectation is to exit the application
+			logger.Fatal(errors.Wrapf(err, "failed to start the cluster Node=(%s).💥", n.name))
 		}
 	}()
 
diff --git a/mocks/cluster/interface.go b/mocks/cluster/interface.go
new file mode 100644
index 0000000..99ab3cc
--- /dev/null
+++ b/mocks/cluster/interface.go
@@ -0,0 +1,568 @@
+// Code generated by mockery v2.32.4. DO NOT EDIT.
+
+package cluster
+
+import (
+	context "context"
+
+	internalcluster "github.com/tochemey/goakt/internal/cluster"
+	internalpb "github.com/tochemey/goakt/internal/internalpb"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Interface is an autogenerated mock type for the Interface type
+type Interface struct {
+	mock.Mock
+}
+
+type Interface_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *Interface) EXPECT() *Interface_Expecter {
+	return &Interface_Expecter{mock: &_m.Mock}
+}
+
+// AdvertisedAddress provides a mock function with given fields:
+func (_m *Interface) AdvertisedAddress() string {
+	ret := _m.Called()
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func() string); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	return r0
+}
+
+// Interface_AdvertisedAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AdvertisedAddress'
+type Interface_AdvertisedAddress_Call struct {
+	*mock.Call
+}
+
+// AdvertisedAddress is a helper method to define mock.On call
+func (_e *Interface_Expecter) AdvertisedAddress() *Interface_AdvertisedAddress_Call {
+	return &Interface_AdvertisedAddress_Call{Call: _e.mock.On("AdvertisedAddress")}
+}
+
+func (_c *Interface_AdvertisedAddress_Call) Run(run func()) *Interface_AdvertisedAddress_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *Interface_AdvertisedAddress_Call) Return(_a0 string) *Interface_AdvertisedAddress_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_AdvertisedAddress_Call) RunAndReturn(run func() string) *Interface_AdvertisedAddress_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// Events provides a mock function with given fields:
+func (_m *Interface) Events() <-chan *internalcluster.Event {
+	ret := _m.Called()
+
+	var r0 <-chan *internalcluster.Event
+	if rf, ok := ret.Get(0).(func() <-chan *internalcluster.Event); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(<-chan *internalcluster.Event)
+		}
+	}
+
+	return r0
+}
+
+// Interface_Events_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Events'
+type Interface_Events_Call struct {
+	*mock.Call
+}
+
+// Events is a helper method to define mock.On call
+func (_e *Interface_Expecter) Events() *Interface_Events_Call {
+	return &Interface_Events_Call{Call: _e.mock.On("Events")}
+}
+
+func (_c *Interface_Events_Call) Run(run func()) *Interface_Events_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *Interface_Events_Call) Return(_a0 <-chan *internalcluster.Event) *Interface_Events_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_Events_Call) RunAndReturn(run func() <-chan *internalcluster.Event) *Interface_Events_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// GetActor provides a mock function with given fields: ctx, actorName
+func (_m *Interface) GetActor(ctx context.Context, actorName string) (*internalpb.WireActor, error) {
+	ret := _m.Called(ctx, actorName)
+
+	var r0 *internalpb.WireActor
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) (*internalpb.WireActor, error)); ok {
+		return rf(ctx, actorName)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, string) *internalpb.WireActor); ok {
+		r0 = rf(ctx, actorName)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*internalpb.WireActor)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, actorName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Interface_GetActor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetActor'
+type Interface_GetActor_Call struct {
+	*mock.Call
+}
+
+// GetActor is a helper method to define mock.On call
+//   - ctx context.Context
+//   - actorName string
+func (_e *Interface_Expecter) GetActor(ctx interface{}, actorName interface{}) *Interface_GetActor_Call {
+	return &Interface_GetActor_Call{Call: _e.mock.On("GetActor", ctx, actorName)}
+}
+
+func (_c *Interface_GetActor_Call) Run(run func(ctx context.Context, actorName string)) *Interface_GetActor_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(string))
+	})
+	return _c
+}
+
+func (_c *Interface_GetActor_Call) Return(_a0 *internalpb.WireActor, _a1 error) *Interface_GetActor_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *Interface_GetActor_Call) RunAndReturn(run func(context.Context, string) (*internalpb.WireActor, error)) *Interface_GetActor_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// GetPartition provides a mock function with given fields: actorName
+func (_m *Interface) GetPartition(actorName string) int {
+	ret := _m.Called(actorName)
+
+	var r0 int
+	if rf, ok := ret.Get(0).(func(string) int); ok {
+		r0 = rf(actorName)
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	return r0
+}
+
+// Interface_GetPartition_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPartition'
+type Interface_GetPartition_Call struct {
+	*mock.Call
+}
+
+// GetPartition is a helper method to define mock.On call
+//   - actorName string
+func (_e *Interface_Expecter) GetPartition(actorName interface{}) *Interface_GetPartition_Call {
+	return &Interface_GetPartition_Call{Call: _e.mock.On("GetPartition", actorName)}
+}
+
+func (_c *Interface_GetPartition_Call) Run(run func(actorName string)) *Interface_GetPartition_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(string))
+	})
+	return _c
+}
+
+func (_c *Interface_GetPartition_Call) Return(_a0 int) *Interface_GetPartition_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_GetPartition_Call) RunAndReturn(run func(string) int) *Interface_GetPartition_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// KeyExists provides a mock function with given fields: ctx, key
+func (_m *Interface) KeyExists(ctx context.Context, key string) (bool, error) {
+	ret := _m.Called(ctx, key)
+
+	var r0 bool
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok {
+		return rf(ctx, key)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok {
+		r0 = rf(ctx, key)
+	} else {
+		r0 = ret.Get(0).(bool)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, key)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Interface_KeyExists_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'KeyExists'
+type Interface_KeyExists_Call struct {
+	*mock.Call
+}
+
+// KeyExists is a helper method to define mock.On call
+//   - ctx context.Context
+//   - key string
+func (_e *Interface_Expecter) KeyExists(ctx interface{}, key interface{}) *Interface_KeyExists_Call {
+	return &Interface_KeyExists_Call{Call: _e.mock.On("KeyExists", ctx, key)}
+}
+
+func (_c *Interface_KeyExists_Call) Run(run func(ctx context.Context, key string)) *Interface_KeyExists_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(string))
+	})
+	return _c
+}
+
+func (_c *Interface_KeyExists_Call) Return(_a0 bool, _a1 error) *Interface_KeyExists_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *Interface_KeyExists_Call) RunAndReturn(run func(context.Context, string) (bool, error)) *Interface_KeyExists_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NodeHost provides a mock function with given fields:
+func (_m *Interface) NodeHost() string {
+	ret := _m.Called()
+
+	var r0 string
+	if rf, ok := ret.Get(0).(func() string); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(string)
+	}
+
+	return r0
+}
+
+// Interface_NodeHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NodeHost'
+type Interface_NodeHost_Call struct {
+	*mock.Call
+}
+
+// NodeHost is a helper method to define mock.On call
+func (_e *Interface_Expecter) NodeHost() *Interface_NodeHost_Call {
+	return &Interface_NodeHost_Call{Call: _e.mock.On("NodeHost")}
+}
+
+func (_c *Interface_NodeHost_Call) Run(run func()) *Interface_NodeHost_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *Interface_NodeHost_Call) Return(_a0 string) *Interface_NodeHost_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_NodeHost_Call) RunAndReturn(run func() string) *Interface_NodeHost_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NodeRemotingPort provides a mock function with given fields:
+func (_m *Interface) NodeRemotingPort() int {
+	ret := _m.Called()
+
+	var r0 int
+	if rf, ok := ret.Get(0).(func() int); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Get(0).(int)
+	}
+
+	return r0
+}
+
+// Interface_NodeRemotingPort_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NodeRemotingPort'
+type Interface_NodeRemotingPort_Call struct {
+	*mock.Call
+}
+
+// NodeRemotingPort is a helper method to define mock.On call
+func (_e *Interface_Expecter) NodeRemotingPort() *Interface_NodeRemotingPort_Call {
+	return &Interface_NodeRemotingPort_Call{Call: _e.mock.On("NodeRemotingPort")}
+}
+
+func (_c *Interface_NodeRemotingPort_Call) Run(run func()) *Interface_NodeRemotingPort_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *Interface_NodeRemotingPort_Call) Return(_a0 int) *Interface_NodeRemotingPort_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_NodeRemotingPort_Call) RunAndReturn(run func() int) *Interface_NodeRemotingPort_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// PutActor provides a mock function with given fields: ctx, actor
+func (_m *Interface) PutActor(ctx context.Context, actor *internalpb.WireActor) error {
+	ret := _m.Called(ctx, actor)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *internalpb.WireActor) error); ok {
+		r0 = rf(ctx, actor)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Interface_PutActor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutActor'
+type Interface_PutActor_Call struct {
+	*mock.Call
+}
+
+// PutActor is a helper method to define mock.On call
+//   - ctx context.Context
+//   - actor *internalpb.WireActor
+func (_e *Interface_Expecter) PutActor(ctx interface{}, actor interface{}) *Interface_PutActor_Call {
+	return &Interface_PutActor_Call{Call: _e.mock.On("PutActor", ctx, actor)}
+}
+
+func (_c *Interface_PutActor_Call) Run(run func(ctx context.Context, actor *internalpb.WireActor)) *Interface_PutActor_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*internalpb.WireActor))
+	})
+	return _c
+}
+
+func (_c *Interface_PutActor_Call) Return(_a0 error) *Interface_PutActor_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_PutActor_Call) RunAndReturn(run func(context.Context, *internalpb.WireActor) error) *Interface_PutActor_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// RemoveActor provides a mock function with given fields: ctx, actorName
+func (_m *Interface) RemoveActor(ctx context.Context, actorName string) error {
+	ret := _m.Called(ctx, actorName)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, actorName)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Interface_RemoveActor_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveActor'
+type Interface_RemoveActor_Call struct {
+	*mock.Call
+}
+
+// RemoveActor is a helper method to define mock.On call
+//   - ctx context.Context
+//   - actorName string
+func (_e *Interface_Expecter) RemoveActor(ctx interface{}, actorName interface{}) *Interface_RemoveActor_Call {
+	return &Interface_RemoveActor_Call{Call: _e.mock.On("RemoveActor", ctx, actorName)}
+}
+
+func (_c *Interface_RemoveActor_Call) Run(run func(ctx context.Context, actorName string)) *Interface_RemoveActor_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(string))
+	})
+	return _c
+}
+
+func (_c *Interface_RemoveActor_Call) Return(_a0 error) *Interface_RemoveActor_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_RemoveActor_Call) RunAndReturn(run func(context.Context, string) error) *Interface_RemoveActor_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// SetKey provides a mock function with given fields: ctx, key
+func (_m *Interface) SetKey(ctx context.Context, key string) error {
+	ret := _m.Called(ctx, key)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, key)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Interface_SetKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetKey'
+type Interface_SetKey_Call struct {
+	*mock.Call
+}
+
+// SetKey is a helper method to define mock.On call
+//   - ctx context.Context
+//   - key string
+func (_e *Interface_Expecter) SetKey(ctx interface{}, key interface{}) *Interface_SetKey_Call {
+	return &Interface_SetKey_Call{Call: _e.mock.On("SetKey", ctx, key)}
+}
+
+func (_c *Interface_SetKey_Call) Run(run func(ctx context.Context, key string)) *Interface_SetKey_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(string))
+	})
+	return _c
+}
+
+func (_c *Interface_SetKey_Call) Return(_a0 error) *Interface_SetKey_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_SetKey_Call) RunAndReturn(run func(context.Context, string) error) *Interface_SetKey_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// Start provides a mock function with given fields: ctx
+func (_m *Interface) Start(ctx context.Context) error {
+	ret := _m.Called(ctx)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Interface_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start'
+type Interface_Start_Call struct {
+	*mock.Call
+}
+
+// Start is a helper method to define mock.On call
+//   - ctx context.Context
+func (_e *Interface_Expecter) Start(ctx interface{}) *Interface_Start_Call {
+	return &Interface_Start_Call{Call: _e.mock.On("Start", ctx)}
+}
+
+func (_c *Interface_Start_Call) Run(run func(ctx context.Context)) *Interface_Start_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context))
+	})
+	return _c
+}
+
+func (_c *Interface_Start_Call) Return(_a0 error) *Interface_Start_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_Start_Call) RunAndReturn(run func(context.Context) error) *Interface_Start_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// Stop provides a mock function with given fields: ctx
+func (_m *Interface) Stop(ctx context.Context) error {
+	ret := _m.Called(ctx)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context) error); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Interface_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop'
+type Interface_Stop_Call struct {
+	*mock.Call
+}
+
+// Stop is a helper method to define mock.On call
+//   - ctx context.Context
+func (_e *Interface_Expecter) Stop(ctx interface{}) *Interface_Stop_Call {
+	return &Interface_Stop_Call{Call: _e.mock.On("Stop", ctx)}
+}
+
+func (_c *Interface_Stop_Call) Run(run func(ctx context.Context)) *Interface_Stop_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context))
+	})
+	return _c
+}
+
+func (_c *Interface_Stop_Call) Return(_a0 error) *Interface_Stop_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *Interface_Stop_Call) RunAndReturn(run func(context.Context) error) *Interface_Stop_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewInterface creates a new instance of Interface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewInterface(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *Interface {
+	mock := &Interface{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/mocks/cluster/option.go b/mocks/cluster/option.go
new file mode 100644
index 0000000..cedc8e3
--- /dev/null
+++ b/mocks/cluster/option.go
@@ -0,0 +1,68 @@
+// Code generated by mockery v2.32.4. DO NOT EDIT.
+
+package cluster
+
+import (
+	mock "github.com/stretchr/testify/mock"
+	internalcluster "github.com/tochemey/goakt/internal/cluster"
+)
+
+// Option is an autogenerated mock type for the Option type
+type Option struct {
+	mock.Mock
+}
+
+type Option_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *Option) EXPECT() *Option_Expecter {
+	return &Option_Expecter{mock: &_m.Mock}
+}
+
+// Apply provides a mock function with given fields: cl
+func (_m *Option) Apply(cl *internalcluster.Node) {
+	_m.Called(cl)
+}
+
+// Option_Apply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Apply'
+type Option_Apply_Call struct {
+	*mock.Call
+}
+
+// Apply is a helper method to define mock.On call
+//   - cl *internalcluster.Node
+func (_e *Option_Expecter) Apply(cl interface{}) *Option_Apply_Call {
+	return &Option_Apply_Call{Call: _e.mock.On("Apply", cl)}
+}
+
+func (_c *Option_Apply_Call) Run(run func(cl *internalcluster.Node)) *Option_Apply_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(*internalcluster.Node))
+	})
+	return _c
+}
+
+func (_c *Option_Apply_Call) Return() *Option_Apply_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *Option_Apply_Call) RunAndReturn(run func(*internalcluster.Node)) *Option_Apply_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewOption creates a new instance of Option. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewOption(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *Option {
+	mock := &Option{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/mocks/cluster/option_func.go b/mocks/cluster/option_func.go
new file mode 100644
index 0000000..c8ba57a
--- /dev/null
+++ b/mocks/cluster/option_func.go
@@ -0,0 +1,68 @@
+// Code generated by mockery v2.32.4. DO NOT EDIT.
+
+package cluster
+
+import (
+	mock "github.com/stretchr/testify/mock"
+	internalcluster "github.com/tochemey/goakt/internal/cluster"
+)
+
+// OptionFunc is an autogenerated mock type for the OptionFunc type
+type OptionFunc struct {
+	mock.Mock
+}
+
+type OptionFunc_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *OptionFunc) EXPECT() *OptionFunc_Expecter {
+	return &OptionFunc_Expecter{mock: &_m.Mock}
+}
+
+// Execute provides a mock function with given fields: cl
+func (_m *OptionFunc) Execute(cl *internalcluster.Node) {
+	_m.Called(cl)
+}
+
+// OptionFunc_Execute_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Execute'
+type OptionFunc_Execute_Call struct {
+	*mock.Call
+}
+
+// Execute is a helper method to define mock.On call
+//   - cl *internalcluster.Node
+func (_e *OptionFunc_Expecter) Execute(cl interface{}) *OptionFunc_Execute_Call {
+	return &OptionFunc_Execute_Call{Call: _e.mock.On("Execute", cl)}
+}
+
+func (_c *OptionFunc_Execute_Call) Run(run func(cl *internalcluster.Node)) *OptionFunc_Execute_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(*internalcluster.Node))
+	})
+	return _c
+}
+
+func (_c *OptionFunc_Execute_Call) Return() *OptionFunc_Execute_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *OptionFunc_Execute_Call) RunAndReturn(run func(*internalcluster.Node)) *OptionFunc_Execute_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewOptionFunc creates a new instance of OptionFunc. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewOptionFunc(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *OptionFunc {
+	mock := &OptionFunc{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}


Test patch:
diff --git a/actors/actor_system_test.go b/actors/actor_system_test.go
index d2c72b8..d9e8986 100644
--- a/actors/actor_system_test.go
+++ b/actors/actor_system_test.go
@@ -30,6 +30,7 @@ import (
 	"os"
 	"sort"
 	"strconv"
+	"sync"
 	"testing"
 	"time"
 
@@ -40,11 +41,14 @@ import (
 	"github.com/travisjeffery/go-dynaport"
 	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
 	"go.opentelemetry.io/otel/sdk/metric/metricdata"
+	"go.opentelemetry.io/otel/trace/noop"
+	"go.uber.org/atomic"
 	"google.golang.org/protobuf/proto"
 
 	"github.com/tochemey/goakt/discovery"
 	"github.com/tochemey/goakt/goaktpb"
 	"github.com/tochemey/goakt/log"
+	clustermocks "github.com/tochemey/goakt/mocks/cluster"
 	testkit "github.com/tochemey/goakt/mocks/discovery"
 	"github.com/tochemey/goakt/telemetry"
 	testpb "github.com/tochemey/goakt/test/data/testpb"
@@ -1351,7 +1355,6 @@ func TestActorSystem(t *testing.T) {
 		assert.EqualError(t, err, ErrActorSystemNotStarted.Error())
 		assert.Nil(t, actorRef)
 	})
-
 	t.Run("With happy path Register", func(t *testing.T) {
 		ctx := context.TODO()
 		logger := log.New(log.DebugLevel, os.Stdout)
@@ -1376,7 +1379,6 @@ func TestActorSystem(t *testing.T) {
 			assert.NoError(t, err)
 		})
 	})
-
 	t.Run("With Register when actor system not started", func(t *testing.T) {
 		ctx := context.TODO()
 		logger := log.New(log.DebugLevel, os.Stdout)
@@ -1398,7 +1400,6 @@ func TestActorSystem(t *testing.T) {
 			assert.Error(t, err)
 		})
 	})
-
 	t.Run("With happy path Deregister", func(t *testing.T) {
 		ctx := context.TODO()
 		logger := log.New(log.DebugLevel, os.Stdout)
@@ -1426,7 +1427,6 @@ func TestActorSystem(t *testing.T) {
 			assert.NoError(t, err)
 		})
 	})
-
 	t.Run("With Deregister when actor system not started", func(t *testing.T) {
 		ctx := context.TODO()
 		logger := log.New(log.DebugLevel, os.Stdout)
@@ -1447,4 +1447,32 @@ func TestActorSystem(t *testing.T) {
 			assert.Error(t, err)
 		})
 	})
+
+	t.Run("With cluster start failure", func(t *testing.T) {
+		ctx := context.TODO()
+		logger := log.DiscardLogger
+		mockedCluster := new(clustermocks.Interface)
+		mockedErr := errors.New("failed to start")
+		mockedCluster.EXPECT().Start(ctx).Return(mockedErr)
+
+		// mock the discovery provider
+		provider := new(testkit.Provider)
+		config := discovery.NewConfig()
+		sd := discovery.NewServiceDiscovery(provider, config)
+
+		system := &actorSystem{
+			name:             "testSystem",
+			logger:           logger,
+			cluster:          mockedCluster,
+			clusterEnabled:   *atomic.NewBool(true),
+			telemetry:        telemetry.New(),
+			mutex:            sync.Mutex{},
+			tracer:           noop.NewTracerProvider().Tracer("testSystem"),
+			scheduler:        newScheduler(logger, time.Second, withSchedulerCluster(mockedCluster)),
+			serviceDiscovery: sd,
+		}
+
+		err := system.Start(ctx)
+		require.Error(t, err)
+	})
 }