feat: publish standalone worker
Separate worker packaging and service lifecycle from the control plane.
Этот коммит содержится в:
77
internal/notifier/notifier.go
Обычный файл
77
internal/notifier/notifier.go
Обычный файл
@@ -0,0 +1,77 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"rsgit.ru/rsmon/rsmon/app/models"
|
||||
)
|
||||
|
||||
// Tunables for the periodic notifier loops. The values match what the legacy
|
||||
// Start() function used so behavior is unchanged.
|
||||
var (
|
||||
interval = 5 * time.Second
|
||||
expInterval = 2 * time.Hour
|
||||
deletionInterval = 1 * time.Hour
|
||||
)
|
||||
|
||||
// StartScheduler launches the three periodic loops that used to be triggered
|
||||
// by the retired notifier.Start singleton: the notification producer (Run),
|
||||
// the expiry-alert producer (RunExp), and the pending-deletion sweep. Phase 3
|
||||
// of docs/plans/worker-notifier-mvp.md replaces the in-process notifier loop
|
||||
// with the worker-driven task queue; this scheduler keeps the producer
|
||||
// running on its existing cadence so the tasks table stays populated.
|
||||
//
|
||||
// The loops respect ctx.Done() so a graceful shutdown can unwind them, and
|
||||
// each tick is wrapped in recover() so a transient bug in one producer does
|
||||
// not tear down the whole scheduler.
|
||||
//
|
||||
// Reaper: StartTaskReaper lives in app/models/task_reaper.go and runs the
|
||||
// leased->queued recycling on a separate 30s tick.
|
||||
func StartScheduler(ctx context.Context) {
|
||||
go scheduleLoop(ctx, interval, Run, "Run")
|
||||
go scheduleLoop(ctx, expInterval, RunExp, "RunExp")
|
||||
go scheduleLoop(ctx, deletionInterval, RunPendingDeletions, "RunPendingDeletions")
|
||||
}
|
||||
|
||||
// scheduleLoop runs fn immediately and then on every tick. Any panic from
|
||||
// fn is recovered and logged so the loop keeps running.
|
||||
func scheduleLoop(ctx context.Context, tick time.Duration, fn func(), name string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("notifier: scheduler %s goroutine recovered from panic: %v", name, r)
|
||||
}
|
||||
}()
|
||||
|
||||
safeRun(name, fn)
|
||||
ticker := time.NewTicker(tick)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
safeRun(name, fn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// safeRun invokes fn with a panic recovery guard. Each tick is wrapped so a
|
||||
// single bad tick cannot kill the loop. The loop goroutine itself has its own
|
||||
// recover() (see scheduleLoop) for paranoia.
|
||||
func safeRun(name string, fn func()) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("notifier: %s recovered from panic: %v", name, r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}
|
||||
|
||||
// RunPendingDeletions hard-deletes users whose 7-day grace period has elapsed.
|
||||
func RunPendingDeletions() {
|
||||
if _, err := models.ProcessPendingDeletions(); err != nil {
|
||||
log.Printf("notifier: process pending deletions: %v", err)
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user