From 187931be8edfcfd61c0e695388609f7a28c901a7 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 26 Jun 2026 09:43:37 -0400 Subject: [PATCH 1/3] perf(#4759): parallelize fresh sidebar boot fetches --- static/sessions.js | 30 +- ...t_issue4759_parallel_sidebar_boot_fetch.py | 259 ++++++++++++++++++ 2 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 tests/test_issue4759_parallel_sidebar_boot_fetch.py diff --git a/static/sessions.js b/static/sessions.js index e6980ad934a..3e8ce4d7ae9 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -4148,16 +4148,7 @@ async function _runRenderSessionListRefresh(opts, _gen){ retryTimeouts:true, retryStatuses:[502,503,504], }; - const sessData = _sessionListHasLoadedOnce - ? await api('/api/sessions' + sessionListQS,{timeoutToast:false}) - : await api('/api/sessions' + sessionListQS,sessionRequestOpts); - let projData={projects:_allProjects||[]}; - try{ - const projectQS = _showAllProfiles ? '?all_profiles=1' : ''; - projData = await api('/api/projects' + projectQS,{timeoutToast:false}); - }catch(projectError){ - console.warn('renderProjectsList',projectError); - } + const {sessData, projData}=await _loadSidebarSessionListPayload(sessionListQS, sessionRequestOpts); // Discard stale response — a newer renderSessionList() call superseded us. if (_gen !== _renderSessionListGen) return; // #4671: while a profile switch is mid-flight, drop ANY payload — even one whose @@ -4211,6 +4202,25 @@ async function _runRenderSessionListRefresh(opts, _gen){ } } +async function _loadSidebarSessionListPayload(sessionListQS, sessionRequestOpts){ + const projectPromise = (async() => { + try{ + const projectQS = _showAllProfiles ? '?all_profiles=1' : ''; + return await api('/api/projects' + projectQS,{timeoutToast:false}); + }catch(projectError){ + console.warn('renderProjectsList',projectError); + return {projects:_allProjects||[]}; + } + })(); + + const sessData = _sessionListHasLoadedOnce + ? await api('/api/sessions' + sessionListQS,{timeoutToast:false}) + : await api('/api/sessions' + sessionListQS,sessionRequestOpts); + const projData = await projectPromise; + + return {sessData,projData}; +} + async function _drainRenderSessionListQueue(initialRequest){ let request=initialRequest; try{ diff --git a/tests/test_issue4759_parallel_sidebar_boot_fetch.py b/tests/test_issue4759_parallel_sidebar_boot_fetch.py new file mode 100644 index 00000000000..983ecec5598 --- /dev/null +++ b/tests/test_issue4759_parallel_sidebar_boot_fetch.py @@ -0,0 +1,259 @@ +"""Regression tests for #4759: parallelize first-load sidebar boot fetches.""" + +from __future__ import annotations + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") +NODE = shutil.which("node") + + +def _extract_function(source_text: str, function_name: str) -> str: + marker = f"async function {function_name}(" + start = source_text.find(marker) + if start < 0: + marker = f"function {function_name}(" + start = source_text.find(marker) + assert start >= 0, f"{function_name}() not found" + brace_start = source_text.find("{", start) + assert brace_start >= 0, f"{function_name} body not found" + + depth = 0 + in_string = None + escaped = False + in_line_comment = False + in_block_comment = False + + for index in range(brace_start, len(source_text)): + char = source_text[index] + nxt = source_text[index + 1] if index + 1 < len(source_text) else "" + + if in_line_comment: + if char == "\n": + in_line_comment = False + continue + if in_block_comment: + if char == "*" and nxt == "/": + in_block_comment = False + continue + if in_string: + if escaped: + escaped = False + elif char == "\\": + escaped = True + elif char == in_string: + in_string = None + continue + if char == "/" and nxt == "/": + in_line_comment = True + continue + if char == "/" and nxt == "*": + in_block_comment = True + continue + if char in ("'", '"', "`"): + in_string = char + continue + + if char == "{": + depth += 1 + elif char == "}": + depth -= 1 + if depth == 0: + return source_text[start : index + 1] + + raise AssertionError(f"could not extract {function_name}()") + + +def _run_node(script: str): + completed = subprocess.run( + [NODE, "-e", script], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + ) + assert completed.returncode == 0, completed.stderr or completed.stdout + return json.loads(completed.stdout.strip()) + + +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_cold_boot_starts_projects_fetch_before_sessions_resolve(): + """The project request must start before the session request settles.""" + fetch_helper = _extract_function(SESSIONS_JS, "_loadSidebarSessionListPayload") + script = f""" + const calls = []; + let resolveSessions; + let resolveProjects; + + global._showAllProfiles = false; + global._allProjects = []; + global._sessionListHasLoadedOnce = false; + global.api = (url) => {{ + if (url.startsWith('/api/projects')) {{ + calls.push('projects'); + return new Promise(resolve => {{ + resolveProjects = resolve; + }}); + }} + if (url.startsWith('/api/sessions')) {{ + calls.push('sessions'); + return new Promise(resolve => {{ + resolveSessions = resolve; + }}); + }} + return Promise.reject(new Error('unexpected endpoint ' + url)); +}}; +{fetch_helper} + +(async () => {{ + const run = _loadSidebarSessionListPayload('?v=1', {{ + timeoutToast:false, + timeoutMs:90000, + retries:1, + retryTimeouts:true, + retryStatuses:[502,503,504], + }}); + + await Promise.resolve(); + const orderAtSettleBoundary = calls.slice(); + + resolveSessions({{ + sessions:[{{session_id:'s1'}}], + other_profile_count:0, + archived_count:0, + archived_webui_count:0, + archived_cli_count:0, + }}); + resolveProjects({{projects:[]}}); + const payload = await run; + console.log(JSON.stringify({{orderAtSettleBoundary,payload,calls:calls}})); +}})().catch(error => {{ + console.error(error); + process.exit(1); +}}); +""" + + body = _run_node(script) + assert body["orderAtSettleBoundary"] == ["projects", "sessions"] + assert body["payload"]["sessData"]["sessions"] == [{"session_id": "s1"}] + assert body["payload"]["projData"]["projects"] == [] + + +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_project_failure_falls_back_without_blocking_session_payload(): + """Project endpoint failure should still return session payload and fallback projects.""" + fetch_helper = _extract_function(SESSIONS_JS, "_loadSidebarSessionListPayload") + script = f""" + const calls = []; + global._showAllProfiles = true; + global._allProjects = []; + global._sessionListHasLoadedOnce = false; + global.api = (url) => {{ + if (url.startsWith('/api/projects')) {{ + calls.push('projects'); + return Promise.reject(new Error('project endpoint down')); + }} + if (url.startsWith('/api/sessions')) {{ + calls.push('sessions'); + return Promise.resolve({{ + sessions:[{{session_id:'s1'}}], + other_profile_count:0, + archived_count:0, + archived_webui_count:0, + archived_cli_count:0, + }}); + }} + return Promise.reject(new Error('unexpected endpoint ' + url)); +}}; +{fetch_helper} + +(async () => {{ + const payload = await _loadSidebarSessionListPayload('?v=1', {{ + timeoutToast:false, + timeoutMs:90000, + retries:1, + retryTimeouts:true, + retryStatuses:[502,503,504], + }}); + console.log(JSON.stringify({{calls, payload}})); +}})().catch(error => {{ + console.error(error); + process.exit(1); +}}); +""" + + body = _run_node(script) + assert body["calls"] == ["projects", "sessions"] + assert body["payload"]["sessData"]["sessions"][0]["session_id"] == "s1" + assert body["payload"]["projData"]["projects"] == [] + + +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_existing_session_request_options_are_preserved(): + """The cold-load /api/sessions request keeps the existing timeout/retry policy.""" + fetch_helper = _extract_function(SESSIONS_JS, "_loadSidebarSessionListPayload") + refresh_fn = _extract_function(SESSIONS_JS, "_runRenderSessionListRefresh") + script = f""" +global._SESSION_LIST_BOOT_TIMEOUT_MS = 90000; +global._sessionListHasLoadedOnce = false; +global._renderSessionListGen = 1; +global._profileSwitchListEmbargo = false; +global._pendingSessionListPayload = null; +global._showAllProfiles = false; +global._allProjects = []; +global._contentSearchResults = []; +global.$ = () => ({{ value: '' }}); +global._isSessionListUserInteracting = () => false; +global._schedulePendingSessionListApply = () => {{}}; +global._showSessionListLoadError = error => {{ throw new Error(error && error.message ? error.message : String(error)); }}; +global._applySessionListPayload = () => {{}}; +global._sessionListQueryString = () => '?v=1'; +const calls = []; +global.api = (url, opts) => {{ + calls.push({{endpoint:url.replace('/api/',''), opts}}); + if(url.startsWith('/api/sessions')){{ + return Promise.resolve({{ + sessions:[{{session_id:'s1'}}], + other_profile_count:0, + archived_count:0, + archived_webui_count:0, + archived_cli_count:0, + }}); + }} + return Promise.resolve({{projects:[]}}); +}}; +{fetch_helper} +{refresh_fn} + +(async () => {{ + await _runRenderSessionListRefresh({{}}, 1); + const sessionCall = calls.find(entry => entry.endpoint === 'sessions?') || calls.find(entry => entry.endpoint.startsWith('sessions')); + const projectCall = calls.find(entry => entry.endpoint === 'projects?') || calls.find(entry => entry.endpoint.startsWith('projects')); + console.log(JSON.stringify({{ + sessionOpts: sessionCall ? sessionCall.opts : null, + projectOpts: projectCall ? projectCall.opts : null, + calls, + }})); +}})().catch(error => {{ + console.error(error); + process.exit(1); +}}); +""" + + body = _run_node(script) + session_opts = body["sessionOpts"] or {} + project_opts = body["projectOpts"] or {} + + assert session_opts["timeoutToast"] is False + assert session_opts["timeoutMs"] == 90000 + assert session_opts["retries"] == 1 + assert session_opts["retryTimeouts"] is True + assert session_opts["retryStatuses"] == [502, 503, 504] + assert project_opts == {"timeoutToast": False} From 9cae66a1a0c42b713dc9d5516ae639a3db21e7d2 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 26 Jun 2026 09:55:14 -0400 Subject: [PATCH 2/3] test(#4759): keep sidebar harnesses aligned with helper extraction --- ...t_issue4759_parallel_sidebar_boot_fetch.py | 71 +++++++++++++++++++ .../test_issue4766_sidebar_source_pushdown.py | 6 ++ tests/test_session_sidebar_resilience.py | 13 +++- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/tests/test_issue4759_parallel_sidebar_boot_fetch.py b/tests/test_issue4759_parallel_sidebar_boot_fetch.py index 983ecec5598..7c19c53f94b 100644 --- a/tests/test_issue4759_parallel_sidebar_boot_fetch.py +++ b/tests/test_issue4759_parallel_sidebar_boot_fetch.py @@ -257,3 +257,74 @@ def test_existing_session_request_options_are_preserved(): assert session_opts["retryTimeouts"] is True assert session_opts["retryStatuses"] == [502, 503, 504] assert project_opts == {"timeoutToast": False} + + +@pytest.mark.skipif(NODE is None, reason="node not on PATH") +def test_warm_refresh_parallelizes_projects_fetch_and_keeps_minimal_session_opts(): + """Warm refresh uses minimal session opts, but still starts projects immediately.""" + fetch_helper = _extract_function(SESSIONS_JS, "_loadSidebarSessionListPayload") + script = f""" +const calls = []; +let resolveSessions; +let resolveProjects; +global._showAllProfiles = false; +global._allProjects = []; +global._sessionListHasLoadedOnce = true; +global.api = (url, opts) => {{ + if (url.startsWith('/api/projects')) {{ + calls.push({{ endpoint:'projects', opts: opts || null }}); + return new Promise(resolve => {{ + resolveProjects = resolve; + }}); + }} + if (url.startsWith('/api/sessions')) {{ + calls.push({{ endpoint:'sessions', opts: opts || null }}); + return new Promise(resolve => {{ + resolveSessions = resolve; + }}); + }} + return Promise.reject(new Error('unexpected endpoint ' + url)); +}}; +{fetch_helper} + +(async () => {{ + const run = _loadSidebarSessionListPayload('?v=1', {{ + timeoutToast:false, + timeoutMs:90000, + retries:1, + retryTimeouts:true, + retryStatuses:[502,503,504], + }}); + + await Promise.resolve(); + const orderAtSettleBoundary = calls.map(call => call.endpoint); + const sessionOpts = calls.find(call => call.endpoint === 'sessions').opts; + const projectOpts = calls.find(call => call.endpoint === 'projects').opts; + + resolveSessions({{ + sessions:[{{session_id:'warm-1'}}], + other_profile_count:0, + archived_count:0, + archived_webui_count:0, + archived_cli_count:0, + }}); + resolveProjects({{projects:[{{name:'demo'}}]}}); + const payload = await run; + console.log(JSON.stringify({{ + orderAtSettleBoundary, + sessionOpts, + projectOpts, + payload, + }})); +}})().catch(error => {{ + console.error(error); + process.exit(1); +}}); +""" + + body = _run_node(script) + assert body["orderAtSettleBoundary"] == ["projects", "sessions"] + assert body["sessionOpts"] == {"timeoutToast": False} + assert body["projectOpts"] == {"timeoutToast": False} + assert body["payload"]["sessData"]["sessions"] == [{"session_id": "warm-1"}] + assert body["payload"]["projData"]["projects"] == [{"name": "demo"}] diff --git a/tests/test_issue4766_sidebar_source_pushdown.py b/tests/test_issue4766_sidebar_source_pushdown.py index 182708c5080..f804c46479f 100644 --- a/tests/test_issue4766_sidebar_source_pushdown.py +++ b/tests/test_issue4766_sidebar_source_pushdown.py @@ -529,6 +529,11 @@ def test_scope_mismatch_error_path_respects_sidebar_source(): requested_source_fn = _extract_function(src, "_requestedSessionSidebarSource") exclude_hidden_fn = _extract_function(src, "_sessionListExcludeHiddenEnabled") query_fn = _extract_function(src, "_sessionListQueryString") + fetch_helper_fn = _extract_function(src, "_loadSidebarSessionListPayload").replace( + "function _loadSidebarSessionListPayload", + "async function _loadSidebarSessionListPayload", + 1, + ) refresh_fn = _extract_function(src, "_runRenderSessionListRefresh").replace( "function _runRenderSessionListRefresh", "async function _runRenderSessionListRefresh", @@ -574,6 +579,7 @@ def test_scope_mismatch_error_path_respects_sidebar_source(): {requested_source_fn} {exclude_hidden_fn} {query_fn} + {fetch_helper_fn} {refresh_fn} async function runCase(requestedSource, cachedSource) {{ global._sessionSourceFilter = requestedSource; diff --git a/tests/test_session_sidebar_resilience.py b/tests/test_session_sidebar_resilience.py index 09d18c83154..bba6627aaa0 100644 --- a/tests/test_session_sidebar_resilience.py +++ b/tests/test_session_sidebar_resilience.py @@ -41,11 +41,18 @@ def test_sessions_and_projects_load_independently_so_projects_failure_cannot_bla block_end = src.find("async function _drainRenderSessionListQueue", block_start) assert block_end > block_start block = src[block_start:block_end] + helper_start = src.find("async function _loadSidebarSessionListPayload") + assert helper_start > 0 + helper_end = src.find("async function _drainRenderSessionListQueue", helper_start) + assert helper_end > helper_start + helper = src[helper_start:helper_end] assert "Promise.all" not in block - assert "api('/api/sessions' + sessionListQS" in block - assert "try{\n const projectQS = _showAllProfiles ? '?all_profiles=1' : '';\n projData = await api('/api/projects' + projectQS" in block - assert "console.warn('renderProjectsList'," in block + assert "_loadSidebarSessionListPayload(sessionListQS, sessionRequestOpts)" in block + assert "const projectPromise = (async() => {" in helper + assert "return await api('/api/projects' + projectQS,{timeoutToast:false});" in helper + assert "console.warn('renderProjectsList',projectError);" in helper + assert "const projData = await projectPromise;" in helper assert "_applySessionListPayload(sessData,projData)" in block From c071da4f5d53d423d61100145082a5fb387fd11e Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 26 Jun 2026 10:35:43 -0400 Subject: [PATCH 3/3] test(#4759): guard async harness extraction for sidebar refresh --- .../test_issue4766_sidebar_source_pushdown.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/tests/test_issue4766_sidebar_source_pushdown.py b/tests/test_issue4766_sidebar_source_pushdown.py index f804c46479f..a72087c6233 100644 --- a/tests/test_issue4766_sidebar_source_pushdown.py +++ b/tests/test_issue4766_sidebar_source_pushdown.py @@ -101,6 +101,16 @@ def _extract_function(source_text, function_name): raise AssertionError(f"Could not extract {function_name}") +def _ensure_async(function_source, function_name): + if function_source.startswith("async function "): + return function_source + return function_source.replace( + f"function {function_name}", + f"async function {function_name}", + 1, + ) + + def _run_node(script): proc = subprocess.run([NODE, "-e", script], capture_output=True, text=True, check=True) return json.loads(proc.stdout) @@ -529,15 +539,13 @@ def test_scope_mismatch_error_path_respects_sidebar_source(): requested_source_fn = _extract_function(src, "_requestedSessionSidebarSource") exclude_hidden_fn = _extract_function(src, "_sessionListExcludeHiddenEnabled") query_fn = _extract_function(src, "_sessionListQueryString") - fetch_helper_fn = _extract_function(src, "_loadSidebarSessionListPayload").replace( - "function _loadSidebarSessionListPayload", - "async function _loadSidebarSessionListPayload", - 1, + fetch_helper_fn = _ensure_async( + _extract_function(src, "_loadSidebarSessionListPayload"), + "_loadSidebarSessionListPayload", ) - refresh_fn = _extract_function(src, "_runRenderSessionListRefresh").replace( - "function _runRenderSessionListRefresh", - "async function _runRenderSessionListRefresh", - 1, + refresh_fn = _ensure_async( + _extract_function(src, "_runRenderSessionListRefresh"), + "_runRenderSessionListRefresh", ) script = f""" global.window = {{ _showCliSessions: true }};