Skip to content

fix/phantom progress on 403 - #573

Merged
SuperCoolPencil merged 7 commits into
mainfrom
fix/phantom-progress-on-403
Aug 1, 2026
Merged

fix/phantom progress on 403#573
SuperCoolPencil merged 7 commits into
mainfrom
fix/phantom-progress-on-403

Conversation

@SuperCoolPencil

@SuperCoolPencil SuperCoolPencil commented Aug 1, 2026

Copy link
Copy Markdown
Member
  • fix: defer activeTasks removal on error and handle permanent HTTP status codes in worker execution
  • fix(strategy/concurrent): prevent phantom progress and handle 403 as permanent error

Summary by CodeRabbit

  • Bug Fixes

    • Improved download state snapshots after failed tasks so outstanding bytes are preserved accurately.
    • Ensured permanent HTTP failures return a specific permanent-error classification.
    • Improved pause handling during retry backoff, including pause notifications and accurate zero-byte state reporting.
    • Improved task recovery and cleanup after failed or completed work.
    • Preserved shared download limits when resuming remaining work.
  • Tests

    • Added coverage for pausing downloads while waiting to retry.
    • Added coverage for preserving shared download limits during task recovery.

…permanent error

- Moves activeTasks cleanup to success path to ensure snapshot includes failing tasks
- Wraps permanent HTTP status codes (401, 403, 404) with ErrPermanentHTTP so scheduler doesn't retry them 10 times
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f79a15d-1d88-4514-95b9-d79d139f0ffe

📥 Commits

Reviewing files that changed from the base of the PR and between 43c2739 and c62d483.

📒 Files selected for processing (2)
  • internal/strategy/concurrent/downloader.go
  • internal/strategy/concurrent/worker.go
💤 Files with no reviewable changes (1)
  • internal/strategy/concurrent/downloader.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/strategy/concurrent/worker.go

📝 Walkthrough

Walkthrough

The concurrent downloader preserves shared task offsets, updates worker failure handling, wraps permanent HTTP errors, and adds coverage for pausing during retry backoff. The change also includes formatting-only updates.

Changes

Concurrent downloader behavior

Layer / File(s) Summary
Shared task offset propagation
internal/strategy/concurrent/task.go, internal/strategy/concurrent/task_test.go
RemainingTask and RemainingBytes capture SharedMaxOffset under its lock. Offset calculations use the captured pointer, and returned tasks preserve it. Tests cover the shared-offset behavior.
Failure-state handling and pause validation
internal/strategy/concurrent/worker.go, internal/strategy/concurrent/concurrent_test.go, internal/strategy/concurrent/downloader.go, internal/strategy/concurrent/downloader_helpers_test.go
Workers update active-worker counts before result handling, requeue remaining work after failures, and remove active-task entries. Permanent HTTP statuses wrap types.ErrPermanentHTTP. Integration coverage validates pause errors, events, and saved progress during retry backoff. Supporting test formatting and whitespace cleanup are included.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary fix: preventing phantom progress for HTTP 403 responses.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@internal/strategy/concurrent/worker.go`:
- Around line 169-183: Update the failed-task handling in the worker flow around
activeTasks and saveStateSnapshot so an errored task cannot contribute work to a
saved snapshot. Mark failed entries terminal and make snapshot collection skip
them, or otherwise ensure balancing completes before saveStateSnapshot runs;
preserve accurate remaining-byte accounting and queue only recoverable active
ranges.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e202a7b2-81c9-44de-8bcf-da65c18687da

📥 Commits

Reviewing files that changed from the base of the PR and between a3afd44 and ebcdec2.

📒 Files selected for processing (5)
  • internal/strategy/concurrent/concurrent_test.go
  • internal/strategy/concurrent/downloader.go
  • internal/strategy/concurrent/downloader_helpers_test.go
  • internal/strategy/concurrent/task.go
  • internal/strategy/concurrent/worker.go
💤 Files with no reviewable changes (1)
  • internal/strategy/concurrent/concurrent_test.go

Comment thread internal/strategy/concurrent/worker.go
- Pushes remaining work of a failed active task back to the queue before deleting it from activeTasks, marking it terminal but preserving accurate byte accounting and queuing recoverable ranges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/strategy/concurrent/worker.go (1)

169-182: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make failed-task requeue and active-task removal atomic.

ActiveWorkers is decremented before the failed remainder is queued. The queue push also occurs before d.activeTasks[id] is deleted and outside d.activeMu. StealWork and HedgeWork can observe the failed task between these operations and enqueue overlapping ranges. This can duplicate downloads and corrupt byte accounting.

Coordinate the queue push, active-task removal, and worker-count update as one state transition. Holding d.activeMu across the requeue and deletion, or marking the task terminal and making all balancers skip it, prevents this race. Add a regression test that interleaves failure handling with StealWork.

🤖 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 `@internal/strategy/concurrent/worker.go` around lines 169 - 182, Make
failed-task cleanup a single synchronized state transition in the worker failure
path: coordinate the ActiveWorkers decrement, remaining-task requeue, and
deletion from d.activeTasks under d.activeMu so StealWork and HedgeWork cannot
observe an intermediate state or enqueue overlapping ranges. Preserve existing
retry behavior, and add a regression test that interleaves failure handling with
StealWork.
🤖 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.

Inline comments:
In `@internal/strategy/concurrent/worker.go`:
- Around line 176-178: The requeue path in the worker drops SharedMaxOffset from
failed hedged task remainders, allowing duplicate work. Update
ActiveTask.RemainingTask to preserve and safely read SharedMaxOffset under
SharedMaxOffsetMu, ensure the requeued task retains that shared deduplication
state, and add a regression test covering hedged-task failure and requeue
behavior.

---

Outside diff comments:
In `@internal/strategy/concurrent/worker.go`:
- Around line 169-182: Make failed-task cleanup a single synchronized state
transition in the worker failure path: coordinate the ActiveWorkers decrement,
remaining-task requeue, and deletion from d.activeTasks under d.activeMu so
StealWork and HedgeWork cannot observe an intermediate state or enqueue
overlapping ranges. Preserve existing retry behavior, and add a regression test
that interleaves failure handling with StealWork.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bcc21d41-d4f1-4bc2-9db0-cad9883fccc0

📥 Commits

Reviewing files that changed from the base of the PR and between ebcdec2 and 105f955.

📒 Files selected for processing (1)
  • internal/strategy/concurrent/worker.go

Comment thread internal/strategy/concurrent/worker.go
…ueue

- ActiveTask.RemainingTask now safely reads and preserves SharedMaxOffset under SharedMaxOffsetMu
- Ensures requeued remainder tasks correctly retain their deduplication state

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/strategy/concurrent/task_test.go (1)

176-204: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add concurrency coverage for SharedMaxOffset initialization.

This test validates value propagation in one goroutine. It does not overlap RemainingTask() with initialization, so it would pass even if the mutex protection were removed. Add a coordinated concurrent test and run it with the race detector.

🤖 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 `@internal/strategy/concurrent/task_test.go` around lines 176 - 204, Extend
TestActiveTask_RemainingTask_PreservesSharedMaxOffset with a coordinated
concurrent scenario that overlaps SharedMaxOffset initialization with
RemainingTask(), using synchronization to control ordering and multiple
goroutines as needed. Assert the resulting task preserves the initialized shared
offset and run the test under the race detector to verify the mutex-protected
initialization is race-free.
🤖 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.

Inline comments:
In `@internal/strategy/concurrent/task.go`:
- Around line 58-65: Update the task construction flow to acquire
SharedMaxOffsetMu before calculating current, capture a single SharedMaxOffset
pointer snapshot, and pass that snapshot into at.currentOffset() (or its updated
offset-calculation signature). Reuse the same captured pointer for
types.Task.SharedMaxOffset so Offset and the returned task are based on one
synchronized pointer state.

---

Nitpick comments:
In `@internal/strategy/concurrent/task_test.go`:
- Around line 176-204: Extend
TestActiveTask_RemainingTask_PreservesSharedMaxOffset with a coordinated
concurrent scenario that overlaps SharedMaxOffset initialization with
RemainingTask(), using synchronization to control ordering and multiple
goroutines as needed. Assert the resulting task preserves the initialized shared
offset and run the test under the race detector to verify the mutex-protected
initialization is race-free.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2eb459c-dc8a-48cc-a4f9-2c3811929950

📥 Commits

Reviewing files that changed from the base of the PR and between 105f955 and a00c9df.

📒 Files selected for processing (2)
  • internal/strategy/concurrent/task.go
  • internal/strategy/concurrent/task_test.go

Comment thread internal/strategy/concurrent/task.go Outdated
- Acquires SharedMaxOffsetMu before calculating currentOffset
- Captures a single SharedMaxOffset pointer snapshot to pass to currentOffset
- Reuses the identical pointer snapshot for types.Task to guarantee synchronized pointer state between the offset calculation and the returned task structure

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/strategy/concurrent/task.go (1)

52-66: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve the SharedMaxOffset captured by RemainingTask.

RemainingTask already guards at.SharedMaxOffset and returns the captured pointer in types.Task. Lines 568-570 overwrite this with active.SharedMaxOffset before persisting, so snapshot deduplication can use a different pointer than the calculated remaining offset. Use remaining.SharedMaxOffset directly for the snapshot deduplication map.

🤖 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 `@internal/strategy/concurrent/task.go` around lines 52 - 66, Update the
snapshot deduplication logic to use the SharedMaxOffset captured in the task
returned by RemainingTask, rather than replacing it with active.SharedMaxOffset
before persistence. Locate the persistence path around the remaining-task
handling and pass remaining.SharedMaxOffset directly to the deduplication map,
preserving the calculated snapshot pointer.
🤖 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.

Outside diff comments:
In `@internal/strategy/concurrent/task.go`:
- Around line 52-66: Update the snapshot deduplication logic to use the
SharedMaxOffset captured in the task returned by RemainingTask, rather than
replacing it with active.SharedMaxOffset before persistence. Locate the
persistence path around the remaining-task handling and pass
remaining.SharedMaxOffset directly to the deduplication map, preserving the
calculated snapshot pointer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa8f41a2-f08b-4cbf-804c-79fa87747271

📥 Commits

Reviewing files that changed from the base of the PR and between a00c9df and 43c2739.

📒 Files selected for processing (1)
  • internal/strategy/concurrent/task.go

@SuperCoolPencil

Copy link
Copy Markdown
Member Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Reviews resumed.

@SuperCoolPencil
SuperCoolPencil merged commit 6ea3359 into main Aug 1, 2026
18 of 19 checks passed
@SuperCoolPencil
SuperCoolPencil deleted the fix/phantom-progress-on-403 branch August 1, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant