Add timeout and retry to combine_restore kubectl cp#4323
Conversation
kubectl cp streams a tar over the exec channel and can stall intermittently. run_cmd() called subprocess.run() with no timeout, so a stalled copy during a restore hung forever with no output or error (see issue #4322), undermining the disaster-recovery guarantee. - Add an optional timeout to run_cmd() and thread it (plus check_results) through CombineApp.kubectl() and CombineApp.exec(). - Wrap each restore kubectl cp (the database dump and every backend-file item) in kubectl_cp(): bound each attempt with a timeout, retry a few times so a transient stall is recovered, and on repeated failure log the failing item and exit non-zero instead of hanging silently. - Make the timeout and retry count configurable via the kubectl_cp_timeout and kubectl_cp_retries environment variables. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
Warning Review limit reached
Next review available in: 42 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4323 +/- ##
==========================================
- Coverage 75.96% 66.88% -9.08%
==========================================
Files 305 248 -57
Lines 11384 6303 -5081
Branches 1411 794 -617
==========================================
- Hits 8648 4216 -4432
+ Misses 2332 1826 -506
+ Partials 404 261 -143
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The backup script's `kubectl cp` calls (database and backend files) can stall on the exec channel exactly like the restore ones. combine_backup.py runs unattended as a cron job, so a silent hang there quietly stops backups from being produced. Lift the restore-only kubectl_cp() into CombineApp.cp_with_retry() so both scripts share one timeout/retry implementation, and wrap combine_backup.py's two copies with it. The kubectl_cp_timeout and kubectl_cp_retries environment variables now configure both backup and restore. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The loop runs the copy 'retries' total times, so the conventional reading of retries (extra tries after the first) was misleading. Rename the parameter, constant, and env var (kubectl_cp_attempts) before the knob becomes part of the ops interface. Co-Authored-By: Claude Fable 5 <[email protected]>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
maintenance/scripts/combine_app.py (1)
100-140: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueOptional: add a short backoff between retries.
Attempts currently retry immediately after a non-zero return code. A brief sleep between attempts would avoid hammering a transient failure that isn't a timeout. Timeout attempts already have inherent delay, so this mainly helps the fast-fail path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@maintenance/scripts/combine_app.py` around lines 100 - 140, The retry loop in cp_with_retry currently retries immediately after a non-zero kubectl cp failure, which can rapidly hammer a transient fast-fail path. Add a short sleep between failed attempts in cp_with_retry (for the returncode != 0 case, and optionally after timeouts if desired) before the next iteration, using the existing attempt/attempts logging to keep behavior clear. Keep the change localized to cp_with_retry and preserve the final abort behavior after all attempts are exhausted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@maintenance/scripts/combine_app.py`:
- Around line 100-140: The retry loop in cp_with_retry currently retries
immediately after a non-zero kubectl cp failure, which can rapidly hammer a
transient fast-fail path. Add a short sleep between failed attempts in
cp_with_retry (for the returncode != 0 case, and optionally after timeouts if
desired) before the next iteration, using the existing attempt/attempts logging
to keep behavior clear. Keep the change localized to cp_with_retry and preserve
the final abort behavior after all attempts are exhausted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 729281c5-f5f1-4e34-8f56-0b6bcceee7db
📒 Files selected for processing (4)
maintenance/scripts/combine_app.pymaintenance/scripts/combine_backup.pymaintenance/scripts/combine_restore.pymaintenance/scripts/maint_utils.py
Without a delay, a fast-failing kubectl cp exhausts all attempts within the same second, giving a transient failure no time to clear. Co-Authored-By: Claude Fable 5 <[email protected]>
A misconfigured kubectl_cp_attempts of 0 or negative made range(1, attempts+1) empty, so cp_with_retry aborted without ever attempting the copy. Clamp both env-driven knobs so a bad value cannot skip the copy or set a nonsensical timeout. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
jasonleenaylor
left a comment
There was a problem hiding this comment.
@jasonleenaylor reviewed 4 files and all commit messages, and made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on imnasnainaec).
Fixes
Fixes #4322
Problem
maintenance/scripts/combine_restore.pycopies backend files into the backend pod with a per-projectkubectl cploop (and copies the DB dump the same way).kubectl cpstreams a tar over the exec channel and can stall intermittently. Becausemaint_utils.run_cmd()calledsubprocess.run(...)with notimeout, a stalled copy hung forever with no error and no output. Since this is The Combine's disaster-recovery restore, a silent infinite hang is a serious reliability problem.Changes
maint_utils.py: add an optionaltimeoutparameter torun_cmd(), passed through tosubprocess.run(...). When set, a stalled command is killed andsubprocess.TimeoutExpiredis raised for the caller to handle. DefaultNonepreserves existing behavior.combine_app.py: threadtimeoutthroughCombineApp.exec()andCombineApp.kubectl()(also addingcheck_resultstokubectl()).combine_restore.py: wrap eachkubectl cp(the database dump and every backend-file item) in a newkubectl_cp()helper that:kubectl_cp_timeout(default 300s) andkubectl_cp_retries(default 3) environment variables.I kept the existing per-item
kubectl cploop rather than switching to a singletar-pipe (listed as optional in the issue) to keep the change focused; the timeout/retry guard applies either way.Testing
toxequivalents pass locally on the changed files:isort --check,black --check,flake8, andmypy --platform linux maintenance/scripts(strict) — all clean.run_cmd(..., timeout=1)on a 30s sleep raisesTimeoutExpired(no longer hangs) and still returns output for fast commands.kubectl_cp()succeeds without retry on a clean copy (and passescheck_results=False+timeout), recovers from transient stalls (timeout → timeout → success), and exits non-zero after exhausting retries on repeated failure.🤖 Generated with Claude Code
Devin review: https://app.devin.ai/review/sillsdev/TheCombine/pull/4323
This change is
Summary by CodeRabbit
Bug Fixes
Chores