Files
talk/internal/queue/rate.go
T
m3ta-chiron e492baddac feat(queue): serialisierte Ansage-Queue mit Prioritäten, now-Sprung, Mute-Store und Rate-Limit-Struktur
- Worker-Loop: eine Ansage gleichzeitig; now > high > normal > low, FIFO je Level
- Cancel bricht die laufende Ansage ab (ctx) und leert die Queue (Zahl als Rückgabe)
- Close verwirft wartende Items, bricht die laufende Ansage ab und wartet auf den Worker
- MuteStore: persistenter Mute-State als Datei mute im Config-Dir (atomar geschrieben)
- RateLimiter: Struktur + Semantik für Task 05, bewusst noch nicht angebunden
  (Task-04-Default unbegrenzt, dokumentierte Seam in rate.go)
2026-10-18 17:10:26 +02:00

58 lines
1.7 KiB
Go

package queue
import (
"sync"
"time"
)
// RateLimiter ist die Struktur für das Daemon-Gesetz gegen Ansage-Spam
// (Spec: Default ca. 1 Ansage / 30 s, konfigurierbar).
//
// SEAM FÜR TASK 05: Struktur und Semantik stehen und sind getestet, sind
// aber noch NICHT an den Worker-Loop angebunden — Task 04 erzeugt keinen
// Limiter (unbegrenzt, so verlangt es der Task-04-Brief: „Default unendlich
// in T04-Tests, dokumentiert"). Task 05 verdrahtet den Limiter (config
// queue.rate_limit) in den Loop und ergänzt die prioritätsbewusste
// Entscheidung: low verwerfen, normal parken, high konfigurierbar verstoßen
// (queue.allow_high_burst) — dafür ist Next() bereits Teil der Struktur.
type RateLimiter struct {
// Interval ist der Mindestabstand zwischen zwei Ansage-Starts.
// 0 oder negativ = unbegrenzt (Task-04-Default).
Interval time.Duration
mu sync.Mutex
last time.Time // Start der zuletzt per Reserve gebuchten Ansage
}
// Allow meldet, ob sofort eine Ansage starten dürfte.
func (l *RateLimiter) Allow() bool {
if l == nil || l.Interval <= 0 {
return true
}
l.mu.Lock()
defer l.mu.Unlock()
return time.Since(l.last) >= l.Interval
}
// Reserve bucht einen Ansage-Slot (direkt nach Allow aufrufen).
func (l *RateLimiter) Reserve() {
if l == nil || l.Interval <= 0 {
return
}
l.mu.Lock()
defer l.mu.Unlock()
l.last = time.Now()
}
// Next liefert, wann der nächste Ansage-Slot frei wird (Nullzeit, wenn
// Allow sofort true liefern würde) — Basis für das Parken wartender Items
// in Task 05.
func (l *RateLimiter) Next() time.Time {
if l == nil || l.Interval <= 0 {
return time.Time{}
}
l.mu.Lock()
defer l.mu.Unlock()
return l.last.Add(l.Interval)
}