Basis: feature/fdn-09-verschluesselung-at-rest-schluesselverwaltung (enthaelt FDN-04, wovon FDN-11 laut Ticket abhaengt; fdn-10 wurde NICHT als Basis genommen, da FDN-04 kein Vorfahre von fdn-10 ist). Ergaenzt harte, konfigurierbare Obergrenzen je Verarbeitungsjob (Zeit, Speicher, Eingabegroesse/Seitenzahl) um internal/jobqueue: Ueberschreitung fuehrt zum kontrollierten Abbruch in die Dead-Letter-Queue statt zur Blockade des Workers, inkl. Nutzerbenachrichtigung ueber ein neues Notifier-Interface. - limits.go: Limits-Struct, DefaultLimits, Vorab-Pruefung von size_bytes/pages aus den Job-Metadaten vor Handler-Start - notifier.go: Notifier-Interface + LogNotifier als Standard - queue.go: Queue.FailPermanently fuer sofortigen DLQ-Transfer (kein Backoff/Retry bei Ressourcenlimit-Verstoss) - worker.go: Worker.runWithLimits fuehrt Handler mit Zeit-Timeout und Speicher-Sampling (runtime.MemStats, prozessweite Annaeherung) aus und kehrt bei Ueberschreitung sofort zurueck statt zu blockieren Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
59 lines
1.8 KiB
Go
59 lines
1.8 KiB
Go
package jobqueue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Limits sind harte Obergrenzen je Job (Akzeptanzkriterium 1). Ohne sie kann
|
|
// eine sehr grosse oder praeparierte Datei (viele Seiten, Zip-Bomb-artige
|
|
// Kompression) einen Worker dauerhaft blockieren (Threat-Model-Finding,
|
|
// DMS-Board 2026-09-12).
|
|
type Limits struct {
|
|
MaxDuration time.Duration
|
|
MaxMemoryBytes uint64
|
|
MaxInputBytes int64
|
|
MaxPages int
|
|
}
|
|
|
|
// DefaultLimits gelten, wenn fuer einen Jobtyp keine eigenen Limits
|
|
// konfiguriert sind (WithJobTypeLimits).
|
|
var DefaultLimits = Limits{
|
|
MaxDuration: 5 * time.Minute,
|
|
MaxMemoryBytes: 512 * 1024 * 1024,
|
|
MaxInputBytes: 200 * 1024 * 1024,
|
|
MaxPages: 2000,
|
|
}
|
|
|
|
// jobMetadata sind optionale, vom Aufrufer bei Enqueue mitgegebene Kennzahlen
|
|
// zur Eingabegroesse — so ist die Groessen-/Seitenpruefung VOR dem Start des
|
|
// Handlers moeglich, ohne die Datei erst zu laden.
|
|
type jobMetadata struct {
|
|
SizeBytes int64 `json:"size_bytes"`
|
|
Pages int `json:"pages"`
|
|
}
|
|
|
|
// LimitExceededError kennzeichnet einen wegen Ressourcenlimit kontrolliert
|
|
// abgebrochenen Job (statt eines transienten Verarbeitungsfehlers).
|
|
type LimitExceededError struct {
|
|
Reason string
|
|
}
|
|
|
|
func (e *LimitExceededError) Error() string {
|
|
return fmt.Sprintf("ressourcenlimit ueberschritten: %s", e.Reason)
|
|
}
|
|
|
|
func checkInputLimits(payload json.RawMessage, limits Limits) error {
|
|
var meta jobMetadata
|
|
_ = json.Unmarshal(payload, &meta)
|
|
|
|
if limits.MaxInputBytes > 0 && meta.SizeBytes > limits.MaxInputBytes {
|
|
return &LimitExceededError{Reason: fmt.Sprintf("eingabegroesse %d bytes > obergrenze %d bytes", meta.SizeBytes, limits.MaxInputBytes)}
|
|
}
|
|
if limits.MaxPages > 0 && meta.Pages > limits.MaxPages {
|
|
return &LimitExceededError{Reason: fmt.Sprintf("seitenzahl %d > obergrenze %d", meta.Pages, limits.MaxPages)}
|
|
}
|
|
return nil
|
|
}
|