From 1a579c214925ca77c225dfd92f5fb5b41f8811a9 Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Thu, 21 Nov 2024 10:26:48 +0100 Subject: [PATCH 1/2] Add mutex for concurrent send protection Introduced a sendMutex to synchronize access to shared resources in the DialAndSendWithContext method. This ensures thread safety when sending multiple messages concurrently. Added a corresponding test to verify the concurrent sending functionality. --- client.go | 5 +++++ client_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/client.go b/client.go index c301f45..a7dead8 100644 --- a/client.go +++ b/client.go @@ -170,6 +170,9 @@ type ( // requestDSN indicates wether we want to request DSN (Delivery Status Notifications). requestDSN bool + // sendMutex is used to synchronize access to shared resources during the dial and send methods. + sendMutex sync.Mutex + // smtpAuth is the authentication type that is used to authenticate the user with SMTP server. It // satisfies the smtp.Auth interface. // @@ -1058,6 +1061,8 @@ func (c *Client) DialAndSend(messages ...*Msg) error { // - An error if the connection fails, if sending the messages fails, or if closing the // connection fails; otherwise, returns nil. func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) error { + c.sendMutex.Lock() + defer c.sendMutex.Unlock() if err := c.DialWithContext(ctx); err != nil { return fmt.Errorf("dial failed: %w", err) } diff --git a/client_test.go b/client_test.go index a6530f0..d1912f4 100644 --- a/client_test.go +++ b/client_test.go @@ -2297,6 +2297,45 @@ func TestClient_DialAndSendWithContext(t *testing.T) { t.Errorf("client was supposed to fail on dial") } }) + t.Run("concurrent sending via DialAndSendWithContext", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + PortAdder.Add(1) + serverPort := int(TestServerPortBase + PortAdder.Load()) + featureSet := "250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" + go func() { + if err := simpleSMTPServer(ctx, t, &serverProps{ + FeatureSet: featureSet, + ListenPort: serverPort, + }); err != nil { + t.Errorf("failed to start test server: %s", err) + return + } + }() + time.Sleep(time.Millisecond * 30) + + client, err := NewClient(DefaultHost, WithPort(serverPort), WithTLSPolicy(NoTLS)) + if err != nil { + t.Fatalf("failed to create new client: %s", err) + } + + wg := sync.WaitGroup{} + for i := 0; i < 50; i++ { + wg.Add(1) + go func() { + defer wg.Done() + msg := testMessage(t) + msg.SetMessageIDWithValue("this.is.a.message.id") + + ctxDial, cancelDial := context.WithTimeout(ctx, time.Second*5) + defer cancelDial() + if goroutineErr := client.DialAndSendWithContext(ctxDial, msg); goroutineErr != nil { + t.Errorf("failed to dial and send message: %s", goroutineErr) + } + }() + } + wg.Wait() + }) } func TestClient_auth(t *testing.T) { From 6e9df0b724bb4be71f520c037c02e49bfe5ed31e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Thu, 21 Nov 2024 10:33:24 +0100 Subject: [PATCH 2/2] Increase timeout for DialAndSend context Updated the timeout for the DialAndSend context in 'client_test.go' from 5 seconds to 1 minute to ensure sufficient time for the operation to complete. This change helps prevent premature timeouts that can cause test failures. --- client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index d1912f4..f6d5161 100644 --- a/client_test.go +++ b/client_test.go @@ -2327,7 +2327,7 @@ func TestClient_DialAndSendWithContext(t *testing.T) { msg := testMessage(t) msg.SetMessageIDWithValue("this.is.a.message.id") - ctxDial, cancelDial := context.WithTimeout(ctx, time.Second*5) + ctxDial, cancelDial := context.WithTimeout(ctx, time.Minute) defer cancelDial() if goroutineErr := client.DialAndSendWithContext(ctxDial, msg); goroutineErr != nil { t.Errorf("failed to dial and send message: %s", goroutineErr)