fix(#6435): serialize ImportProfile with flock and retry EnsureProvider - #6437
Conversation
|
🤖 Finished Review · ✅ Success · Started 3:51 AM UTC · Completed 4:06 AM UTC Commit: |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
ReviewFindingsMedium
Low
Previous runReviewFindingsLow
Previous run (2)ReviewFindingsLow
Previous run (3)ReviewFindingsLow
Info
Previous run (4)ReviewFindingsMedium
Low
|
|
/fs-fix Address the review findings. Additionally: (1) strengthen TestImportProfile_FlockSerializesConcurrent — the current fake openshell always succeeds, so the test passes even without the flock; use a fake that detects concurrent execution (e.g. marker file that fails if already present at entry) to prove serialization is load-bearing; (2) add a comment to isUnsupportedProviderErr noting it matches openshell CLI output and is fragile if wording changes; (3) extract a shared profileTempPath(id, ext) helper to deduplicate profileFileCachePath and profileFileLockPath. Skip the loop style finding (retry-pattern-consistency) — both forms are correct. |
|
🤖 Finished Fix · ✅ Success · Started 7:54 AM UTC · Completed 8:02 AM UTC Commit: |
- Extract shared profileTempPath(id, ext) helper to deduplicate the hashing logic in profileFileCachePath and profileFileLockPath - Add fragility comment to isUnsupportedProviderErr noting it matches openshell CLI stderr output and will break if wording changes - Strengthen TestImportProfile_FlockSerializesConcurrent: replace the always-succeeding fake with one that uses a marker file to detect concurrent execution, proving the flock is load-bearing Addresses review feedback on #6437
🔧 Fix agent — iteration 1 (human-triggered)Addressed 3 of 4 review findings: extracted profileTempPath helper to deduplicate path functions, added fragility comment to isUnsupportedProviderErr, and strengthened the flock serialization test with a concurrent-execution-detecting fake. Skipped retry-pattern-consistency per human instruction. Fixed (3):
Disagreed (1):
Tests: passedNext steps:
|
|
🤖 Review · Commit: |
|
/fs-fix Address the remaining review findings from the fullsend-ai-review bot comment: (1) wrap the retry exhaustion return at line 322 with attempt-count context — use fmt.Errorf("provider create %q failed after %d attempts: %w", name, providerRetries, lastErr) to match the CreateWithRetry pattern; (2) replace time.Sleep(providerRetryBackoff) at line 319 with RetrySleepFn(providerRetryBackoff) to match the existing testable retry pattern in this file; (3) make the retry sleep context-aware — use select on ctx.Done() with time.After instead of bare sleep so cancellation is respected. |
|
🤖 Finished Fix · ✅ Success · Started 8:12 AM UTC · Completed 8:18 AM UTC Commit: |
🔧 Fix agent — iteration 2 (human-triggered)Addressed all 3 review findings: (1) wrapped retry-exhaustion return with attempt-count context matching CreateWithRetry pattern, (2+3) replaced bare time.Sleep with context-aware select on ctx.Done()/time.After — this single fix addresses both the testability and context-awareness concerns. Added new test covering context cancellation during retry sleep. Fixed (3):
Tests: passedNext steps:
|
…ovider Replace bare time.Sleep with select on ctx.Done()/time.After so context cancellation is respected during provider retry backoff. Wrap the retry-exhaustion return with attempt-count context to match the CreateWithRetry error pattern. Add test covering cancellation during retry sleep. Addresses review feedback on #6437
|
🤖 Review · Commit: |
- Extract shared profileTempPath(id, ext) helper to deduplicate the hashing logic in profileFileCachePath and profileFileLockPath - Add fragility comment to isUnsupportedProviderErr noting it matches openshell CLI stderr output and will break if wording changes - Strengthen TestImportProfile_FlockSerializesConcurrent: replace the always-succeeding fake with one that uses a marker file to detect concurrent execution, proving the flock is load-bearing Addresses review feedback on #6437
166dc98 to
2148076
Compare
…ovider Replace bare time.Sleep with select on ctx.Done()/time.After so context cancellation is respected during provider retry backoff. Wrap the retry-exhaustion return with attempt-count context to match the CreateWithRetry error pattern. Add test covering cancellation during retry sleep. Addresses review feedback on #6437
|
🤖 Finished Review · ✅ Success · Started 8:33 AM UTC · Completed 8:47 AM UTC Commit: |
|
/fs-fix Fix the double-nested error prefix in internal/sandbox/sandbox.go:326 — the retry-exhaustion fmt.Errorf wraps lastErr which already contains "provider create" from tryCreateProvider; restructure the wrapping to avoid repeating the prefix. Replace assert.Equal(t, N, len(entries)) with assert.Len(t, entries, N, ...) in the three new test functions in internal/sandbox/sandbox_test.go to match the established pattern. |
|
🤖 Finished Fix · ✅ Success · Started 8:53 AM UTC · Completed 8:59 AM UTC Commit: |
The #6421 hash-based cache reduced the ImportProfile race from 4/4 to 1/4 failures but did not eliminate it: when all parallel fullsend run processes start simultaneously (no cache file yet), multiple processes enter the non-atomic delete+reimport path. A concurrent EnsureProvider call that lands between delete and reimport finds no profile and fails with "unsupported provider type or profile". Two layered fixes: 1. flock in ImportProfile: the delete+reimport critical section is now protected by a cross-process file lock keyed by profile id. Only one process mutates the profile at a time. Processes that block on the lock re-check the cache after acquiring it (double-check pattern) and skip the import entirely if the winner already wrote the cache. 2. Retry in EnsureProvider: the specific "unsupported provider type or profile" error is treated as transient and retried up to 3 times with 500ms backoff. This provides defense-in-depth for any remaining timing edge cases. Non-transient errors are returned immediately without retry. Note: golangci-lint could not run in sandbox (not installed). go vet passed. Pre-commit could not run (network access blocked in sandbox); the post-script runs it authoritatively. Closes #6435
- Extract shared profileTempPath(id, ext) helper to deduplicate the hashing logic in profileFileCachePath and profileFileLockPath - Add fragility comment to isUnsupportedProviderErr noting it matches openshell CLI stderr output and will break if wording changes - Strengthen TestImportProfile_FlockSerializesConcurrent: replace the always-succeeding fake with one that uses a marker file to detect concurrent execution, proving the flock is load-bearing Addresses review feedback on #6437
…ovider Replace bare time.Sleep with select on ctx.Done()/time.After so context cancellation is respected during provider retry backoff. Wrap the retry-exhaustion return with attempt-count context to match the CreateWithRetry error pattern. Add test covering cancellation during retry sleep. Addresses review feedback on #6437
Remove the redundant "provider create" prefix from the retry-exhaustion error in EnsureProvider — lastErr from tryCreateProvider already carries that prefix, producing a double-nested "provider create … provider create" message. Replace assert.Equal(t, N, len(entries)) with assert.Len(t, entries, N, ...) in three test functions to match the established pattern. Addresses review feedback on #6437
fefc8cb to
0d8f197
Compare
|
🤖 Finished Review · ✅ Success · Started 9:26 AM UTC · Completed 9:41 AM UTC Commit: |
|
/fs-fix In internal/sandbox/sandbox.go around line 199, syscall.Flock(LOCK_EX) blocks without respecting context cancellation. Either switch to LOCK_NB in a poll loop that checks ctx.Done() between attempts, or add a comment documenting that blocking is bounded by the lock holder's execution time (which is short — just delete + import). |
|
🤖 Finished Fix · ✅ Success · Started 9:54 AM UTC · Completed 10:01 AM UTC Commit: |
Add a comment above the syscall.Flock(LOCK_EX) call documenting that blocking does not respect context cancellation, and explaining why this is acceptable: the lock holder's critical section is bounded by two providerTimeout (30 s) operations (delete + reimport), so worst-case wait is ~60 s. The comment also notes the LOCK_NB poll-loop alternative if stricter context-awareness is needed in the future. Addresses human review feedback on #6437
🔧 Fix agent — iteration 4 (human-triggered)Addressed 1 finding: added a comment documenting that syscall.Flock(LOCK_EX) blocking is bounded by the lock holder's execution time (~60 s worst-case) and does not need ctx cancellation. All tests pass. Fixed (1):
Tests: passed
Decision points
Next steps:
|
|
🤖 Finished Review · ✅ Success · Started 10:03 AM UTC · Completed 10:17 AM UTC Commit: |
|
🤖 Finished Retro · ✅ Success · Started 10:32 AM UTC · Completed 10:45 AM UTC Commit: |
Retro: PR #6437 — serialize ImportProfile with flock and retry EnsureProviderTimeline: Issue #6435 opened at 03:31 UTC → triage completed in 5 min → code agent created PR #6437 at 03:49 UTC → review agent posted first review at ~04:06 UTC → human reviewer (maruiz93) arrived at 07:52 UTC and drove 4 Code agent first-attempt quality: The code agent implemented the correct architecture (flock + retry, defense-in-depth) matching the triage agent's recommended approach. The production code was functionally correct and would have resolved the race condition as-shipped. However, 4 fix iterations (+91 lines changed) were needed for Go best-practice polish — a ~55% rework ratio on the initial +201 lines. Review agent performance: The review agent caught 8 findings across 4 review passes (double-nested error prefix, Novel finding — context-aware blocking: The most impactful human finding was that Evidence for existing issues:
Proposals filed
|
ImportProfiles (batch) performed delete+reimport without flock protection, causing races under parallel execution. When multiple processes saw a hash cache miss simultaneously, each deleted and reimported the same profiles, and concurrent EnsureProvider calls hit "unsupported provider type or profile" during the delete window. Add the same flock serialization pattern that ImportProfile (singular) already uses: acquire an exclusive file lock keyed by directory path, double-check the hash cache after acquisition, then perform delete+reimport inside the critical section. This is the third instance of this race class, after #6421 and #6437. Add profileDirLockPath helper for directory-keyed lock paths and a concurrent-safety test using the same marker-file technique as TestImportProfile_FlockSerializesConcurrent. Note: pre-commit could not run (sandbox network policy blocked git fetch). go vet passed. golangci-lint was not available in the sandbox. Closes #6448
Summary
Fixes the remaining profile race condition in
ImportProfilethat persisted after #6421. When multiple parallelfullsend runprocesses share an openshell gateway and all start before any cache file exists, they all enter the non-atomic delete+reimport path, causing intermittentunsupported provider type or profilefailures inEnsureProvider.Related Issue
Closes #6435
Changes
syscall.Flock) keyed by profile ID. Only one process mutates the profile at a time. Processes that block on the lock re-check the cache after acquiring it (double-check pattern) and skip the import if the winner already wrote the cache.unsupported provider type or profileerror is treated as transient and retried up to 3 times with 500ms backoff. Non-transient errors are returned immediately without retry. This provides defense-in-depth.profileFileLockPath(lock file path keyed by profile ID),isUnsupportedProviderErr(error classifier),tryCreateProvider(single-attempt create extracted fromEnsureProvider).Testing
-raceTestProfileFileLockPath_DeterministicAndUnique— verifies lock path determinism and uniquenessTestImportProfile_FlockSerializesConcurrent— 12 goroutines serialize through flockTestEnsureProvider_RetriesUnsupportedProvider— verifies retry on transient errorTestEnsureProvider_NoRetryOnOtherErrors— verifies no retry on non-transient errorsChecklist
!for breaking changes)Closes #6435
Post-script verification
agent/6435-import-profile-flock)934df3779e2d6dde861b28d97df3443b5a9b284c..HEAD)