fix(chatgpt): use pure sleeps and cheap generation checks in polling loops#2099
Open
Astro-Han wants to merge 4 commits into
Open
fix(chatgpt): use pure sleeps and cheap generation checks in polling loops#2099Astro-Han wants to merge 4 commits into
Astro-Han wants to merge 4 commits into
Conversation
…loops (jackwener#2095) During a long `chatgpt ask` (10-20 min answers) the chatgpt.com renderer hit ~700% CPU and >4GB RSS. Root causes, all in the poll loops that run for the whole generation: - `page.wait(n>=1)` does not sleep client-side; it injects a whole-body MutationObserver (DOM-stable probe) that never goes quiet while the answer streams, so it re-arms and fires on every mutation for the full interval. Add `page.sleep(seconds)` (bare setTimeout, no page evaluation) to BasePage and IPage, and switch the poll-interval waits to it: waitForChatGPTResponse, waitForChatGPTDetailRows, waitForChatGPTDeepResearchResult, waitForChatGPTImages, waitForChatGPTUploadPreview, and the ask pre-send settle loop. One-shot post-navigation settle waits keep `page.wait` for its DOM-stable early return. - `isGenerating` read `document.body.innerText` every poll, forcing a full-page reflow and a conversation-sized string allocation. Rewrite it to cheap signals: stop-button test id, control aria-labels, and a `textContent` (no reflow) scan scoped to the composer + last turn. - `getVisibleMessages` read both innerHTML and innerText per turn. Add a `textOnly` option that skips innerHTML and use it from the response poll loop, whose output is text-only; read/detail markdown paths are unchanged.
15 tasks
…an (jackwener#2095) The scoped text fallback only looked at article conversation turns, but CONVERSATION_MESSAGE_SELECTOR supports bare [data-message-author-role] nodes too. On that DOM shape a plain-text Thinking pill (no stop button, no aria-label) would read as idle and waitForChatGPTResponse could return a truncated answer. Prefer the article turn (wider container), fall back to the last role-attribute node when articles are absent. Addresses the P2 from external review of PR jackwener#2099.
…ting (jackwener#2095) Scanning whole-scope textContent flags any finished answer that merely mentions "Thinking" / "正在思考" (prose or backticked code spans) as still generating — e.g. a conversation reviewing this very code — permanently blocking follow-up sends. Count only short leaf elements outside .markdown/pre/code as status pills. Verified against a live conversation whose messages discuss isGenerating: detail reported Generating=true before, false after; a real streaming pill still matches (leaf, short, outside rendered content).
…te (jackwener#2095) 'Thinking' is a supported idle model label (CHATGPT_MODEL_TARGETS.advanced) rendered as a composer-form button, so both the page-wide aria-label match and the composer-scope leaf scan flagged an idle conversation with that model selected as generating forever, blocking sends. Drop bare 'Thinking' from aria-label matching (the stop button covers English streaming states) and only count it inside the last conversation turn. Addresses the round-2 P2 from external review of PR jackwener#2099.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Part 1 of 2 for the renderer-CPU problem in long-running
chatgpt ask(part 2 will add session busy arbitration).During a 10-20 min ChatGPT response, the chatgpt.com renderer climbs to ~700% CPU and >4GB RSS. Two mechanisms in the polling loops are responsible:
page.wait(3)between polls is not a sleep: its numeric path injectswaitForDomStableJs, which installs aMutationObserverondocument.bodywith{childList, subtree, attributes}and only resolves after 500ms of quiet. A streaming response never goes quiet, so every poll runs a whole-body observer for the full 3s cap, firing on every mutation, re-armed continuously for the entire generation.isGeneratingreaddocument.body.innerTextevery poll, forcing a synchronous full-page reflow and allocating a conversation-sized string in the renderer heap, cost growing with response length.Changes:
page.sleep(seconds): a pure client-sidesetTimeoutwith no page evaluation.wait(n)semantics are unchanged for post-navigation settles that benefit from DOM-stable early return.waitForChatGPTResponse,waitForChatGPTDetailRows,waitForChatGPTDeepResearchResult,waitForChatGPTImagespoll interval, upload preview poll, ask pre-send settle) topage.sleep. One-shot post-gotosettles keeppage.wait.isGeneratingwithoutbody.innerText: stop-button test id, control aria-labels, and atextContent(no reflow) scan scoped to the last conversation turn + composer container.getVisibleMessagesgains{ textOnly }to skip per-turninnerHTMLduring response polling;read/detailmarkdown paths are untouched and their output is byte-identical.ask.js waitForConversationUrlandsendChatGPTMessageare deliberately untouched to avoid conflicting with #2094.Related issue: #2095
Type of Change
Checklist
Documentation (if adding/modifying an adapter)
docs/adapters/(if new adapter) — N/A, no new adapterdocs/adapters/index.mdtable (if new adapter) — N/Adocs/.vitepress/config.mts(if new adapter) — N/AREADME.md/README.zh-CN.mdwhen command discoverability changed — N/A, no command surface changeCliErrorsubclasses instead of rawError(unchanged; existingCommandExecutionError/TimeoutErrorpaths kept)Screenshots / Output
New tests:
BasePage.sleepnever evaluates page script; the chatgpt response-wait loop polls viasleepand never hits the DOM-stable numericwaitpath.Live Validation (added after review rounds)
Dogfooded during real ChatGPT sessions while this PR was under review; two failures of the 1.8.6
isGeneratingwere reproduced and fixed here:chatgpt ask --project ...on an empty project page failed with "conversation is still generating" — the whole-bodyinnerTextscan matched thinking-status text inside the conversation-list previews. This PR's scoped scan is immune; the same command succeeded with this branch.Thinking/正在思考was reported as generating, permanently blocking follow-up sends. Red-green verified live:Generating2→0 on the same conversation before/after the fix.Thinkingmodel selected, the composer's model button (aria-label="Thinking") matched the aria scan on an idle conversation. Scan patterns are now per-scope: composer scope no longer treats bareThinkingas a generating signal (English streaming state is covered bystop-button).External review: 3 rounds of GPT Pro review on this PR; final verdict after 902082d: no remaining substantive findings.