fix(backups): add failure cooldown and export throttling#1214
fix(backups): add failure cooldown and export throttling#1214
Conversation
WalkthroughAdded configurable FailureCooldown and ExportThrottle; missed-backup detection now short-circuits for pending/running runs or recent cancellations; export operations are throttled via a ticker and waitForExportThrottle gating; Service.DataDir() accessor added; expanded per-run progress updates. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Scheduler
participant Service
participant Throttle as ExportThrottleTicker
participant Exporter
participant Progress as ProgressTracker
Scheduler->>Service: isBackupMissed()
Service-->>Scheduler: return missed? (short-circuit if pending/running or within FailureCooldown)
opt backup needed
Service->>Progress: init run progress
loop per torrent/blob
Service->>Throttle: waitForExportThrottle(ctx)
Throttle-->>Service: tick / allow
Service->>Exporter: ExportTorrent(blob)
Exporter-->>Service: result (ok/error)
Service->>Progress: update per-torrent progress
end
Service->>Progress: finalize run
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @internal/backups/service.go:
- Around line 315-335: Remove the redundant nil check guarding the cooldown
comparison: after assigning ref := r.CompletedAt and falling back to ref =
&r.RequestedAt, ref is always non-nil, so replace the conditional "if ref != nil
&& now.Before(ref.Add(s.cfg.FailureCooldown))" with just "if
now.Before(ref.Add(s.cfg.FailureCooldown))" inside the loop that iterates over
runs (the block referencing r.CompletedAt, r.RequestedAt, s.cfg.FailureCooldown
and now.Before).
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/backups/service.go
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
📚 Learning: 2025-11-25T22:46:03.762Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 632
File: internal/backups/service.go:1401-1404
Timestamp: 2025-11-25T22:46:03.762Z
Learning: In qui's backup service (internal/backups/service.go), background torrent downloads initiated during manifest import intentionally use a fire-and-forget pattern with the shared service context (s.ctx). Per-run cancellation is not needed, as orphaned downloads completing after run deletion are considered harmless and acceptable. This design prioritizes simplicity over per-run lifecycle management for background downloads.
Applied to files:
internal/backups/service.go
📚 Learning: 2025-12-28T18:44:10.496Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 876
File: internal/logstream/hub_test.go:188-192
Timestamp: 2025-12-28T18:44:10.496Z
Learning: In Go 1.25 (Aug 2025), use wg.Go(func()) to spawn a goroutine and automate the Add/Done lifecycle. Replace manual patterns like wg.Add(1); go func(){ defer wg.Done(); ... }() with wg.Go(func(){ ... }). Ensure the codebase builds with Go 1.25+ and apply this in relevant Go files (e.g., internal/logstream/hub_test.go). If targeting older Go versions, maintain the existing pattern.
Applied to files:
internal/backups/service.go
🧬 Code graph analysis (1)
internal/backups/service.go (1)
internal/models/backups.go (4)
BackupRunStatusPending(59-59)BackupRunStatusRunning(60-60)BackupRunStatusFailed(62-62)BackupRunStatusCanceled(63-63)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run tests
🔇 Additional comments (5)
internal/backups/service.go (5)
40-44: LGTM!The new configuration fields are well-typed and appropriately exposed for external configuration.
118-123: LGTM!The default values (10m cooldown, 200ms throttle) follow the existing pattern and are reasonable starting points.
557-565: LGTM!Proper ticker lifecycle management with conditional creation and deferred cleanup. The nil check before stopping prevents panics.
662-668: Throttle correctly paces exports and respects cancellation.The throttle is applied before each
ExportTorrentcall, preventing rapid-fire requests to qBittorrent. Note that this introduces a delay before the first export as well; if you want to avoid the initial delay, move the throttle wait to after the export. However, for preventing crashes, the current approach is safer.
1664-1667: LGTM!Clean accessor method for the data directory configuration.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@internal/backups/service.go`:
- Around line 315-335: The switch on r.Status in the runs loop is missing an
explicit case for models.BackupRunStatusSuccess which fails exhaustive-switch
checks; add a case for models.BackupRunStatusSuccess (or a default) inside that
switch in the short-circuit logic (the block that iterates over runs and checks
Pending/Running/Failed/Canceled) and make it a no-op or explicitly handle it
(e.g., `case models.BackupRunStatusSuccess: // fallthrough/no-op`) so the
exhaustive linter check passes while preserving the existing behavior that
non-pending/running/failed statuses continue past the cooldown logic.
Motivation
Description
FailureCooldownandExportThrottleto the backupConfigand set sensible defaults inNewService(10mand200msrespectively).isBackupMissedto short-circuit when the latest run ispendingorrunning, and to respect a failure cooldown forfailedandcanceledruns usingCompletedAtorRequestedAtas the reference time.executeBackupby creating atime.Tickerbased onExportThrottleand selecting on its channel before eachsyncManager.ExportTorrentcall, and ensured the ticker is stopped withdefer.BackupRunStatusCanceledin the cooldown logic and preserved existing logic that finds the most recent successful run as the schedule reference.Testing
Codex Task
Summary by CodeRabbit
New Features
Bug Fixes
Tests