Compare commits

..

2 Commits

3 changed files with 22 additions and 1 deletions

View File

@@ -138,6 +138,8 @@ func runCycle(
return return
} }
evaluator.WaitForNotifications()
duration := time.Since(startTime) duration := time.Since(startTime)
log.Printf("[%s] Cycle complete: %d observations, %d alerts fired in %s", log.Printf("[%s] Cycle complete: %d observations, %d alerts fired in %s",
time.Now().UTC().Format(time.RFC3339), time.Now().UTC().Format(time.RFC3339),

View File

@@ -18,7 +18,7 @@ import (
const ( const (
rpcTimeoutMS = 30000 rpcTimeoutMS = 30000
rpcMaxRetries = 3 rpcMaxRetries = 3
rpcRetryBaseMS = 1000 rpcRetryBaseMS = 2000
) )
type JsonRpcEthereum struct { type JsonRpcEthereum struct {

View File

@@ -4,8 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"log" "log"
"sync"
"time" "time"
"golang.org/x/sync/semaphore"
"github.com/kjannette/koin-ping/backend-go/internal/domain" "github.com/kjannette/koin-ping/backend-go/internal/domain"
"github.com/kjannette/koin-ping/backend-go/internal/models" "github.com/kjannette/koin-ping/backend-go/internal/models"
"github.com/kjannette/koin-ping/backend-go/internal/notifications" "github.com/kjannette/koin-ping/backend-go/internal/notifications"
@@ -17,6 +20,7 @@ const (
notificationTimeout = 30 * time.Second notificationTimeout = 30 * time.Second
notificationMaxRetries = 3 notificationMaxRetries = 3
notificationRetryBase = time.Second notificationRetryBase = time.Second
maxConcurrentNotifications = 5
) )
type EvaluatorService struct { type EvaluatorService struct {
@@ -27,6 +31,8 @@ type EvaluatorService struct {
notifConfigs *models.NotificationConfigModel notifConfigs *models.NotificationConfigModel
resendAPIKey string resendAPIKey string
emailFrom string emailFrom string
notifSem *semaphore.Weighted
notifWg sync.WaitGroup
} }
func NewEvaluatorService( func NewEvaluatorService(
@@ -46,6 +52,7 @@ func NewEvaluatorService(
notifConfigs: notifConfigs, notifConfigs: notifConfigs,
resendAPIKey: resendAPIKey, resendAPIKey: resendAPIKey,
emailFrom: emailFrom, emailFrom: emailFrom,
notifSem: semaphore.NewWeighted(maxConcurrentNotifications),
} }
} }
@@ -189,7 +196,14 @@ func (s *EvaluatorService) fireAlert(ctx context.Context, rule domain.AlertRule,
if addr != nil { if addr != nil {
userID := addr.UserID userID := addr.UserID
address := addr.Address address := addr.Address
if err := s.notifSem.Acquire(ctx, 1); err != nil {
log.Printf("Failed to acquire notification semaphore for rule %d: %v", rule.ID, err)
return nil
}
s.notifWg.Add(1)
go func() { go func() {
defer s.notifSem.Release(1)
defer s.notifWg.Done()
notifCtx, cancel := context.WithTimeout(context.Background(), notificationTimeout) notifCtx, cancel := context.WithTimeout(context.Background(), notificationTimeout)
defer cancel() defer cancel()
s.sendNotification(notifCtx, userID, message, obs, addressLabel, rule, address) s.sendNotification(notifCtx, userID, message, obs, addressLabel, rule, address)
@@ -199,6 +213,11 @@ func (s *EvaluatorService) fireAlert(ctx context.Context, rule domain.AlertRule,
return nil return nil
} }
// WaitForNotifications blocks until all in-flight notification goroutines finish.
func (s *EvaluatorService) WaitForNotifications() {
s.notifWg.Wait()
}
func (s *EvaluatorService) buildNotifiers(cfg *domain.NotificationConfig) []notifications.Notifier { func (s *EvaluatorService) buildNotifiers(cfg *domain.NotificationConfig) []notifications.Notifier {
var notifiers []notifications.Notifier var notifiers []notifications.Notifier