Repository: Tochemey/goakt
Pull request #111 (2023-09-06 23:53:52)

feat: add stash buffer

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) Report
Patch coverage: **`90.47%`** and project coverage change: **`-0.06%`** :warning:
> Comparison is base [(`15b3528`)](https://app.codecov.io/gh/Tochemey/goakt/commit/15b35289ff1d83b38dd25e3444e9bab8d0111518?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) 83.49% compared to head [(`6c943fa`)](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) 83.44%.

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


```diff
@@            Coverage Diff             @@
##             main     #111      +/-   ##
==========================================
- Coverage   83.49%   83.44%   -0.06%     
==========================================
  Files          36       37       +1     
  Lines        2909     2948      +39     
==========================================
+ Hits         2429     2460      +31     
- Misses        383      389       +6     
- Partials       97       99       +2     
```


| [Files Changed](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Coverage Δ | |
|---|---|---|
| [actors/pid.go](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3BpZC5nbw==) | `83.25% <77.77%> (-1.12%)` | :arrow_down: |
| [actors/actor\_system.go](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FjdG9yX3N5c3RlbS5nbw==) | `82.77% <100.00%> (+0.05%)` | :arrow_up: |
| [actors/mailbox.go](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL21haWxib3guZ28=) | `100.00% <100.00%> (ø)` | |
| [actors/option.go](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL29wdGlvbi5nbw==) | `100.00% <100.00%> (ø)` | |
| [actors/pid\_option.go](https://app.codecov.io/gh/Tochemey/goakt/pull/111?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3BpZF9vcHRpb24uZ28=) | `100.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Tochemey/goakt/pull/111?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 78b29fb..84f5041 100644
--- a/actors/actor_system.go
+++ b/actors/actor_system.go
@@ -131,6 +131,8 @@ type actorSystem struct {
 
 	// help protect some the fields to set
 	mu sync.Mutex
+	// specifies actors mailbox size
+	mailboxSize int
 }
 
 // enforce compilation error when all methods of the ActorSystem interface are not implemented
@@ -167,6 +169,7 @@ func NewActorSystem(name string, opts ...Option) (ActorSystem, error) {
 				clusterEnabled:      atomic.NewBool(false),
 				mu:                  sync.Mutex{},
 				shutdownTimeout:     DefaultShutdownTimeout,
+				mailboxSize:         defaultMailboxSize,
 			}
 			// set the reflection
 			system.reflection = NewReflection(system.typesLoader)
@@ -243,6 +246,7 @@ func (x *actorSystem) Spawn(ctx context.Context, name string, actor Actor) PID {
 		withCustomLogger(x.logger),
 		withActorSystem(x),
 		withSupervisorStrategy(x.supervisorStrategy),
+		withMailboxSize(x.mailboxSize),
 		withTelemetry(x.telemetry))
 
 	// add the given actor to the actor map
diff --git a/actors/mailbox.go b/actors/mailbox.go
new file mode 100644
index 0000000..ee85115
--- /dev/null
+++ b/actors/mailbox.go
@@ -0,0 +1,64 @@
+package actors
+
+import (
+	"github.com/pkg/errors"
+	"go.uber.org/atomic"
+)
+
+var (
+	// errEmptyMailbox is returned when the mailbox is empty
+	errEmptyMailbox = errors.New("mailbox is empty")
+	// errFullMailbox is returned when the mailbox is full
+	errFullMailbox = errors.New("mailbox is full")
+)
+
+// mailbox is the actor mailbox
+type mailbox struct {
+	// specifies the number of messages to stash
+	capacity int
+	counter  *atomic.Uint64
+	buffer   chan ReceiveContext
+}
+
+// newMailbox creates a mailbox with a fixed capacity
+func newMailbox(capacity int) *mailbox {
+	return &mailbox{
+		capacity: capacity,
+		buffer:   make(chan ReceiveContext, capacity),
+		counter:  atomic.NewUint64(0),
+	}
+}
+
+// Push pushes a message into the mailbox. This returns an error
+// when the box is full
+func (x *mailbox) Push(msg ReceiveContext) error {
+	// check whether the buffer is full
+	if int(x.Size()) < x.capacity {
+		x.buffer <- msg
+		x.counter.Inc()
+		return nil
+	}
+	return errFullMailbox
+}
+
+// Pop fetches a message from the mailbox
+func (x *mailbox) Pop() (msg ReceiveContext, err error) {
+	// check whether the buffer is empty
+	if x.IsEmpty() {
+		return nil, errEmptyMailbox
+	}
+	// grab the message
+	msg = <-x.buffer
+	x.counter.Dec()
+	return
+}
+
+// IsEmpty returns true when the buffer is empty
+func (x *mailbox) IsEmpty() bool {
+	return x.counter.Load() == 0
+}
+
+// Size returns the size of the buffer
+func (x *mailbox) Size() uint64 {
+	return x.counter.Load()
+}
diff --git a/actors/option.go b/actors/option.go
index 792f0eb..12b22c6 100644
--- a/actors/option.go
+++ b/actors/option.go
@@ -107,3 +107,10 @@ func WithShutdownTimeout(timeout time.Duration) Option {
 		a.shutdownTimeout = timeout
 	})
 }
+
+// WithMailboxSize sets the actors mailbox size
+func WithMailboxSize(size int) Option {
+	return OptionFunc(func(a *actorSystem) {
+		a.mailboxSize = size
+	})
+}
diff --git a/actors/pid.go b/actors/pid.go
index 7207054..df647f5 100644
--- a/actors/pid.go
+++ b/actors/pid.go
@@ -143,7 +143,9 @@ type pid struct {
 	shutdownTimeout *atomic.Duration
 
 	// specifies the actor mailbox
-	mailbox chan ReceiveContext
+	mailbox     *mailbox
+	mailboxSize int
+
 	// receives a shutdown signal. Once the signal is received
 	// the actor is shut down gracefully.
 	shutdownSignal     chan Unit
@@ -166,7 +168,6 @@ type pid struct {
 	receivedMessageCounter *atomic.Uint64
 	restartCounter         *atomic.Uint64
 	lastProcessingDuration *atomic.Duration
-	mailboxSizeCounter     *atomic.Uint64
 
 	// rwMutex that helps synchronize the pid in a concurrent environment
 	// this helps protect the pid fields accessibility
@@ -204,7 +205,6 @@ func newPID(ctx context.Context, actorPath *Path, actor Actor, opts ...pidOption
 		sendReplyTimeout:       atomic.NewDuration(DefaultReplyTimeout),
 		shutdownTimeout:        atomic.NewDuration(DefaultShutdownTimeout),
 		initMaxRetries:         atomic.NewInt32(DefaultInitMaxRetries),
-		mailbox:                make(chan ReceiveContext, defaultMailboxSize),
 		shutdownSignal:         make(chan Unit, 1),
 		haltPassivationLnr:     make(chan Unit, 1),
 		logger:                 log.DefaultLogger,
@@ -212,22 +212,27 @@ func newPID(ctx context.Context, actorPath *Path, actor Actor, opts ...pidOption
 		receivedMessageCounter: atomic.NewUint64(0),
 		lastProcessingDuration: atomic.NewDuration(0),
 		restartCounter:         atomic.NewUint64(0),
-		mailboxSizeCounter:     atomic.NewUint64(0),
-
-		children:           newPIDMap(10),
-		supervisorStrategy: DefaultSupervisoryStrategy,
-		watchMen:           slices.NewConcurrentSlice[*WatchMan](),
-		telemetry:          telemetry.New(),
-		actorPath:          actorPath,
-		rwMutex:            sync.RWMutex{},
-		shutdownMutex:      sync.Mutex{},
-		httpClient:         http.Client(),
+		mailboxSize:            defaultMailboxSize,
+		children:               newPIDMap(10),
+		supervisorStrategy:     DefaultSupervisoryStrategy,
+		watchMen:               slices.NewConcurrentSlice[*WatchMan](),
+		telemetry:              telemetry.New(),
+		actorPath:              actorPath,
+		rwMutex:                sync.RWMutex{},
+		shutdownMutex:          sync.Mutex{},
+		httpClient:             http.Client(),
 	}
 
 	// set the custom options to override the default values
 	for _, opt := range opts {
 		opt(pid)
 	}
+	// set the mailbox
+	pid.mailbox = newMailbox(pid.mailboxSize)
+	// set the actor behavior stack
+	behaviorStack := newBehaviorStack()
+	behaviorStack.Push(pid.Receive)
+	pid.behaviorStack = behaviorStack
 
 	// initialize the actor and init processing public
 	pid.init(ctx)
@@ -237,12 +242,6 @@ func newPID(ctx context.Context, actorPath *Path, actor Actor, opts ...pidOption
 	if pid.passivateAfter.Load() > 0 {
 		go pid.passivationListener()
 	}
-
-	// set the actor behavior stack
-	behaviorStack := newBehaviorStack()
-	behaviorStack.Push(pid.Receive)
-	pid.behaviorStack = behaviorStack
-
 	// register metrics. However, we don't panic when we fail to register
 	// we just log it for now
 	// TODO decide what to do when we fail to register the metrics or export the metrics registration as public
@@ -412,7 +411,7 @@ func (p *pid) MailboxSize(ctx context.Context) uint64 {
 	// add a span context
 	ctx, span := telemetry.SpanContext(ctx, "MailboxSize")
 	defer span.End()
-	return p.mailboxSizeCounter.Load()
+	return p.mailbox.Size()
 }
 
 // ReceivedCount returns the total number of messages processed by the actor
@@ -751,9 +750,12 @@ func (p *pid) doReceive(ctx ReceiveContext) {
 	}()
 
 	// push the message to the actor mailbox
-	p.mailbox <- ctx
-	// increment the mailbox size counter
-	p.mailboxSizeCounter.Store(uint64(len(p.mailbox)))
+	if err := p.mailbox.Push(ctx); err != nil {
+		// add a warning log because the mailbox is full and do nothing
+		p.logger.Warn(err)
+		return
+	}
+
 	// increase the received message counter
 	p.receivedMessageCounter.Inc()
 }
@@ -798,7 +800,6 @@ func (p *pid) reset() {
 	p.receivedMessageCounter.Store(0)
 	p.lastProcessingDuration.Store(0)
 	p.restartCounter.Store(0)
-	p.mailboxSizeCounter.Store(0)
 	p.children = newPIDMap(10)
 	p.watchMen = slices.NewConcurrentSlice[*WatchMan]()
 	p.telemetry = telemetry.New()
@@ -870,8 +871,13 @@ func (p *pid) receive() {
 		select {
 		case <-p.shutdownSignal:
 			return
-		case received := <-p.mailbox:
-			p.handleReceived(received)
+		default:
+			// only grab the message when the mailbox is not empty
+			if !p.mailbox.IsEmpty() {
+				// grab the message from the mailbox. Ignore the error when the mailbox is empty
+				received, _ := p.mailbox.Pop()
+				p.handleReceived(received)
+			}
 		}
 	}
 }
diff --git a/actors/pid_option.go b/actors/pid_option.go
index dca404c..3f98e73 100644
--- a/actors/pid_option.go
+++ b/actors/pid_option.go
@@ -75,3 +75,10 @@ func withTelemetry(telemetry *telemetry.Telemetry) pidOption {
 		pid.telemetry = telemetry
 	}
 }
+
+// withMailboxSize sets the actor mailbox size
+func withMailboxSize(size int) pidOption {
+	return func(pid *pid) {
+		pid.mailboxSize = size
+	}
+}
diff --git a/readme.md b/readme.md
index 718664f..2673f77 100644
--- a/readme.md
+++ b/readme.md
@@ -210,29 +210,22 @@ earthly +test
 ```
 
 ## Benchmark Result
-One can run the benchmark test: `go test -bench=. -benchtime 2s -count 2 -benchmem -cpu 4 -run notest` from the [bench package](./bench)
+One can run the benchmark test: `go test -bench=. -benchtime 2s -count 5 -benchmem -cpu 8 -run notest` from the [bench package](./bench)
 
 ```bash
 goos: darwin
-goarch: amd64
+goarch: arm64
 pkg: github.com/tochemey/goakt/bench
-cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz
-BenchmarkActor/receive:single_sender-4         	 4113325	       549.1 ns/op	     176 B/op	       4 allocs/op
-BenchmarkActor/receive:single_sender-4         	 4386228	       553.1 ns/op	     176 B/op	       4 allocs/op
-BenchmarkActor/receive:send_only-4             	 4311411	       551.6 ns/op	     176 B/op	       4 allocs/op
-BenchmarkActor/receive:send_only-4             	 4327274	       563.1 ns/op	     176 B/op	       4 allocs/op
-BenchmarkActor/receive:multiple_senders-4      	 1915783	      1255 ns/op	     265 B/op	       5 allocs/op
-BenchmarkActor/receive:multiple_senders-4      	 1694605	      1266 ns/op	     235 B/op	       5 allocs/op
-BenchmarkActor/receive:multiple_senders_times_hundred-4         	   34424	     71860 ns/op	   17744 B/op	     402 allocs/op
-BenchmarkActor/receive:multiple_senders_times_hundred-4         	   35589	     70236 ns/op	   17744 B/op	     402 allocs/op
-BenchmarkActor/receive-reply:_single_sender-4                   	 1254375	      1915 ns/op	     552 B/op	      10 allocs/op
-BenchmarkActor/receive-reply:_single_sender-4                   	 1235786	      1956 ns/op	     552 B/op	      10 allocs/op
-BenchmarkActor/receive-reply:_send_only-4                       	 1236798	      1945 ns/op	     552 B/op	      10 allocs/op
-BenchmarkActor/receive-reply:_send_only-4                       	 1248726	      1897 ns/op	     552 B/op	      10 allocs/op
-BenchmarkActor/receive-reply:multiple_senders-4                 	 1555826	      1654 ns/op	     615 B/op	      11 allocs/op
-BenchmarkActor/receive-reply:multiple_senders-4                 	 1533507	      1733 ns/op	     614 B/op	      11 allocs/op
-BenchmarkActor/receive-reply:multiple_senders_times_hundred-4   	   19311	    133615 ns/op	   55562 B/op	    1004 allocs/op
-BenchmarkActor/receive-reply:multiple_senders_times_hundred-4   	   19354	    133310 ns/op	   55591 B/op	    1004 allocs/op
+BenchmarkActor/tell(send_only)-8         	 5521537	       421.7 ns/op	     176 B/op	       4 allocs/op
+BenchmarkActor/tell(send_only)-8         	 5954017	       400.6 ns/op	     176 B/op	       4 allocs/op
+BenchmarkActor/tell(send_only)-8         	 6015830	       425.8 ns/op	     176 B/op	       4 allocs/op
+BenchmarkActor/tell(send_only)-8         	 5857168	       424.2 ns/op	     176 B/op	       4 allocs/op
+BenchmarkActor/tell(send_only)-8         	 5609856	       426.0 ns/op	     176 B/op	       4 allocs/op
+BenchmarkActor/ask(send/reply)-8         	 2770830	       851.1 ns/op	     552 B/op	      10 allocs/op
+BenchmarkActor/ask(send/reply)-8         	 2818917	       851.3 ns/op	     552 B/op	      10 allocs/op
+BenchmarkActor/ask(send/reply)-8         	 2980618	       853.3 ns/op	     552 B/op	      10 allocs/op
+BenchmarkActor/ask(send/reply)-8         	 2830461	       859.2 ns/op	     552 B/op	      10 allocs/op
+BenchmarkActor/ask(send/reply)-8         	 2893876	       828.0 ns/op	     552 B/op	      10 allocs/op
 PASS
-ok  	github.com/tochemey/goakt/bench	62.953s
+ok  	github.com/tochemey/goakt/bench	32.245s
 ```


Test patch:
diff --git a/actors/mailbox_test.go b/actors/mailbox_test.go
new file mode 100644
index 0000000..9de1504
--- /dev/null
+++ b/actors/mailbox_test.go
@@ -0,0 +1,53 @@
+package actors
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMailbox(t *testing.T) {
+	t.Run("With happy path Push", func(t *testing.T) {
+		buffer := newMailbox(10)
+		assert.True(t, buffer.IsEmpty())
+		err := buffer.Push(new(receiveContext))
+		assert.NoError(t, err)
+		assert.EqualValues(t, 1, buffer.Size())
+	})
+	t.Run("With Push when buffer is full", func(t *testing.T) {
+		buffer := newMailbox(10)
+		assert.True(t, buffer.IsEmpty())
+		for i := 0; i < 10; i++ {
+			err := buffer.Push(new(receiveContext))
+			assert.NoError(t, err)
+		}
+		assert.EqualValues(t, 10, buffer.Size())
+		// push another message to the buffer
+		err := buffer.Push(new(receiveContext))
+		assert.Error(t, err)
+		assert.EqualError(t, err, errFullMailbox.Error())
+		assert.False(t, buffer.IsEmpty())
+	})
+	t.Run("With happy path Pop", func(t *testing.T) {
+		buffer := newMailbox(10)
+		assert.True(t, buffer.IsEmpty())
+		assert.NoError(t, buffer.Push(new(receiveContext)))
+		assert.NoError(t, buffer.Push(new(receiveContext)))
+		assert.NoError(t, buffer.Push(new(receiveContext)))
+		assert.NoError(t, buffer.Push(new(receiveContext)))
+
+		popped, err := buffer.Pop()
+		assert.NoError(t, err)
+		assert.NotNil(t, popped)
+		assert.EqualValues(t, 3, buffer.Size())
+	})
+	t.Run("With Pop when buffer is empty", func(t *testing.T) {
+		buffer := newMailbox(10)
+		assert.True(t, buffer.IsEmpty())
+
+		popped, err := buffer.Pop()
+		assert.Error(t, err)
+		assert.EqualError(t, err, errEmptyMailbox.Error())
+		assert.Nil(t, popped)
+	})
+}
diff --git a/actors/option_test.go b/actors/option_test.go
index 3470484..54d66ae 100644
--- a/actors/option_test.go
+++ b/actors/option_test.go
@@ -62,6 +62,11 @@ func TestOptions(t *testing.T) {
 			option:   WithTelemetry(tel),
 			expected: actorSystem{telemetry: tel},
 		},
+		{
+			name:     "WithMailboxSize",
+			option:   WithMailboxSize(10),
+			expected: actorSystem{mailboxSize: 10},
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/actors/pid_option_test.go b/actors/pid_option_test.go
index 52c9a5e..6ffa7fe 100644
--- a/actors/pid_option_test.go
+++ b/actors/pid_option_test.go
@@ -50,6 +50,11 @@ func TestPIDOptions(t *testing.T) {
 			option:         withPassivationDisabled(),
 			expectedConfig: &pid{passivateAfter: atomic.NewDuration(-1)},
 		},
+		{
+			name:           "WithMailboxSize",
+			option:         withMailboxSize(10),
+			expectedConfig: &pid{mailboxSize: 10},
+		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
diff --git a/bench/bench_test.go b/bench/bench_test.go
index a893d7d..e12e0a0 100644
--- a/bench/bench_test.go
+++ b/bench/bench_test.go
@@ -2,8 +2,6 @@ package bench
 
 import (
 	"context"
-	"fmt"
-	"sync"
 	"testing"
 	"time"
 
@@ -18,7 +16,6 @@ const (
 
 // Benchmarker is an actor that helps run benchmark tests
 type Benchmarker struct {
-	Wg sync.WaitGroup
 }
 
 func (p *Benchmarker) PreStart(context.Context) error {
@@ -28,10 +25,8 @@ func (p *Benchmarker) PreStart(context.Context) error {
 func (p *Benchmarker) Receive(ctx actors.ReceiveContext) {
 	switch ctx.Message().(type) {
 	case *testspb.TestSend:
-		p.Wg.Done()
 	case *testspb.TestReply:
 		ctx.Response(&testspb.Reply{Content: "received message"})
-		p.Wg.Done()
 	}
 }
 
@@ -40,13 +35,14 @@ func (p *Benchmarker) PostStop(context.Context) error {
 }
 
 func BenchmarkActor(b *testing.B) {
-	b.Run("receive:single sender", func(b *testing.B) {
+	b.Run("tell(send only)", func(b *testing.B) {
 		ctx := context.TODO()
 
 		// create the actor system
 		actorSystem, _ := actors.NewActorSystem("testSys",
 			actors.WithLogger(log.DiscardLogger),
 			actors.WithActorInitMaxRetries(1),
+			actors.WithMailboxSize(b.N),
 			actors.WithReplyTimeout(receivingTimeout))
 
 		// start the actor system
@@ -59,122 +55,23 @@ func BenchmarkActor(b *testing.B) {
 		pid := actorSystem.Spawn(ctx, "test", actor)
 
 		b.ResetTimer() // Reset the benchmark timer
-		actor.Wg.Add(b.N)
-		go func() {
-			for i := 0; i < b.N; i++ {
+		b.RunParallel(func(pb *testing.PB) {
+			for pb.Next() {
 				// send a message to the actor
 				_ = actors.Tell(ctx, pid, &testspb.TestSend{})
 			}
-		}()
-		actor.Wg.Wait()
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive:send only", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
-
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N)
-		for i := 0; i < b.N; i++ {
-			// send a message to the actor
-			_ = actors.Tell(ctx, pid, new(testspb.TestSend))
-		}
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive:multiple senders", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
+		})
 
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N)
-		for i := 0; i < b.N; i++ {
-			go func() {
-				defer func() {
-					if r := recover(); r != nil {
-						fmt.Println("Failed to send", r)
-					}
-				}()
-				// send a message to the actor
-				_ = actors.Tell(ctx, pid, new(testspb.TestSend))
-			}()
-		}
-		actor.Wg.Wait()
 		_ = pid.Shutdown(ctx)
 		_ = actorSystem.Stop(ctx)
 	})
-	b.Run("receive:multiple senders times hundred", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
-
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N * 100)
-		for i := 0; i < b.N; i++ {
-			go func() {
-				defer func() {
-					if r := recover(); r != nil {
-						fmt.Println("Failed to send", r)
-					}
-				}()
-				for i := 0; i < 100; i++ {
-					// send a message to the actor
-					_ = actors.Tell(ctx, pid, new(testspb.TestSend))
-				}
-			}()
-		}
-		actor.Wg.Wait()
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive-reply: single sender", func(b *testing.B) {
+	b.Run("ask(send/reply)", func(b *testing.B) {
 		ctx := context.TODO()
 		// create the actor system
 		actorSystem, _ := actors.NewActorSystem("testSys",
 			actors.WithLogger(log.DiscardLogger),
 			actors.WithActorInitMaxRetries(1),
+			actors.WithMailboxSize(b.N),
 			actors.WithExpireActorAfter(5*time.Second),
 			actors.WithReplyTimeout(receivingTimeout))
 
@@ -188,117 +85,12 @@ func BenchmarkActor(b *testing.B) {
 		pid := actorSystem.Spawn(ctx, "test", actor)
 
 		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N)
-		go func() {
-			for i := 0; i < b.N; i++ {
+		b.RunParallel(func(pb *testing.PB) {
+			for pb.Next() {
 				// send a message to the actor
 				_, _ = actors.Ask(ctx, pid, new(testspb.TestReply), receivingTimeout)
 			}
-		}()
-		actor.Wg.Wait()
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive-reply: send only", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithExpireActorAfter(5*time.Second),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
-
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N)
-		for i := 0; i < b.N; i++ {
-			// send a message to the actor
-			_, _ = actors.Ask(ctx, pid, new(testspb.TestReply), receivingTimeout)
-		}
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive-reply:multiple senders", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithExpireActorAfter(5*time.Second),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
-
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N)
-		for i := 0; i < b.N; i++ {
-			go func() {
-				defer func() {
-					if r := recover(); r != nil {
-						fmt.Println("Failed to send", r)
-					}
-				}()
-				// send a message to the actor
-				_, _ = actors.Ask(ctx, pid, new(testspb.TestReply), receivingTimeout)
-			}()
-		}
-		actor.Wg.Wait()
-		_ = pid.Shutdown(ctx)
-		_ = actorSystem.Stop(ctx)
-	})
-	b.Run("receive-reply:multiple senders times hundred", func(b *testing.B) {
-		ctx := context.TODO()
-		// create the actor system
-		actorSystem, _ := actors.NewActorSystem("testSys",
-			actors.WithLogger(log.DiscardLogger),
-			actors.WithActorInitMaxRetries(1),
-			actors.WithExpireActorAfter(5*time.Second),
-			actors.WithReplyTimeout(receivingTimeout))
-
-		// start the actor system
-		_ = actorSystem.Start(ctx)
-
-		// define the benchmark actor
-		actor := &Benchmarker{}
-
-		// create the actor ref
-		pid := actorSystem.Spawn(ctx, "test", actor)
-
-		b.ResetTimer() // Reset the benchmark timer
-
-		actor.Wg.Add(b.N * 100)
-		for i := 0; i < b.N; i++ {
-			go func() {
-				defer func() {
-					if r := recover(); r != nil {
-						fmt.Println("Failed to send", r)
-					}
-				}()
-				for i := 0; i < 100; i++ {
-					// send a message to the actor
-					_, _ = actors.Ask(ctx, pid, new(testspb.TestReply), receivingTimeout)
-				}
-			}()
-		}
-		actor.Wg.Wait()
+		})
 		_ = pid.Shutdown(ctx)
 		_ = actorSystem.Stop(ctx)
 	})