Track background jobs; wire session + rate-limit cleanup (M-12, M-13) #112
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
qwc-open/asiakirjat!112
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/background-jobs-lifecycle"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Workstream item #17. Two related audit findings.
M-13 — fire-and-forget goroutines
Three sites started goroutines the process abandoned on shutdown:
upload.go+api.go:searchIndex.IndexVersion(post-upload reindex)upload.go+api.go:enforceRetentionPolicy(context.Background())search.go:handleAdminReindex's worker goroutineenforceRetentionPolicywas called withcontext.Background(), so even cancelling the retention worker's context didn't stop a per-upload retention scan.New
internal/handler/jobs.goadds:jobsstruct holding a cancellable context +sync.WaitGrouph.runJob(fn)registersfnunder the WaitGroup;fnreceives the shared cancellable contexth.StopBackgroundJobs()cancels and waitsAll three fire-and-forget sites migrated to
runJob.enforceRetentionPolicynow runs under the cancellable jobs context — a long retention scan exits promptly on shutdown.M-12 — unused cleanup paths
SessionStore.DeleteExpiredandRateLimiter.Cleanupexisted but were only called from tests. Sessions accumulated indefinitely; the rate-limit map grew linearly with distinct client IPs since boot.New
StartBackgroundWorkersmethod spawns three loops underrunJob:StartRetentionWorker(existing, now wrapped)sessionCleanupLoop—DeleteExpiredevery hour, immediate first runrateLimitCleanupLoop—Cleanupevery 10 minutesAll three exit on jobs context cancel.
main.go
retentionCtx/retentionCancel+ barego h.StartRetentionWorker(...)with oneh.StartBackgroundWorkers().server.Shutdownreturns, callsh.StopBackgroundJobs()so in-flight indexing / retention sweeps / cleanup ticks drain before the process exits.Tests
internal/handler/jobs_test.go:TestRunJobAwaitsCompletion—StopBackgroundJobsblocks until the job exits.TestRunJobCancelsContextOnStop— jobs that watchctx.Doneexit promptly when stop fires.TestStopBackgroundJobsIsSafeWhenIdle— idle Stop returns immediately.All verified under
-race.Test plan
go test ./...and-racepasssessionstable — expired rows are gone (visible after the first hour). Rate-limit map growth — harder to observe directly, but Login attempts from many distinct IPs should eventually plateau.🤖 Generated with Claude Code
Audit findings M-12 and M-13: background goroutines and unused cleanup paths were both lifecycle gaps that this PR closes together. M-13 — fire-and-forget goroutines: Three sites started goroutines that the process abandoned on shutdown: - upload.go and api.go: searchIndex.IndexVersion (post-upload reindex) - upload.go and api.go: enforceRetentionPolicy(context.Background()) - search.go: handleAdminReindex's worker goroutine enforceRetentionPolicy explicitly used context.Background, so even cancelling the retention worker's context didn't stop a retention scan spawned from an upload mid-flight. New internal/handler/jobs.go adds: - jobs struct (ctx + cancel + WaitGroup) - Handler.runJob(fn) registers fn under the WaitGroup; fn receives the cancellable jobs context - Handler.StopBackgroundJobs() cancels and waits Every fire-and-forget goroutine migrated to runJob. enforceRetentionPolicy now gets the cancellable jobs context so a long retention scan exits promptly on shutdown. M-12 — unused cleanup paths: SessionStore.DeleteExpired and RateLimiter.Cleanup existed but were only called from tests. Sessions accumulated indefinitely, the rate-limit map grew linearly with distinct client IPs since boot. New StartBackgroundWorkers method spawns three loops under runJob: - StartRetentionWorker (already existed, just re-wrapped) - sessionCleanupLoop: DeleteExpired every hour, immediate first run - rateLimitCleanupLoop: Cleanup every 10 minutes All three exit on jobs context cancel. main.go: - Replaces the bespoke retentionCtx/retentionCancel + bare `go h.StartRetentionWorker(...)` with one h.StartBackgroundWorkers(). - After server.Shutdown returns, calls h.StopBackgroundJobs() so in-flight indexing / retention sweeps / cleanup ticks drain before the process exits. Three regression tests (jobs_test.go) cover runJob's WaitGroup behavior, context cancellation propagation, and idle-Stop being a no-op. All verified under -race. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>