Skip to content

Downloads: background range tasks stall/crawl after headset off; fresh tasks run fast (pre-dates overhaul) #409

Open

Description

@jlipworth

Symptom (Apple Vision Pro, on power)

Downloads stall or crawl after the headset has been off, and the problem goes back to before the downloads overhaul. It was first noticed around 1.7.2 (build 1501) and reproduced on 1.8.0 (build 1523).

  1. After the headset is taken off and put back on, the running downloads show no progress immediately afterwards.
  2. Two downloads that were already running crawl at about 100 KB/s each.
  3. Pausing one lets a waiting row start, and that row ramps up to 4–6 MB/s within about 20 s, while the other old row stays slow.

What the build-1523 diagnostic report shows

  • The slowness is real, not a display error. Each slow task's own byte counter advances at only 55–62 KB/s, so the row speed readout is accurate. Downloads: trustworthy aggregate speed after headset sleep (#379) #406 (Downloads: make aggregate speed trustworthy after headset sleep #379) makes the aggregate speed header honest, but does not change transfer speed.
  • The fast row is not a new download. It is a paused row with a 1 GiB checkpoint, resumed with new range tasks on the same background session, the same server and the same HTTP/2 path. The old row's tasks stay slow beside it. This points to task-level scheduling or connection state, not a bandwidth limit or the range-resume path itself.
  • Not the cause: session configuration (isDiscretionary = false and sessionSendsLaunchEvents = true on both main and the overhaul), task priority (default), checkpoint copies (finished promptly) and append size (4 MiB blocks).
  • Missing from the report: the retained window has no foreground, background-wake or wake-expiry events, so it cannot date the doff/don, and the rows carry no record of how their tasks were created.

Leading hypothesis

Range tasks created while the app is in the background (for example, the next segment created during an off-head wake) are treated as discretionary by the system, whatever the session flag says, and may stay deprioritised after the app returns to the foreground. The app's foreground reconciliation, on both main and the overhaul, checks only whether a tracked task exists. It counts every tracked task as live, so automatic restart skips those rows. Nothing checks whether a surviving task is suspended or making progress.

On the overhaul, P6 expired-wake fencing (BackgroundDownloadWakeCoordinator.isWindingDown includes expiredOperations) may add delay before refill. It is not the historical cause.

Proposed work

  1. Instrumentation first. Add AppDiagnostics events for task creation, foreground health and reissue, recording for each attempt:
    • session kind and the scene state when the task was created;
    • task state and priority;
    • byte-counter deltas;
    • durable offset and range length;
    • server route and response status;
    • wake-expiry and expired-operation counts.
  2. A bounded foreground task-health pass. On foreground:
    • reconcile actual task IDs against tracked ownership;
    • resume tasks that are legitimately suspended, after durability prerequisites are met;
    • pump admission;
    • sample byte deltas for 30–60 s.
      For a task confirmed stalled, reissue its range once, fenced, on the same session, restarting from the durable checkpoint. Do not use producing resume data: closed-range resume blobs are deliberately rejected.
  3. Then evaluate. Decide whether a default (foreground) session lane is worth it as a controlled fallback. That needs session-qualified task identities and a safe background handoff.

Repro for the instrumented build

  1. Use two downloads of finished static versions.
  2. Leave them running 60 s with the headset on.
  3. Take off the headset, still on power, for 10 minutes.
  4. Put it back on, bring Labstream to the foreground and leave it untouched for 60 s.
  5. Export the diagnostic report immediately.
  6. Pause and resume the same slow row, then export again.

If the slowness persists, a sysdiagnose would show how the system daemon is scheduling the tasks.

Related: #379 / #406 (speed readout), #403 (overhaul acceptance).

Activity

  1. jlipworth commented on Oct 18, 2026

    @jlipworth
    OwnerAuthor

    Why it lasts so long: static range tasks request closed 512 MiB segments, two per row. A task that stays deprioritised keeps its whole segment, and at about 60 KB/s a 512 MiB segment takes roughly 2.5 hours. The app only creates a replacement task when the segment completes, so the slowness persists for hours instead of clearing after a resume hiccup. The report also rules out a resume-time pause: bytes kept arriving the whole time, just slowly, and the resumed row's 1 GiB checkpoint copy began and ended in the same second.

    Option to evaluate alongside the foreground task-health pass: smaller segments, such as 32–64 MiB. Even with no other change, a deprioritised task would be replaced within minutes. Costs to measure: more requests and more frequent checkpoint appends (B85 write amplification).

  2. jlipworth commented on Oct 18, 2026

    @jlipworth
    OwnerAuthor

    The maintainer also saw the same very low per-download throughput on iOS with 1.8.0 (build 1523), so this is not specific to visionOS. That is consistent with the hypothesis: on iOS, background URLSession tasks created while the app is backgrounded are discretionary. The foreground health pass and reissue live in the shared BackgroundDownloadSession, so they apply to iOS/iPadOS and macOS as well. Device acceptance should include an iPhone run: lock the phone or background Labstream for at least 10 minutes with two static downloads running, then foreground it and leave it for 2 minutes before exporting diagnostics.

  3. jlipworth commented on Oct 18, 2026

    @jlipworth
    OwnerAuthor

    Included in 1.8.0 (build 1537), shipped to TestFlight internal from downloads/overhaul @ 12466ea. Device acceptance is pending; the issue stays open until then.

  4. jlipworth commented on Nov 5, 2026

    @jlipworth
    OwnerAuthor

    Landed on main in #425 (0b7f3fcb, tag v1.8.0). Any device or live acceptance gate already noted here still applies; the issue stays open until it's checked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions