You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2026/07/10 02:20:02 UTC

(answer) 16/25: fix(notification): make new question email queue configurable

This is an automated email from the ASF dual-hosted git repository.

LinkinStars pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/answer.git

commit e06311a51403224d9b9a7aace4d960c7960f6986
Author: Artur Iusupov <iu...@phystech.edu>
AuthorDate: Wed Jul 1 13:30:44 2026 +0400

    fix(notification): make new question email queue configurable
---
 .../notification/new_question_email_worker.go      |  30 ++++-
 .../notification/new_question_email_worker_test.go | 128 +++++++++++++++++++++
 2 files changed, 156 insertions(+), 2 deletions(-)

diff --git a/internal/service/notification/new_question_email_worker.go b/internal/service/notification/new_question_email_worker.go
index be3e6504..c3df1bee 100644
--- a/internal/service/notification/new_question_email_worker.go
+++ b/internal/service/notification/new_question_email_worker.go
@@ -21,6 +21,9 @@ package notification
 
 import (
 	"context"
+	"os"
+	"strconv"
+	"strings"
 	"sync"
 	"time"
 
@@ -29,7 +32,11 @@ import (
 	"github.com/segmentfault/pacman/log"
 )
 
-const newQuestionEmailWorkerQueueSize = 128
+const defaultNewQuestionEmailWorkerQueueSize = 1024
+
+const maxNewQuestionEmailWorkerQueueSize = 65536
+
+const newQuestionEmailWorkerQueueSizeEnv = "NEW_QUESTION_NOTIFICATION_EMAIL_QUEUE_SIZE"
 
 type newQuestionEmailTask struct {
 	UserIDs       []string
@@ -68,10 +75,29 @@ func newQuestionEmailWorkerWithDefaults(
 		interval,
 		send,
 		newRealNewQuestionEmailTimer,
-		newQuestionEmailWorkerQueueSize,
+		newQuestionEmailWorkerQueueSize(),
 	)
 }
 
+func newQuestionEmailWorkerQueueSize() int {
+	return parseNewQuestionEmailWorkerQueueSize(os.Getenv(newQuestionEmailWorkerQueueSizeEnv))
+}
+
+func parseNewQuestionEmailWorkerQueueSize(value string) int {
+	value = strings.TrimSpace(value)
+	if len(value) == 0 {
+		return defaultNewQuestionEmailWorkerQueueSize
+	}
+	queueSize, err := strconv.ParseInt(value, 10, 64)
+	if err != nil || queueSize <= 0 {
+		return defaultNewQuestionEmailWorkerQueueSize
+	}
+	if queueSize > int64(maxNewQuestionEmailWorkerQueueSize) {
+		return maxNewQuestionEmailWorkerQueueSize
+	}
+	return int(queueSize)
+}
+
 func newQuestionEmailWorkerWithBuffer(
 	interval newQuestionEmailIntervalProvider,
 	send newQuestionNotificationEmailSender,
diff --git a/internal/service/notification/new_question_email_worker_test.go b/internal/service/notification/new_question_email_worker_test.go
index 302de4eb..e7ea4880 100644
--- a/internal/service/notification/new_question_email_worker_test.go
+++ b/internal/service/notification/new_question_email_worker_test.go
@@ -21,6 +21,7 @@ package notification
 
 import (
 	"context"
+	"os"
 	"reflect"
 	"runtime"
 	"sync"
@@ -31,6 +32,111 @@ import (
 	"github.com/apache/answer/internal/schema"
 )
 
+func TestParseNewQuestionEmailWorkerQueueSize(t *testing.T) {
+	tests := []struct {
+		name  string
+		value string
+		want  int
+	}{
+		{
+			name:  "empty",
+			value: "",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "whitespace",
+			value: "   ",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "invalid",
+			value: "invalid",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "zero",
+			value: "0",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "negative",
+			value: "-1",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "parse int overflow",
+			value: "9223372036854775808",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "positive",
+			value: "2048",
+			want:  2048,
+		},
+		{
+			name:  "max",
+			value: "65536",
+			want:  maxNewQuestionEmailWorkerQueueSize,
+		},
+		{
+			name:  "above max",
+			value: "65537",
+			want:  maxNewQuestionEmailWorkerQueueSize,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := parseNewQuestionEmailWorkerQueueSize(tt.value)
+			if got != tt.want {
+				t.Fatalf("parseNewQuestionEmailWorkerQueueSize(%q) = %d, want %d", tt.value, got, tt.want)
+			}
+		})
+	}
+}
+
+func TestNewQuestionEmailWorkerQueueSizeUnsetEnv(t *testing.T) {
+	setNewQuestionEmailWorkerQueueSizeEnv(t, "", false)
+
+	got := newQuestionEmailWorkerQueueSize()
+	if got != defaultNewQuestionEmailWorkerQueueSize {
+		t.Fatalf("newQuestionEmailWorkerQueueSize() = %d, want %d",
+			got, defaultNewQuestionEmailWorkerQueueSize)
+	}
+}
+
+func TestNewQuestionEmailWorkerWithDefaultsUsesQueueSizeEnv(t *testing.T) {
+	tests := []struct {
+		name  string
+		value string
+		want  int
+	}{
+		{
+			name:  "configured",
+			value: "2048",
+			want:  2048,
+		},
+		{
+			name:  "invalid uses default",
+			value: "invalid",
+			want:  defaultNewQuestionEmailWorkerQueueSize,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			setNewQuestionEmailWorkerQueueSizeEnv(t, tt.value, true)
+
+			worker := newQuestionEmailWorkerWithDefaults(func() time.Duration { return 0 }, nil)
+			defer worker.Close()
+
+			if got := cap(worker.tasks); got != tt.want {
+				t.Fatalf("cap(worker.tasks) = %d, want %d", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestNewQuestionEmailWorkerDelaysBetweenAttempts(t *testing.T) {
 	timerFactory := newFakeNewQuestionEmailTimerFactory()
 	sendCh := make(chan newQuestionEmailSendEvent, 2)
@@ -438,6 +544,28 @@ func newUnstartedNewQuestionEmailWorkerForTest(bufferSize int) *newQuestionEmail
 	}
 }
 
+func setNewQuestionEmailWorkerQueueSizeEnv(t *testing.T, value string, set bool) {
+	t.Helper()
+
+	oldValue, oldSet := os.LookupEnv(newQuestionEmailWorkerQueueSizeEnv)
+	if set {
+		if err := os.Setenv(newQuestionEmailWorkerQueueSizeEnv, value); err != nil {
+			t.Fatalf("set env: %v", err)
+		}
+	} else {
+		if err := os.Unsetenv(newQuestionEmailWorkerQueueSizeEnv); err != nil {
+			t.Fatalf("unset env: %v", err)
+		}
+	}
+	t.Cleanup(func() {
+		if oldSet {
+			_ = os.Setenv(newQuestionEmailWorkerQueueSizeEnv, oldValue)
+		} else {
+			_ = os.Unsetenv(newQuestionEmailWorkerQueueSizeEnv)
+		}
+	})
+}
+
 func receiveNewQuestionEmailSend(t *testing.T, sendCh <-chan newQuestionEmailSendEvent) newQuestionEmailSendEvent {
 	t.Helper()
 	select {