Skip to content

Read the quick session's trace start off the origin, not as a start (#503) - #515

Merged
ramesh130 merged 4 commits into
mainfrom
issue-503-ios-trace-start-bound
Oct 13, 2026
Merged

ramesh130 merged 4 commits into
mainfrom
issue-503-ios-trace-start-bound

Conversation

@ramesh130

Copy link
Copy Markdown
Owner

Closes #503

The reported failure

It is not in any CI log. I searched every failed ci.yml run since the test landed (#474, 2026-10-11), and the first attempts of the re-run runs (38349931735, 38217042037, 38214076415, 38188791871, 38185478104). None carries MeetsTheSameStretch. The only TraceStartPlaybackTests failure in CI is the sibling's, on #474's own branch before it merged (run 38169669001). The issue's report is from local batch workers at a load of about 160. The mechanism below is reproduced with exactly the issue's message:

PlaybackHarness.swift:100: error: -[SuperPlayerTestkitTests.TraceStartPlaybackTests testASessionQuickToReachItsFirstSegmentMeetsTheSameStretch] : failed: caught error: "playback starts did not hold within 60.0 seconds"

Cause

The trace's clock already starts at the first media segment (#474). What the host does after that point is still the host's.

An instrumented run logged each request's arrival relative to the first segment:

  • The stock AVPlayer asks for .videoHls() segments one at a time.
  • It reports readyToPlay only once two are loaded (loadedTimeRanges 0–4 s).

So the session starts only if the second segment is asked for inside the four-second fast stretch. That depends on how quickly the host gets from the first segment to asking for the second, not on anything the anchor decides. A host that misses the stretch asks for segment 1 on the quarter-rate link. AVFoundation then abandons it and asks again roughly every 3–7 s (seen: 0.2, 3.2, 9.2, 11.2, 16.1 … s), and never starts.

Reproduction (before the fix)

The racing side is the request for the second segment, so I made it lose by holding its response rather than by adding host load. I added FaultScript.Builder().addLatencyMs(5_000, kind: .mediaSegment, index: 1) to the quick test's serve. The test then failed every time with the message above, after 62 s.

A held third segment (index: 2) still started at 0.26 s, which is what pins the second segment as the one that matters.

Fix

The quick test no longer waits for a start, because no anchor can make that host-independent. It reads what the anchor decides off the origin instead:

  • The slow stretch is now on .cellular. A trace's transport is reported per response and never switched, so no player behaves differently. That lets OriginResponse.transport say which stretch a request arrived on.
  • Every response up to and including the first segment is held to the fast stretch's transport.
  • The first segment is 200, and its bytes are due inside the fast stretch at that stretch's rate. This is the origin's pacing as arithmetic: zero round trip, first byte at the stretch's start.
  • The first segment is asked for once, as before.

With the 5 s hold on segment 1 still in place, the fixed test passed. I removed the hold before committing.

As a mutant check, I moved the origin's anchor back to its own start and held the media playlist 8 s. The new transport assertion then failed on two responses, so it does catch an anchor that paces the session's pre-segment requests on the slow stretch.

ADR-0021 rule 4 gains a #503 addendum, which is listed in the header's amendment list. docs/testing.md's iOS replay bullet gains one clause.

Decisions argued

  • Removing the start wait is not a weakened assertion. The issue asks to "assert the trace's structure rather than reaching it inside a fixed bound". A start cannot be read host-independently on a link that turns slow on a wall-clock schedule, because the player's second request lands wherever the host puts it. The claim "the anchor costs a quick host nothing" is now carried by structure: nothing the session asked for up to its first segment was served on the slow stretch.
  • The new readings are close to the anchor's own guarantee. For an unheld session the first segment is trace zero by construction, so the transport readings act as a control on pre-segment pacing rather than as a discriminator, as the original test was ("it passed before the anchor too"). The spec review suggested asserting the next response's transport. I did not, because that response is the very request whose timing is the host's, so it would bring back the race.
  • The sibling is untouched. testASessionSlowToReach… keeps its start and the iOS: a SuperPlayer on a video ladder stalls on the iOS 26.5 simulator (fetching stops; four stuck points) #474 margin. It is exposed to the same post-anchor mechanism, but it is outside this issue, and the addendum says so. If it flakes, the same reading applies.
  • No testkit API change. OriginResponse.transport already exists, so there is no baseline diff.

Review summary

  • Standards axis: no hard findings. Soft findings fixed:
    • docs/testing.md now says how the quick session is judged.
    • The slow stretch's .cellular carries a comment, so no one "fixes" it back to .wifi.
    • The delivery arithmetic names its zero-round-trip assumption.
    • The duplicated firstSegmentPath predicate is folded into one closure.
    • The addendum says why the reading holds at any host speed: the first segment is the anchor.
    • ios/CLAUDE.md's harness bullet was left alone, since its claim about the anchor is unchanged.
  • Spec axis:
    • The route taken is the one the issue offers.
    • Main finding: the new assertions are mostly the anchor's own guarantee (answered above).
    • The sibling's exposure is noted above.
    • AC3 (the known-flake list): no tracked file holds that list. It lives in the batch brief, which already says this test is not on it.
    • Reproduction: described above.

Verification

  • xcodebuild test -test-iterations 20 -only-testing:SuperPlayerTestkitTests/TraceStartPlaybackTests/testASessionQuickToReachItsFirstSegmentMeetsTheSameStretch on the sp-503 simulator: 20 of 20 passed.
  • The whole TraceStartPlaybackTests class passed, with and without the reproduction's hold.
  • SP_SIMULATOR=sp-503 ios/check passed: 710 iOS tests, 12 tvOS, 38 MoQ, the demo builds and the API baselines.
  • No device run: this is a test-only change, and the simulator is the environment the issue is about.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe

ramesh130 and others added 4 commits October 13, 2026 19:36
… second segment does not decide it

The quick session waited for a start, and AVPlayer is ready only with two segments asked for one at
a time: a loaded host asked for the second after the four-second fast stretch, met the slow one and
never started (#503). No anchor moves that, so the test now holds what the anchor decides — every
response through the first segment on the fast stretch's transport, the segment whole inside it —
with the slow stretch on a transport of its own. ADR-0021 rule 4 gains a #503 addendum.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe
…mes, mark the slow stretch's transport, and say in testing.md how the quick session is judged

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MJAhQmFgdRRAKUaMHibQLe

# Conflicts:
#	docs/adr/0021-test-ios-against-a-loopback-origin-and-run-its-checks-in-one-script.md
@ramesh130
ramesh130 merged commit 97edf11 into main Oct 13, 2026
3 checks passed
@ramesh130
ramesh130 deleted the issue-503-ios-trace-start-bound branch October 13, 2026 10:42
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.

Flake (iOS): TraceStartPlaybackTests …MeetsTheSameStretch times out at 60 s under load

1 participant