Documentation
¶
Overview ¶
Package scheduler owns the daily deep-analysis tick and its sub-workers (recent-history replay + recently-added sweep).
Responsibilities:
- Schedule a daily deep-analysis run at an absolute HH:MM boundary (clock-jump-safe: DST, NTP step, container start mid-minute all resolve to the next HH:MM boundary).
- Fan out per-item work across a bounded worker pool (runtime-algorithms-p1) with a circuit breaker that aborts the pass after a threshold of consecutive per-item failures.
- Persist the last-run marker through api.Cache so a cold restart does not double-run the analysis.
Inviolate contracts preserved (see refactor-agent-guide.md):
- WARN slog keys ("scheduler: aborting history processing after consecutive failures", "scheduler: failed to fetch history", "scheduler: failed to fetch sections", "scheduler: deep analysis already in progress, skipping") byte-for-byte identical (inviolate item 5).
- INFO slog keys ("scheduler enabled", "scheduled deep analysis starting", "running initial deep analysis", "deep analysis completed", "scheduler: processing recently added episode", "scheduler stopped") identical.
- /config/cache.json schema unchanged — LastSchedulerRun reads and writes go through api.Cache and are tagged by the concrete internal/cache package (inviolate item 7).
Consumer note: scheduler depends on api.PlexReader, api.Cache, api.UserLookup, and a Syncer interface satisfied by *sync.Syncer (declared locally to keep this package testable without importing internal/sync, which would create a visible dependency direction only used in one place). In practice main.go wires the concrete *sync.Syncer through.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CacheSaver ¶
type CacheSaver func() error
CacheSaver is the narrow persistence sink the scheduler needs: a single "flush the cache to disk" call invoked at the end of each deep-analysis tick. Deliberately separate from api.Cache (which deliberately excludes file-system concerns) so the scheduler can trigger a disk flush without the api.Cache consumers needing to know about the persistence path. *cache.Cache satisfies this via a trivial closure in the composition root.
type Config ¶
type Config struct {
Ignore api.IgnoreChecker // library skip rules; nil means "never skip"
ScheduleTime string // "HH:MM"
Enable bool // scheduler on/off
LanguageProfiles bool // reserved for future use
}
Config captures the subset of application configuration the Scheduler actually reads. Decoupling from the full main.config keeps the package boundary clean and lets tests construct a Scheduler without mimicking the app's full env-var surface.
type Scheduler ¶
type Scheduler struct {
// contains filtered or unexported fields
}
Scheduler owns the deep-analysis tick and its workers. Safe for concurrent Run calls, but the intended shape is a single Run goroutine per process.
runtime-concurrency-p1: overlapping deep-analysis triggers (e.g. the initial catch-up firing close to the first scheduled HH:MM tick) collapse onto a single in-flight run via singleflight.Group. The runner goroutine that loses the dedup race still logs a WARN with the "scheduler: deep analysis already in progress, skipping" key so Loki alerts keyed on that string continue to fire (inviolate item 5).
func New ¶
func New(cfg Config, reader api.PlexReader, c api.Cache, lookup api.UserLookup, userClient api.UserClientFunc, s Syncer, saveCache CacheSaver) *Scheduler
New constructs a Scheduler with the given collaborators. saveCache may be nil in tests that don't exercise the disk-flush path.
type Syncer ¶
type Syncer interface {
ChangeTracksForEpisode(ctx context.Context, userClient api.PlexReadWriter, userID string, reference *streams.Episode, trigger string)
ProcessNewOrUpdatedEpisodeAllUsers(ctx context.Context, episode *streams.Episode, trigger string)
}
Syncer is the narrow interface the scheduler needs from the sync package: a per-user track-apply call plus the multi-user fan-out. The ignore-library/ignore-label skip checks live on api.IgnoreChecker (injected via Config.Ignore) rather than on the Syncer, so overlapping event/scheduler paths share one decision point instead of the three duplicated implementations that existed pre-extraction. *sync.Syncer satisfies this. Declared here (rather than imported) to keep scheduler independent of internal/sync for test ergonomics.