fix: retry backend init with backoff so indexer connection failures self-heal - #61
Conversation
This comment has been minimized.
This comment has been minimized.
9ac8c11 to
b13c585
Compare
This comment has been minimized.
This comment has been minimized.
b13c585 to
c8fee3f
Compare
This comment has been minimized.
This comment has been minimized.
c8fee3f to
a807030
Compare
This comment has been minimized.
This comment has been minimized.
a807030 to
eae3a86
Compare
This comment has been minimized.
This comment has been minimized.
eae3a86 to
d0b407a
Compare
|
Thanks @kody. Addressed the stale-onFailure finding in The terminal-error and retry-budget-exhausted branches in Added regression test Note: this same push ( @kody start-review |
This comment has been minimized.
This comment has been minimized.
d0b407a to
200842d
Compare
This comment has been minimized.
This comment has been minimized.
200842d to
24b22e1
Compare
This comment has been minimized.
This comment has been minimized.
|
The new code (lines 593-655 in the diff) does not introduce new GORM queries, but the retry logic around the database operations (OpenDatabase, initCore) could benefit from explicit context timeout handling. However, the specific rule requires GORM queries needing Kody rule violation: Disallow GORM queries without timeout |
There was a problem hiding this comment.
Found critical issues please review the requested changes
- failInitIfStarting fires the destructive onFailure callback after releasing the lock, leaving a TOCTOU window where a concurrent Restart can bring the backend to Running and get its access keys wiped.
- failInitIfStarting fires the destructive onboarding onFailure outside the lock after releasing it, allowing a concurrent successful Restart to be wiped by a stale failure callback.
2e5c206 to
eced05d
Compare
This comment has been minimized.
This comment has been minimized.
eced05d to
56e72c7
Compare
This comment has been minimized.
This comment has been minimized.
56e72c7 to
b55aac5
Compare
This comment has been minimized.
This comment has been minimized.
b55aac5 to
a12b2df
Compare
This comment has been minimized.
This comment has been minimized.
|
The retry loop in runAsyncInit passes initCtx (a context.WithCancel from main.go with no timeout) through attemptInit/initCore to factory.Init, so GORM database operations lack a timeout and can run indefinitely. Derive a per-attempt timed context with context.WithTimeout or context.WithDeadline before calling attemptInit/factory.Init, since GORM queries require a timeout unless inside a db.RetryableComponentLock call. Kody rule violation: Disallow GORM queries without timeout |
…elf-heal Retry async backend init with exponential backoff (5s base, 5min cap) so a transient indexer connection failure self-heals without a manual restart. Bound the loop with maxInitRetries (12) so persistent non-terminal failures are surfaced via onFailure instead of retrying forever, and so Cleanup's initWg.Wait() always returns on shutdown. Hold restartMu only around each init attempt (not across the backoff sleep) to avoid starving concurrent Restart/Init. In InitAfterOnboardingAsync, close the transferred store and reopen it from disk on every retry instead of reusing a closed handle. Cancel the async-init context on shutdown in cmd/s3-server.
a12b2df to
edd699c
Compare
Kody Review CompleteGreat news! 🎉 Keep up the excellent work! 🚀 Kody Guide: Usage and ConfigurationInteracting with Kody
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
New GORM database operations in internal/backend/manager.go — validateAccessKeysExist and ListAccessKeys within the syncInit/attemptInit retry paths — lack .WithContext(ctx) timeouts, allowing queries to run indefinitely when the store reopens. Wrap all new GORM calls with .WithContext(ctx) to enforce timeout boundaries, except inside db.RetryableComponentLock. Kody rule violation: Disallow GORM queries without timeout |
Docker image for testingPull the image for this PR: docker pull ghcr.io/lumeweb/s3-server:sha-ab005c8Or use in docker-compose: services:
s3-server:
image: ghcr.io/lumeweb/s3-server:sha-ab005c8
|
Summary
If the indexer connection fails during startup, the backend initialization (siastorage SDK
CheckAppAuth/initSDK/OptimalDataSizeinsidefactory.Init) failed permanently, setting backend status toerrorand never retrying — so the operator had to restart the server to recover. This PR makes async backend init self-heal with exponential backoff.Changes
internal/backend/manager.goisTerminalInitErrorclassifies config/precondition errors (no app key set,no access keys,failed to open database) as non-retryable — retrying can't fix them.attemptInitruns one init attempt and closes the store on failure (no handle leak) without recordingfailInit; the loop decides retry vs. fail.onFailure, so a clean shutdown doesn't reset onboarding state.retryDelayis an injectableManagerfield (defaults toretryBackoff) for fast, deterministic tests.internal/backend/manager_test.goSelfHeals(2 failures then success via backoff) andRetryBackoffBoundstests.During retries, status stays
starting(surfaced via SSE), so the dashboard shows startup in progress rather than a terminal error.Testing
go build ./...✅go vet ./...✅gofmt -lclean on changed files ✅go test ./internal/backend/...✅ (all pass, incl. new tests)Summary
This pull request implements automatic retry with exponential backoff for backend initialization failures, allowing the system to self-heal from transient indexer/connection failures at startup without requiring manual intervention.
Key Changes
Retry Mechanism with Backoff
Error Classification
Lifecycle Improvements
attemptInitfunction that handles store lifecycle and cleanup consistentlyTest Coverage