From 68b04297e70f6ceb0d8879c3a2b9ab9041f3ad98 Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Tue, 13 Oct 2026 15:39:48 -0400 Subject: [PATCH 1/2] fix(webxr): let 0 disable the stream-attach and warm-up timeouts (#1257) streamAttachTimeoutMs, warmupBeginTimeoutMs and warmupEndTimeoutMs were passed straight to setTimeout, so 0 fired the timer immediately instead of turning detection off. Treat a value <= 0 as disabled for each timer. warmupEndTimeoutMs <= 0 also disables the warm-up-begin timer, since that timer only hands off to the end timer; stream-attach detection is unaffected. Signed-off-by: Gareth Morgan Co-authored-by: Claude Sonnet 5.5 (cherry picked from commit cb3ba6dbdfaf88609b072b05fcfbc5fe4f4658e0) Signed-off-by: Gareth Morgan --- .../helpers/react/CloudXRComponent.tsx | 16 ++++++++++++---- deps/cloudxr/webxr_client/src/config/params.ts | 6 +++--- deps/cloudxr/webxr_client/src/index.html | 10 ++++++---- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx index 8b6b514640..9a7b184a36 100644 --- a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx +++ b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx @@ -151,7 +151,8 @@ interface CloudXRComponentProps { * only whether the resulting synthetic error gets retried depends on `reconnect`. Doubles on * every reconnect attempt (this value, then x2, then x4, ...) so a connection that's genuinely * just slow, not stuck, gets more time on each retry instead of being cut off at the same fixed - * threshold every attempt. Defaults to STREAM_ATTACH_BASE_TIMEOUT_MS (2 minutes). + * threshold every attempt. Defaults to STREAM_ATTACH_BASE_TIMEOUT_MS (2 minutes). A value <= 0 + * disables this detection. */ streamAttachTimeoutMs?: number; @@ -166,7 +167,8 @@ interface CloudXRComponentProps { * message. Like warmupEndTimeoutMs, does NOT grow per reconnect attempt - warm-up is a fast, * bounded server-side priming step once the stream has attached at all, not something that * legitimately takes longer under retry the way the initial attach can. Defaults to - * WARMUP_BEGIN_BASE_TIMEOUT_MS (10 seconds). + * WARMUP_BEGIN_BASE_TIMEOUT_MS (10 seconds). A value <= 0 disables this detection, as does + * warmupEndTimeoutMs <= 0. */ warmupBeginTimeoutMs?: number; @@ -176,7 +178,8 @@ interface CloudXRComponentProps { * the first warm-up log arrives and cleared by WARMUP_COMPLETE_LOG_PATTERN). See * warmupBeginTimeoutMs's doc comment for why this is a separate deadline. Does NOT grow per * reconnect attempt, for the same reason warmupBeginTimeoutMs doesn't. Defaults to - * WARMUP_END_BASE_TIMEOUT_MS (30 seconds). + * WARMUP_END_BASE_TIMEOUT_MS (30 seconds). A value <= 0 disables this detection and + * warmupBeginTimeoutMs too (all warm-up detection); stream-attach detection is unaffected. */ warmupEndTimeoutMs?: number; @@ -357,6 +360,8 @@ export default function CloudXRComponent({ */ const armStreamAttachTimer = useCallback( (cxrSession: CloudXR.Session) => { + // <= 0 disables detection; setTimeout(fn, 0) would fire immediately instead. + if (streamAttachBaseTimeoutMs <= 0) return; const attemptForThisTimer = reconnectAttemptRef.current; const attachTimeoutMs = Math.min( streamAttachBaseTimeoutMs * 2 ** attemptForThisTimer, @@ -397,6 +402,8 @@ export default function CloudXRComponent({ */ const armWarmupBeginTimer = useCallback( (cxrSession: CloudXR.Session) => { + // warmupEndTimeoutMs <= 0 turns off all warm-up detection; the begin timer only hands off to the end timer. + if (warmupBeginBaseTimeoutMs <= 0 || warmupEndBaseTimeoutMs <= 0) return; // Same overflow guard as armStreamAttachTimer: an uncapped delay above setTimeout's // ~24.8-day int32 limit wraps and fires near-immediately instead of waiting. const timeoutMs = Math.min(warmupBeginBaseTimeoutMs, MAX_SET_TIMEOUT_MS); @@ -416,7 +423,7 @@ export default function CloudXRComponent({ } as CloudXR.StreamingError); }, timeoutMs); }, - [warmupBeginBaseTimeoutMs] + [warmupBeginBaseTimeoutMs, warmupEndBaseTimeoutMs] ); /** @@ -429,6 +436,7 @@ export default function CloudXRComponent({ */ const armWarmupEndTimer = useCallback( (cxrSession: CloudXR.Session) => { + if (warmupEndBaseTimeoutMs <= 0) return; // disabled, see armStreamAttachTimer const timeoutMs = Math.min(warmupEndBaseTimeoutMs, MAX_SET_TIMEOUT_MS); warmupEndTimerRef.current = setTimeout(() => { warmupEndTimerRef.current = null; diff --git a/deps/cloudxr/webxr_client/src/config/params.ts b/deps/cloudxr/webxr_client/src/config/params.ts index 662ec019c8..d98b622af9 100644 --- a/deps/cloudxr/webxr_client/src/config/params.ts +++ b/deps/cloudxr/webxr_client/src/config/params.ts @@ -265,21 +265,21 @@ export const URL_PARAMS: UrlParam[] = [ elementId: 'cloudxrStreamAttachTimeoutMs', isValid: isNumber, description: - 'Base timeout for detecting a stream that never attaches ("passthrough-only"), in milliseconds. Independent of retry.', + 'Base timeout for detecting a stream that never attaches ("passthrough-only"), in milliseconds. Independent of retry. 0 disables.', }, { key: 'warmupBeginTimeoutMs', elementId: 'cloudxrWarmupBeginTimeoutMs', isValid: isNumber, description: - 'Timeout for detecting a stream that attached but produced no decoder warm-up signal at all, in milliseconds. Does not grow on retry.', + 'Timeout for detecting a stream that attached but produced no decoder warm-up signal at all, in milliseconds. Does not grow on retry. 0 disables.', }, { key: 'warmupEndTimeoutMs', elementId: 'cloudxrWarmupEndTimeoutMs', isValid: isNumber, description: - 'Timeout for detecting decoder warm-up that began but never finished, in milliseconds. Does not grow on retry.', + 'Timeout for detecting decoder warm-up that began but never finished, in milliseconds. Does not grow on retry. 0 disables all warm-up detection, including the begin timeout.', }, { key: 'proxyUrl', diff --git a/deps/cloudxr/webxr_client/src/index.html b/deps/cloudxr/webxr_client/src/index.html index b2d3e0c9f7..dd8d08de1d 100644 --- a/deps/cloudxr/webxr_client/src/index.html +++ b/deps/cloudxr/webxr_client/src/index.html @@ -1563,8 +1563,8 @@

Advanced settings

How long to wait for the video stream to attach before treating the session as stuck ("passthrough-only" - entered XR with nothing rendering). Independent of - Retry on Error above: this detection always runs; doubles on each retry attempt - when retry is also on. + Retry on Error above: this detection runs even with retry off; doubles on each + retry attempt when retry is also on. Set to 0 to disable.
@@ -1585,7 +1585,8 @@

Advanced settings

How long to wait, after the stream attaches, for the first sign that decoder warm-up has started before treating the session as stuck. Independent of Stream - Attach Timeout above (a separate, later window); does not grow on retry. + Attach Timeout above (a separate, later window); does not grow on retry. Set to 0 + to disable.
@@ -1605,7 +1606,8 @@

Advanced settings

/>
How long to wait for decoder warm-up to finish once it has begun before treating - the session as stuck. Does not grow on retry. + the session as stuck. Does not grow on retry. Set to 0 to disable all warm-up + detection, including the Warm-up Begin Timeout above.
From c99ad01dc556aac965fd2984dc16e5ed8b08ea56 Mon Sep 17 00:00:00 2001 From: Gareth Morgan Date: Wed, 28 Oct 2026 10:26:55 -0400 Subject: [PATCH 2/2] webxr: disable the decoder warm-up timeouts by default A CloudXR runtime with no OpenXR app streams idle frames that count as decoder warm-up frames, so the 10 s begin and 30 s end deadlines can tear down a healthy session that is only waiting for an app to start. Default warmupBeginTimeoutMs and warmupEndTimeoutMs to 0, which disables the detection; callers and the URL/OOB parameters can still set a deadline. Stream-attach detection is unchanged. Co-Authored-By: Claude Sonnet 5.5 Signed-off-by: Gareth Morgan (cherry picked from commit fae20e8f0612094ab41f5b3d8a1e778928607ce8) Signed-off-by: Gareth Morgan --- .../helpers/react/CloudXRComponent.tsx | 22 +++++++++---------- deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx | 4 ++-- deps/cloudxr/webxr_client/src/index.html | 8 +++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx index 9a7b184a36..1ec46cb588 100644 --- a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx +++ b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx @@ -74,15 +74,15 @@ const WARMUP_COMPLETE_LOG_PATTERN = // override via the streamAttachTimeoutMs prop, not by lowering this default. const STREAM_ATTACH_BASE_TIMEOUT_MS = 120000; // 2 minutes -// Defaults for the warmupBeginTimeoutMs/warmupEndTimeoutMs props below. Both shorter than -// STREAM_ATTACH_BASE_TIMEOUT_MS and, unlike it, neither grows per reconnect attempt (see -// armWarmupBeginTimer/armWarmupEndTimer): once the stream has attached at all, decoder warm-up -// is a fast, bounded startup step (server encoder priming), not something that legitimately -// takes longer on a slow network the way the initial attach can. Split into two deadlines -// because "no warm-up signal at all since attaching" and "warm-up started but never finishes" -// are different failure modes worth distinguishing in the synthetic error message. -const WARMUP_BEGIN_BASE_TIMEOUT_MS = 10000; // 10 seconds - first warm-up log since onStreamStarted -const WARMUP_END_BASE_TIMEOUT_MS = 30000; // 30 seconds - completion since warm-up began +// Defaults for the warmupBeginTimeoutMs/warmupEndTimeoutMs props below. 0 disables warm-up +// detection: a CloudXR runtime with no OpenXR app streams idle frames that count as warm-up +// frames, so a fixed deadline can tear down a healthy session that is only waiting for an app. +// Callers that want detection (e.g. tests) set the props. Neither deadline grows per reconnect +// attempt (see armWarmupBeginTimer/armWarmupEndTimer). Split into two because "no warm-up signal +// at all since attaching" and "warm-up started but never finishes" are different failure modes +// worth distinguishing in the synthetic error message. +const WARMUP_BEGIN_BASE_TIMEOUT_MS = 0; // disabled; first warm-up log since onStreamStarted +const WARMUP_END_BASE_TIMEOUT_MS = 0; // disabled; completion since warm-up began // setTimeout's delay is stored as a 32-bit signed int internally; a value above this overflows // and fires (almost) immediately instead of waiting - the opposite of what a long configured @@ -167,7 +167,7 @@ interface CloudXRComponentProps { * message. Like warmupEndTimeoutMs, does NOT grow per reconnect attempt - warm-up is a fast, * bounded server-side priming step once the stream has attached at all, not something that * legitimately takes longer under retry the way the initial attach can. Defaults to - * WARMUP_BEGIN_BASE_TIMEOUT_MS (10 seconds). A value <= 0 disables this detection, as does + * WARMUP_BEGIN_BASE_TIMEOUT_MS (0, disabled). A value <= 0 disables this detection, as does * warmupEndTimeoutMs <= 0. */ warmupBeginTimeoutMs?: number; @@ -178,7 +178,7 @@ interface CloudXRComponentProps { * the first warm-up log arrives and cleared by WARMUP_COMPLETE_LOG_PATTERN). See * warmupBeginTimeoutMs's doc comment for why this is a separate deadline. Does NOT grow per * reconnect attempt, for the same reason warmupBeginTimeoutMs doesn't. Defaults to - * WARMUP_END_BASE_TIMEOUT_MS (30 seconds). A value <= 0 disables this detection and + * WARMUP_END_BASE_TIMEOUT_MS (0, disabled). A value <= 0 disables this detection and * warmupBeginTimeoutMs too (all warm-up detection); stream-attach detection is unaffected. */ warmupEndTimeoutMs?: number; diff --git a/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx b/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx index 70a985d9d6..e8e57ac071 100644 --- a/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx +++ b/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx @@ -596,8 +596,8 @@ export class CloudXR2DUI { reconnectMaxAttempts: 3, reconnectDelayMs: 3000, streamAttachTimeoutMs: 120000, - warmupBeginTimeoutMs: 10000, - warmupEndTimeoutMs: 30000, + warmupBeginTimeoutMs: 0, + warmupEndTimeoutMs: 0, teleopPath: DEFAULT_TELEOP_PATH, }; } diff --git a/deps/cloudxr/webxr_client/src/index.html b/deps/cloudxr/webxr_client/src/index.html index dd8d08de1d..a8ccfe0f5e 100644 --- a/deps/cloudxr/webxr_client/src/index.html +++ b/deps/cloudxr/webxr_client/src/index.html @@ -1570,7 +1570,7 @@

Advanced settings

Warm-up Begin Timeout (ms, default: 0 / disabled) Advanced settings step="1000" placeholder="Warm-up begin timeout in ms" spellcheck="false" - value="10000" + value="0" />
How long to wait, after the stream attaches, for the first sign that decoder @@ -1592,7 +1592,7 @@

Advanced settings

Warm-up End Timeout (ms, default: 0 / disabled) Advanced settings step="1000" placeholder="Warm-up end timeout in ms" spellcheck="false" - value="30000" + value="0" />
How long to wait for decoder warm-up to finish once it has begun before treating