feat(PROJ-56): OCR-Pausenfenster per SIGHUP-Reload statt Restart
paused_hours konnte bisher nur über einen vollen Prozess-Restart geändert werden, was SMTP/IMAP/API unnötig unterbricht. Worker.pausedHours ist jetzt ein atomic.Pointer mit SetPausedHours(); SIGHUP liest config.yml neu und aktualisiert nur die OCR-Pausenzeit im laufenden Prozess. Neue deploy/cron.d/archivmail-ocr-pause(.sh) lässt Admins die Pausenzeiten direkt in der Cron-Datei pflegen und löst per systemctl reload statt restart aus. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
be48a99af9
commit
dba9939880
+26
-12
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"archivmail/internal/index"
|
||||
@@ -38,7 +39,11 @@ type Worker struct {
|
||||
// hour falls inside it, workers stop consuming the queue (jobs stay buffered
|
||||
// in the channel / as ocr_status='pending' in the DB) until it reopens.
|
||||
// nil = never pause (legacy behaviour).
|
||||
pausedHours *[2]int
|
||||
//
|
||||
// Stored as atomic.Pointer so SetPausedHours can be called concurrently
|
||||
// from a signal handler (SIGHUP reload) while run() goroutines read it,
|
||||
// without requiring a process restart to change the window (PROJ-56b).
|
||||
pausedHours atomic.Pointer[[2]int]
|
||||
}
|
||||
|
||||
// pauseCheckInterval is how often a paused worker re-checks whether the pause
|
||||
@@ -73,26 +78,35 @@ func NewWorker(store *storage.Store, idxMgr index.TenantIndexer, opts Options) *
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = slog.Default()
|
||||
}
|
||||
return &Worker{
|
||||
store: store,
|
||||
idxMgr: idxMgr,
|
||||
logger: opts.Logger,
|
||||
queue: make(chan Job, opts.QueueSize),
|
||||
done: make(chan struct{}),
|
||||
workers: opts.Workers,
|
||||
langs: opts.Langs,
|
||||
pausedHours: opts.PausedHours,
|
||||
w := &Worker{
|
||||
store: store,
|
||||
idxMgr: idxMgr,
|
||||
logger: opts.Logger,
|
||||
queue: make(chan Job, opts.QueueSize),
|
||||
done: make(chan struct{}),
|
||||
workers: opts.Workers,
|
||||
langs: opts.Langs,
|
||||
}
|
||||
w.pausedHours.Store(opts.PausedHours)
|
||||
return w
|
||||
}
|
||||
|
||||
// SetPausedHours updates the pause window at runtime, without requiring a
|
||||
// worker/process restart. Pass nil to disable pausing. Safe to call
|
||||
// concurrently with running workers (e.g. from a SIGHUP reload handler).
|
||||
func (w *Worker) SetPausedHours(hours *[2]int) {
|
||||
w.pausedHours.Store(hours)
|
||||
}
|
||||
|
||||
// isPaused reports whether the OCR worker should currently hold off processing
|
||||
// because the local time falls inside the configured pause window.
|
||||
// Supports wrap-around windows where start > end (e.g. [22, 6]).
|
||||
func (w *Worker) isPaused(now time.Time) bool {
|
||||
if w.pausedHours == nil {
|
||||
hours := w.pausedHours.Load()
|
||||
if hours == nil {
|
||||
return false
|
||||
}
|
||||
start, end := w.pausedHours[0], w.pausedHours[1]
|
||||
start, end := hours[0], hours[1]
|
||||
if start == end {
|
||||
// Degenerate / no-op window — never pause.
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user