Files
patrickandClaude Sonnet 5 2a084cbdc8 FDN-11: ressourcenlimits-f-r-asynchrone-verarbeitungsjobs
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
2026-09-17 12:30:17 +02:00

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
}