source_constants_timeouts.bs

' @fileoverview Centralized timeout constants for API requests and task coordination.
' All values in milliseconds.

namespace timeouts

  ' Default HTTP request timeout passed to roku-requests.
  ' Used by ApiTask and SideEffectTask for all API pool requests.
  const HTTP_MS = 10000

  ' API pool caller wait (fetchRes, fetchAsync) for a request with the default HTTP limit.
  ' 2s buffer above HTTP_MS so the HTTP layer times out first,
  ' preventing orphaned pool slots when the wait expires before the HTTP call.
  ' A request with its own limit is waited for that limit plus the same buffer
  ' (apiTimeout.waitMs).
  const API_WAIT_MS = 12000

  ' HTTP limit for one page of a library grid (LoadItemsTask2), set as the request's own
  ' `timeoutMs` (source/api/apiTimeout.bs). A large library on a server whose database has
  ' not been optimized answers its first page slowly, and the answer, not the connection, is
  ' what takes the time: measured 2026-09-24 against Jellyfin 10.11.11 with 8,643 movies,
  ' 39.9-42 s for the first page, first byte at 34.6 s, where HTTP_MS gave up at 10 s and the
  ' grid showed nothing. 60 s is nginx's default proxy_read_timeout, so a server behind a
  ' default reverse proxy fails at the proxy no sooner than here. Safe only because a page
  ' abandoned by Back gives its pool slot up at once (apiPool.bs, "Stopping a request
  ' nobody is waiting for").
  const GRID_PAGE_MS = 60000

  ' HTTP limit for a quick play request that searches a whole library, or every library for one
  ' person or artist (QuickPlayTask), set as the request's own `timeoutMs`. Same shape of wait
  ' as a grid page, and the same 60 s for the same reverse-proxy reason (GRID_PAGE_MS):
  ' measured 2026-09-29 against Jellyfin 10.11.11 with 8,643 movies and `sqlite_stat1` dropped,
  ' the random 500-item library query took 17.2-17.4 s, and 34.2-35.0 s with user data, where
  ' HTTP_MS gave up at 10 s and Play did nothing (#811). Safe only because Back stops the quick
  ' play's Task, which gives its pool slot up at once.
  const QUICKPLAY_LIBRARY_MS = 60000

  ' Whole-run budget for one apiPipeline run (source/api/apiPipeline.bs).
  ' ONE budget across every request in the run, not a fresh one per request:
  ' a per-request wait lets N slow requests stack into N * API_WAIT_MS, so a
  ' dead server made a 10-library Home churn for ~48s (measured 2026-08-03). Must exceed API_WAIT_MS
  ' or a single slow-but-successful request gets cut short.
  '
  ' Charged only while the run waits on the server. The caller's per-result work
  ' between apiPipelineNext calls does not count against it.
  '
  ' This is a budget, not a guarantee of coverage. A run services roughly
  '   PIPELINE_RUN_MS / per-request-latency * pool width (apiPool.currentWidth())
  ' requests before it expires, and every entry past that is yielded undelivered.
  ' At the 11-library Home's measured ~132ms/request (2026-08-03) that is far more headroom
  ' than needed; at ~5s/request on a slow link it is about 12. So on a large,
  ' distant server the tail of a run legitimately fails, and callers must treat
  ' an undelivered entry as "no answer" rather than as data. See
  ' components/home/LoadLatestRowsTask.bs and source/home/latestRows.bs.
  const PIPELINE_RUN_MS = 20000

  ' Device check of a source's upstream HLS or DASH manifest URL, before playing it directly
  ' instead of through the server (LoadVideoContentTask.upstreamManifestAnswers). It exists because
  ' a Roku handed a URL it cannot reach does not fail: an unroutable host or an unresolvable name
  ' leaves it buffering (a live channel until the live stall error, a .strm item past two minutes
  ' with no error, measured 2026-10-11), so the fallback to the server does not run in time. The
  ' budget is the worst-case extra wait before that fallback path. Measured 2026-09-21: the check
  ' itself took 57-129 ms on a Roku Ultra against two live-TV CDNs, and a desktop on the same LAN saw
  ' 0.16-0.82 s per playlist GET, so 3 s is ~3.7x the slowest seen anywhere.
  const UPSTREAM_PREFLIGHT_MS = 3000

  ' HTTP limit for the PlaybackInfo request that opens a Live TV channel's live stream
  ' (LoadVideoContentTask). The server opens the stream whether or not the client is still
  ' waiting, and only its answer names the stream, so a client that gives up first leaves the
  ' stream, and on a tuner-limited server a tuner, open until the server restarts. So the app
  ' waits as long as the server itself can take. Measured 2026-09-26 on 10.7.7, 10.11.11 and
  ' 12.1.0: the server gives up on a source that never answers after its own 100 s
  ' (HttpClient.Timeout), and answered a source that responded at 95 s after 98.5-99.5 s,
  ' including the probe it runs on a channel's first open (>= 3 s by design). A source that
  ' answers but sends no data keeps the server waiting with no limit at all, and never yields a
  ' stream id; a longer limit would only hold a pool slot longer. Unlike GRID_PAGE_MS, Back does
  ' not give the slot up: the pool never stops a POST, and the answer must be heard to close the
  ' stream, so an abandoned open holds its slot for the whole wait (ADR 0045).
  const LIVE_OPEN_MS = 120000

  ' Time limit on a playback start's app wait (appWaits, AppWaitHost). A safety net,
  ' not an expected duration: every start is ended by the code that owns it (the player on its
  ' stream, a failure, Back), and this ends one that nobody did, logging it as an error. It
  ' must outlast the longest start that is still going: a live open's PlaybackInfo (LIVE_OPEN_MS
  ' plus apiTimeout's margin) together with the default-limit requests the load makes around it.
  const PLAYBACK_START_LIMIT_MS = 180000

  ' Fallback-font scale measurement wait (main.bs calculateFontScaleFactor).
  ' A safety net for a task that never reports, not an expected duration: it measures two
  ' labels. Blocks the main thread during bootstrap, so it is not larger.
  const FONT_SCALE_MS = 10000

  ' Session connection check timeout.
  ' Intentionally higher to accommodate slow/remote servers
  ' and redirect chains during initial connection.
  const CONNECTION_CHECK_MS = 35000

end namespace