What changed, and why it matters
This commit only adds new test code for an existing mailbox implementation. It does not change any production code, fix bugs, or alter behavior. There is no security issue in the commit itself.
No security action needed; review the associated mailbox implementation separately if concerns exist, but this commit is purely additive test coverage.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces actor/mailbox_test.go, a 593-line Go test file exercising ChannelMailbox and the Mailbox interface. It covers Send/TrySend/Receive/Close/Drain behavior, context cancellation, actor context cancellation, zero-capacity handling, and concurrent/stress scenarios. No production source files are modified.
Changed components
actor/mailbox_test.goInspect captured patch +593 / −0
diff --git a/actor/mailbox_test.go b/actor/mailbox_test.go
new file mode 100644
index 0000000..b96d5e4
--- /dev/null
+++ b/actor/mailbox_test.go
@@ -0,0 +1,593 @@
+package actor
+
+import (
+ "context"
+ "sync"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+// TestMessage is a test message type that embeds BaseMessage.
+type TestMessage struct {
+ BaseMessage
+ Value int
+}
+
+// MessageType returns the type name of the message for routing/filtering.
+func (tm TestMessage) MessageType() string {
+ return "TestMessage"
+}
+
+// TestChannelMailboxSend tests the Send method of ChannelMailbox.
+func TestChannelMailboxSend(t *testing.T) {
+ t.Run("successful send", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ ctx := context.Background()
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ sent := mailbox.Send(ctx, env)
+ require.True(t, sent, "Send should succeed")
+ })
+
+ t.Run("send with cancelled context", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 1)
+ // Fill the mailbox first.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+ mailbox.TrySend(env)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ // Cancel immediately.
+ cancel()
+
+ env2 := envelope[TestMessage, int]{
+ message: TestMessage{Value: 43},
+ promise: nil,
+ }
+
+ sent := mailbox.Send(ctx, env2)
+ require.False(t, sent, "Send should fail with cancelled context")
+ })
+
+ t.Run("send to closed mailbox", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ mailbox.Close()
+
+ ctx := context.Background()
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ sent := mailbox.Send(ctx, env)
+ require.False(t, sent, "Send should fail on closed mailbox")
+ })
+}
+
+// TestChannelMailboxTrySend tests the TrySend method of ChannelMailbox.
+func TestChannelMailboxTrySend(t *testing.T) {
+ t.Run("successful try send", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ sent := mailbox.TrySend(env)
+ require.True(t, sent, "TrySend should succeed")
+ })
+
+ t.Run("try send to full mailbox", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 1)
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ // Fill the mailbox.
+ sent := mailbox.TrySend(env)
+ require.True(t, sent, "First TrySend should succeed")
+
+ // Try to send again - should fail.
+ sent = mailbox.TrySend(env)
+ require.False(t, sent, "TrySend should fail on full mailbox")
+ })
+
+ t.Run("try send to closed mailbox", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ mailbox.Close()
+
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ sent := mailbox.TrySend(env)
+ require.False(t, sent, "TrySend should fail on closed mailbox")
+ })
+}
+
+// TestChannelMailboxReceive tests the Receive method of ChannelMailbox.
+func TestChannelMailboxReceive(t *testing.T) {
+ t.Run("receive messages", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ ctx := context.Background()
+
+ // Send some messages.
+ for i := 0; i < 3; i++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: i},
+ promise: nil,
+ }
+ mailbox.Send(ctx, env)
+ }
+
+ // Start receiving in a goroutine.
+ var received []int
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for env := range mailbox.Receive(ctx) {
+ received = append(received, env.message.Value)
+ }
+ }()
+
+ // Close the mailbox after sending all messages.
+ mailbox.Close()
+ wg.Wait()
+
+ require.Len(t, received, 3, "Should receive 3 messages")
+ require.Equal(t, []int{0, 1, 2}, received, "Should receive messages in order")
+ })
+
+ t.Run("receive with cancelled context", func(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ ctx, cancel := context.WithCancel(context.Background())
+
+ // Send a message.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+ mailbox.Send(context.Background(), env)
+
+ // Start receiving.
+ var received int
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for env := range mailbox.Receive(ctx) {
+ received++
+ _ = env
+ }
+ }()
+
+ // Cancel the context.
+ cancel()
+ wg.Wait()
+
+ // Might receive 0 or 1 message depending on timing.
+ require.LessOrEqual(t, received, 1,
+ "Should stop receiving after context cancel")
+ })
+}
+
+// TestChannelMailboxClose tests the Close and IsClosed methods.
+func TestChannelMailboxClose(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+
+ require.False(t, mailbox.IsClosed(), "Mailbox should not be closed initially")
+
+ mailbox.Close()
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed after Close()")
+
+ // Closing again should be safe.
+ mailbox.Close()
+ require.True(t, mailbox.IsClosed(), "Mailbox should remain closed")
+}
+
+// TestChannelMailboxDrain tests the Drain method of ChannelMailbox.
+func TestChannelMailboxDrain(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+ ctx := context.Background()
+
+ // Send some messages.
+ for i := 0; i < 3; i++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: i},
+ promise: nil,
+ }
+ mailbox.Send(ctx, env)
+ }
+
+ // Close the mailbox.
+ mailbox.Close()
+
+ // Drain messages.
+ var drained []int
+ for env := range mailbox.Drain() {
+ drained = append(drained, env.message.Value)
+ }
+
+ require.Len(t, drained, 3, "Should drain 3 messages")
+ require.Equal(t, []int{0, 1, 2}, drained, "Should drain messages in order")
+}
+
+// TestChannelMailboxConcurrent tests concurrent operations on ChannelMailbox.
+func TestChannelMailboxConcurrent(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100)
+ ctx := context.Background()
+
+ const numSenders = 10
+ const messagesPerSender = 100
+
+ var wg sync.WaitGroup
+
+ // Start multiple senders.
+ for i := 0; i < numSenders; i++ {
+ wg.Add(1)
+ go func(senderID int) {
+ defer wg.Done()
+ for j := 0; j < messagesPerSender; j++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: senderID*1000 + j},
+ promise: nil,
+ }
+ mailbox.Send(ctx, env)
+ }
+ }(i)
+ }
+
+ // Start receiver.
+ received := make([]int, 0, numSenders*messagesPerSender)
+ var receiverWg sync.WaitGroup
+ receiverWg.Add(1)
+ go func() {
+ defer receiverWg.Done()
+ for env := range mailbox.Receive(ctx) {
+ received = append(received, env.message.Value)
+ }
+ }()
+
+ // Wait for all senders to complete.
+ wg.Wait()
+
+ // Close the mailbox now that all sends are complete.
+ mailbox.Close()
+ receiverWg.Wait()
+
+ require.Len(t, received, numSenders*messagesPerSender,
+ "Should receive all messages")
+}
+
+// TestChannelMailboxZeroCapacity tests that zero capacity defaults to 1.
+func TestChannelMailboxZeroCapacity(t *testing.T) {
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 0)
+
+ // Should default to capacity of 1.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+
+ sent := mailbox.TrySend(env)
+ require.True(t, sent, "Should be able to send one message")
+
+ // Second send should fail (mailbox full).
+ sent = mailbox.TrySend(env)
+ require.False(t, sent, "Second send should fail on full mailbox")
+}
+
+// TestChannelMailboxActorContext tests that the mailbox respects the actor's
+// context for cancellation.
+func TestChannelMailboxActorContext(t *testing.T) {
+ t.Run("send respects actor context", func(t *testing.T) {
+ actorCtx, actorCancel := context.WithCancel(context.Background())
+ mailbox := NewChannelMailbox[TestMessage, int](actorCtx, 1)
+
+ // Fill the mailbox.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+ mailbox.TrySend(env)
+
+ // Cancel the actor context.
+ actorCancel()
+
+ // Try to send with a fresh caller context - should fail due to
+ // actor context cancellation.
+ callerCtx := context.Background()
+ env2 := envelope[TestMessage, int]{
+ message: TestMessage{Value: 43},
+ promise: nil,
+ }
+
+ sent := mailbox.Send(callerCtx, env2)
+ require.False(t, sent, "Send should fail when actor context is cancelled")
+ })
+
+ t.Run("receive respects actor context", func(t *testing.T) {
+ actorCtx, actorCancel := context.WithCancel(context.Background())
+ mailbox := NewChannelMailbox[TestMessage, int](actorCtx, 10)
+
+ // Send a message.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 42},
+ promise: nil,
+ }
+ mailbox.Send(context.Background(), env)
+
+ // Start receiving with a fresh context.
+ callerCtx := context.Background()
+ var received int
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for env := range mailbox.Receive(callerCtx) {
+ received++
+ _ = env
+ }
+ }()
+
+ // Cancel the actor context.
+ actorCancel()
+ wg.Wait()
+
+ // Should have stopped receiving due to actor context cancellation.
+ require.LessOrEqual(t, received, 1,
+ "Should stop receiving when actor context is cancelled")
+ })
+}
+
+// TestMailboxConcurrentSendAndClose tests concurrent Send and Close operations
+// to ensure no race conditions or panics occur.
+func TestMailboxConcurrentSendAndClose(t *testing.T) {
+ const numSenders = 20
+ const sendsPerSender = 100
+
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100)
+ ctx := context.Background()
+
+ var wg sync.WaitGroup
+
+ // Start receiver to drain messages.
+ var recvWg sync.WaitGroup
+ recvWg.Add(1)
+ go func() {
+ defer recvWg.Done()
+ for range mailbox.Receive(ctx) {
+ // Just drain.
+ }
+ }()
+
+ // Start multiple senders.
+ for i := 0; i < numSenders; i++ {
+ wg.Add(1)
+ go func(senderID int) {
+ defer wg.Done()
+ for j := 0; j < sendsPerSender; j++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: senderID*1000 + j},
+ promise: nil,
+ }
+ // Send may fail if mailbox closes, that's ok.
+ mailbox.Send(ctx, env)
+ }
+ }(i)
+ }
+
+ // Concurrently close the mailbox multiple times from different
+ // goroutines.
+ for i := 0; i < 5; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ mailbox.Close()
+ }()
+ }
+
+ wg.Wait()
+ recvWg.Wait()
+
+ // Mailbox should be closed.
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed")
+
+ // Further sends should fail without panic.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 999},
+ promise: nil,
+ }
+ sent := mailbox.Send(ctx, env)
+ require.False(t, sent, "Send should fail on closed mailbox")
+}
+
+// TestMailboxConcurrentTrySendAndClose tests concurrent TrySend and Close
+// operations to ensure no race conditions or panics occur.
+func TestMailboxConcurrentTrySendAndClose(t *testing.T) {
+ const numSenders = 20
+ const sendsPerSender = 100
+
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+
+ var wg sync.WaitGroup
+
+ // Start multiple senders using TrySend.
+ for i := 0; i < numSenders; i++ {
+ wg.Add(1)
+ go func(senderID int) {
+ defer wg.Done()
+ for j := 0; j < sendsPerSender; j++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: senderID*1000 + j},
+ promise: nil,
+ }
+ // TrySend may fail if mailbox is full or closed.
+ mailbox.TrySend(env)
+ }
+ }(i)
+ }
+
+ // Concurrently close the mailbox.
+ for i := 0; i < 5; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ mailbox.Close()
+ }()
+ }
+
+ wg.Wait()
+
+ // Mailbox should be closed.
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed")
+
+ // Further sends should fail without panic.
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: 999},
+ promise: nil,
+ }
+ sent := mailbox.TrySend(env)
+ require.False(t, sent, "TrySend should fail on closed mailbox")
+}
+
+// TestMailboxMultipleCloseCallers tests that multiple goroutines calling
+// Close() simultaneously don't cause panics or issues.
+func TestMailboxMultipleCloseCallers(t *testing.T) {
+ const numClosers = 100
+
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 10)
+
+ var wg sync.WaitGroup
+
+ // Start many goroutines all trying to close the mailbox.
+ for i := 0; i < numClosers; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ mailbox.Close()
+ }()
+ }
+
+ wg.Wait()
+
+ // Mailbox should be closed exactly once.
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed")
+
+ // Calling Close again should be safe.
+ mailbox.Close()
+ require.True(t, mailbox.IsClosed(), "Mailbox should remain closed")
+}
+
+// TestMailboxCloseWhileSending tests closing the mailbox while multiple
+// senders are actively sending messages.
+func TestMailboxCloseWhileSending(t *testing.T) {
+ const numSenders = 10
+ const sendsPerSender = 1000
+
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 100)
+ ctx := context.Background()
+
+ var sendWg sync.WaitGroup
+
+ // Start multiple senders.
+ for i := 0; i < numSenders; i++ {
+ sendWg.Add(1)
+ go func(senderID int) {
+ defer sendWg.Done()
+ for j := 0; j < sendsPerSender; j++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: senderID*1000 + j},
+ promise: nil,
+ }
+ // Send may fail after close, that's expected.
+ mailbox.Send(ctx, env)
+ }
+ }(i)
+ }
+
+ // Start receiver to drain messages.
+ var recvWg sync.WaitGroup
+ recvWg.Add(1)
+ receivedCount := 0
+ go func() {
+ defer recvWg.Done()
+ for range mailbox.Receive(ctx) {
+ receivedCount++
+ }
+ }()
+
+ // Close mailbox while sends are happening.
+ mailbox.Close()
+
+ sendWg.Wait()
+ recvWg.Wait()
+
+ // Should have received at least some messages (exact count depends on
+ // timing).
+ t.Logf("Received %d messages before close", receivedCount)
+
+ // Mailbox should be closed.
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed")
+}
+
+// TestMailboxStressTest performs a high-concurrency stress test with multiple
+// senders, receivers, and close operations.
+func TestMailboxStressTest(t *testing.T) {
+ const numSenders = 50
+ const numReceivers = 5
+ const sendsPerSender = 200
+
+ mailbox := NewChannelMailbox[TestMessage, int](context.Background(), 200)
+ ctx := context.Background()
+
+ var sendWg sync.WaitGroup
+
+ // Start multiple senders.
+ for i := 0; i < numSenders; i++ {
+ sendWg.Add(1)
+ go func(senderID int) {
+ defer sendWg.Done()
+ for j := 0; j < sendsPerSender; j++ {
+ env := envelope[TestMessage, int]{
+ message: TestMessage{Value: senderID*1000 + j},
+ promise: nil,
+ }
+ mailbox.Send(ctx, env)
+ }
+ }(i)
+ }
+
+ // Start multiple receivers.
+ var recvWg sync.WaitGroup
+ for i := 0; i < numReceivers; i++ {
+ recvWg.Add(1)
+ go func() {
+ defer recvWg.Done()
+ for range mailbox.Receive(ctx) {
+ // Just drain messages.
+ }
+ }()
+ }
+
+ // Wait for all sends to complete.
+ sendWg.Wait()
+
+ // Close mailbox.
+ mailbox.Close()
+
+ // Wait for all receivers to finish.
+ recvWg.Wait()
+
+ // Mailbox should be closed.
+ require.True(t, mailbox.IsClosed(), "Mailbox should be closed")
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.