perf: optimize session loading and desktop startup (#2545)

* perf: optimize session loading and startup

* fix(chat): stabilize history prepend virtualization

* perf: unblock first session open from startup network contention

Opening the first session after app start waited seconds for its message
fetch. Three independent contributors, each measured via CDP network
capture and Chromium net-log against the packaged desktop app:

- The active-session watchdog fired an uncapped per-directory status poll
  and child-session discovery burst at startup, and other subsystems
  (git checks, global session pages, command/skill discovery) fanned out
  alongside it, saturating the browser's ~6 HTTP/1.1 sockets per origin.
  Add a shared background-network gate (concurrency 3) and route the
  watchdog, poll-shaped git reads (also priority: low), global session
  pages, command/skill loads, and the background update check through it.

- The packaged renderer is cross-origin to the loopback backend, so every
  API call needs a CORS preflight; a few slow OpenCode-proxied requests
  held the whole pool while preflights and interactive traffic queued
  behind them. Lift Chromium's per-host connection cap for loopback via
  ignore-connections-limit in the Electron shell.

- OpenCode initializes each directory lazily on its first request, so the
  first click paid that cost interactively. Warm the last-used directory
  and the three most recently opened projects right after OpenCode
  readiness, sequentially and best-effort, overlapping UI startup.

Validation: new background-network tests, lifecycle warmup test, focused
store/sync tests, UI type-check and lint, dead-code report, node --check
plus electron type-check/lint, and CDP first-open measurements on the
packaged app (message fetch socket queue 5.4s -> 0.03s).

* fix(ui): keep interactive git reads out of background queue

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
𝖎𝖚𝖑𝖎𝖎𝖆
2026-07-31 12:51:15 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 09f0c64839
commit aae889b904
41 changed files with 1690 additions and 203 deletions
+70 -47
View File
@@ -61,6 +61,11 @@ type FetchedPage = {
complete: boolean
}
type LoadPerformanceDetails = {
retryCount: number
recordCount: number
}
type LoaderConfiguration = {
sdk: OpencodeClient
runtimeKey: string
@@ -201,15 +206,8 @@ export class SessionMessageLoader {
}
if (options?.force) this.bumpGeneration(entry)
const kind: SessionMessageLoadKind = options?.reason === "prefetch" ? "prefetch" : "initial"
return this.startLoad(normalized, entry, store, kind, async (isCurrent) => {
await this.loadInitial(normalized, entry, store, isCurrent)
if (!isMobileSurfaceRuntime() && isCurrent()) {
queueMicrotask(() => {
if (isCurrent() && entry.snapshot.cursor && !entry.snapshot.complete) {
void this.loadOlder(normalized)
}
})
}
return this.startLoad(normalized, entry, store, kind, async (isCurrent, performance) => {
await this.loadInitial(normalized, entry, store, isCurrent, performance)
})
}
@@ -225,8 +223,8 @@ export class SessionMessageLoader {
if (entry.snapshot.complete || !entry.snapshot.cursor) return Promise.resolve()
const store = this.childStores.ensureChild(normalized.directory, { bootstrap: false })
const cursor = entry.snapshot.cursor
return this.startLoad(normalized, entry, store, "older", async (isCurrent) => {
const page = await this.fetchPage(normalized, HISTORY_MESSAGE_PAGE_SIZE, cursor)
return this.startLoad(normalized, entry, store, "older", async (isCurrent, performance) => {
const page = await this.fetchPage(normalized, HISTORY_MESSAGE_PAGE_SIZE, cursor, "older", performance)
if (!isCurrent()) return
const committed = this.commitPage(normalized, entry, store, page, "prepend", isCurrent)
if (!committed || !isCurrent()) return
@@ -279,11 +277,11 @@ export class SessionMessageLoader {
}
const store = this.childStores.ensureChild(normalized.directory, { bootstrap: false })
this.bumpGeneration(entry)
return this.startLoad(normalized, entry, store, "refresh", async (isCurrent) => {
return this.startLoad(normalized, entry, store, "refresh", async (isCurrent, performance) => {
const previousCoverage = entry.snapshot.resolved
? { cursor: entry.snapshot.cursor, complete: entry.snapshot.complete }
: null
const page = await this.fetchPage(normalized, Math.max(1, limit))
const page = await this.fetchPage(normalized, Math.max(1, limit), undefined, "refresh", performance)
if (!isCurrent()) return
const committed = this.commitPage(normalized, entry, store, page, "merge", isCurrent)
if (!committed || !isCurrent()) return
@@ -455,15 +453,12 @@ export class SessionMessageLoader {
entry: LoaderEntry,
store: { getState: () => DirectoryStore; setState: DirectoryStoreSetter },
kind: SessionMessageLoadKind,
run: (isCurrent: () => boolean) => Promise<void>,
run: (isCurrent: () => boolean, performance: LoadPerformanceDetails) => Promise<void>,
): Promise<void> {
const generation = entry.snapshot.generation
const sdkEpoch = this.sdkEpoch
const finishPerformanceEvent = startSessionLoadPerformanceEvent({
operation: kind === "prefetch" ? "session-prefetch" : `session-messages.${kind}`,
runtimeKey: this.runtimeKey,
directory: target.directory,
sessionID: target.sessionID,
caller: kind,
})
const isCurrent = () => (
@@ -472,21 +467,22 @@ export class SessionMessageLoader {
&& entry.snapshot.generation === generation
&& this.childStores.getChild(target.directory) === store
)
const performance = { retryCount: 0, recordCount: 0 }
this.patchEntry(entry, { status: "loading", loadingKind: kind, error: null })
let loadPromise: Promise<void>
try {
loadPromise = run(isCurrent)
loadPromise = run(isCurrent, performance)
} catch (error) {
loadPromise = Promise.reject(error)
}
const promise = loadPromise
.then(() => finishPerformanceEvent(isCurrent() ? "complete" : "stale"))
.then(() => finishPerformanceEvent(isCurrent() ? "complete" : "stale", performance))
.catch((error: unknown) => {
if (!isCurrent()) {
finishPerformanceEvent("stale")
finishPerformanceEvent("stale", performance)
return
}
finishPerformanceEvent("error")
finishPerformanceEvent("error", performance)
this.patchEntry(entry, {
status: "error",
loadingKind: null,
@@ -505,10 +501,11 @@ export class SessionMessageLoader {
entry: LoaderEntry,
store: { getState: () => DirectoryStore; setState: DirectoryStoreSetter },
isCurrent: () => boolean,
performance?: LoadPerformanceDetails,
): Promise<void> {
const storeMessageCount = store.getState().message[target.sessionID]?.length ?? 0
const firstLimit = Math.max(entry.snapshot.limit, storeMessageCount, getInitialPageSize())
const firstPage = await this.fetchPage(target, firstLimit)
const firstPage = await this.fetchPage(target, firstLimit, undefined, "initial-page", performance)
if (!isCurrent()) return
const deferFirstCommit = !firstPage.complete && !hasUserMessage(firstPage.session)
let committed = deferFirstCommit
@@ -519,7 +516,7 @@ export class SessionMessageLoader {
if (deferFirstCommit) {
for (const limit of getInitialExpansionLimits()) {
if (limit <= firstLimit || !isCurrent()) continue
const expandedPage = await this.fetchPage(target, limit)
const expandedPage = await this.fetchPage(target, limit, undefined, "initial-page", performance)
if (!isCurrent()) return
acceptedPage = expandedPage
const boundaryFound = hasUserMessage(expandedPage.session)
@@ -547,32 +544,58 @@ export class SessionMessageLoader {
this.persistCoverage(target, entry.snapshot)
}
private async fetchPage(target: SessionMessageTarget, limit: number, before?: string): Promise<FetchedPage> {
const result = await retry(async () => {
const response = await this.sdk.session.messages({
sessionID: target.sessionID,
directory: target.directory,
limit,
before,
})
assertSdkSuccess(response, "session.messages")
if (!Array.isArray(response.data)) {
const error = new Error("session.messages returned no data") as Error & { status?: number }
error.status = 503
throw error
}
return { data: response.data, response: response.response }
private async fetchPage(
target: SessionMessageTarget,
limit: number,
before?: string,
caller: "initial-page" | "older" | "refresh" = "initial-page",
performance?: LoadPerformanceDetails,
): Promise<FetchedPage> {
const finishPagePerformance = startSessionLoadPerformanceEvent({
operation: "session-messages.page",
caller,
requestLimit: limit,
cursorPresent: before !== undefined,
})
const records = result.data.filter((record: { info?: { id?: string } }) => Boolean(record?.info?.id))
const session = records
.map((record: { info: Message }) => stripMessageDiffSnapshots(record.info))
.sort((left: Message, right: Message) => cmp(left.id, right.id))
const partsByMessageID = new Map<string, Part[]>()
for (const record of records as Array<{ info: { id: string }; parts?: Part[] }>) {
partsByMessageID.set(record.info.id, sortParts(record.parts ?? []))
let attempts = 0
let recordCount = 0
try {
const result = await retry(async () => {
attempts += 1
const response = await this.sdk.session.messages({
sessionID: target.sessionID,
directory: target.directory,
limit,
before,
})
assertSdkSuccess(response, "session.messages")
const data = response.data
if (!Array.isArray(data)) {
const error = new Error("session.messages returned no data") as Error & { status?: number }
error.status = 503
throw error
}
return { data, response: response.response }
})
const records = result.data.filter((record: { info?: { id?: string } }) => Boolean(record?.info?.id))
recordCount = records.length
if (performance) performance.recordCount += recordCount
const session = records
.map((record: { info: Message }) => stripMessageDiffSnapshots(record.info))
.sort((left: Message, right: Message) => cmp(left.id, right.id))
const partsByMessageID = new Map<string, Part[]>()
for (const record of records as Array<{ info: { id: string }; parts?: Part[] }>) {
partsByMessageID.set(record.info.id, sortParts(record.parts ?? []))
}
const cursor = result.response?.headers?.get?.("x-next-cursor") ?? undefined
finishPagePerformance("complete", { retryCount: Math.max(0, attempts - 1), recordCount })
return { session, partsByMessageID, cursor, complete: !cursor }
} catch (error) {
finishPagePerformance("error", { retryCount: Math.max(0, attempts - 1), recordCount })
throw error
} finally {
if (performance) performance.retryCount += Math.max(0, attempts - 1)
}
const cursor = result.response?.headers?.get?.("x-next-cursor") ?? undefined
return { session, partsByMessageID, cursor, complete: !cursor }
}
private commitPage(