Implement notification reload mechanism and improve thread safety with mutex

This commit is contained in:
headlessdev 2025-04-17 16:13:29 +02:00
parent e925f37b19
commit 8f647d3489

View File

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
_ "github.com/jackc/pgx/v4/stdlib" _ "github.com/jackc/pgx/v4/stdlib"
@ -36,7 +37,10 @@ type Notification struct {
DiscordWebhook sql.NullString DiscordWebhook sql.NullString
} }
var notifications []Notification var (
notifications []Notification
notifMutex sync.RWMutex
)
func main() { func main() {
if err := godotenv.Load(); err != nil { if err := godotenv.Load(); err != nil {
@ -54,12 +58,34 @@ func main() {
} }
defer db.Close() defer db.Close()
// Load notification configs // initial load
notifications, err = loadNotifications(db) notifs, err := loadNotifications(db)
if err != nil { if err != nil {
panic(fmt.Sprintf("Failed to load notifications: %v", err)) panic(fmt.Sprintf("Failed to load notifications: %v", err))
} }
notifMutex.Lock()
notifications = notifMutexCopy(notifs)
notifMutex.Unlock()
// reload notification configs every minute
go func() {
reloadTicker := time.NewTicker(time.Minute)
defer reloadTicker.Stop()
for range reloadTicker.C {
newNotifs, err := loadNotifications(db)
if err != nil {
fmt.Printf("Failed to reload notifications: %v\n", err)
continue
}
notifMutex.Lock()
notifications = notifMutexCopy(newNotifs)
notifMutex.Unlock()
fmt.Println("Reloaded notification configurations")
}
}()
// clean up old entries hourly
go func() { go func() {
deletionTicker := time.NewTicker(time.Hour) deletionTicker := time.NewTicker(time.Hour)
defer deletionTicker.Stop() defer deletionTicker.Stop()
@ -88,6 +114,13 @@ func main() {
} }
} }
// helper to safely copy slice
func notifMutexCopy(src []Notification) []Notification {
copyDst := make([]Notification, len(src))
copy(copyDst, src)
return copyDst
}
func loadNotifications(db *sql.DB) ([]Notification, error) { func loadNotifications(db *sql.DB) ([]Notification, error) {
rows, err := db.Query( rows, err := db.Query(
`SELECT id, enabled, type, "smtpHost", "smtpPort", "smtpFrom", "smtpUser", "smtpPass", "smtpSecure", "smtpTo", `SELECT id, enabled, type, "smtpHost", "smtpPort", "smtpFrom", "smtpUser", "smtpPass", "smtpSecure", "smtpTo",
@ -166,7 +199,7 @@ func checkAndUpdateStatus(db *sql.DB, client *http.Client, apps []Application) {
} }
resp, err := client.Do(req) resp, err := client.Do(req)
isOnline := err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 || resp.StatusCode == 405 isOnline := (err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300) || resp.StatusCode == 405
// Notify on status change // Notify on status change
if isOnline != app.Online { if isOnline != app.Online {
@ -203,7 +236,11 @@ func checkAndUpdateStatus(db *sql.DB, client *http.Client, apps []Application) {
} }
func sendNotifications(message string) { func sendNotifications(message string) {
for _, n := range notifications { notifMutex.RLock()
notifs := notifMutexCopy(notifications)
notifMutex.RUnlock()
for _, n := range notifs {
switch n.Type { switch n.Type {
case "email": case "email":
if n.SMTPHost.Valid && n.SMTPTo.Valid { if n.SMTPHost.Valid && n.SMTPTo.Valid {