mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-22 06:37:11 +08:00
docs: add 2.1.4 release notes
This commit is contained in:
@@ -9,6 +9,7 @@ period of inactivity to free resources.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import queue
|
||||
import threading
|
||||
@@ -215,6 +216,14 @@ _SNAPSHOT_JS = """
|
||||
str(list(_INTERACTIVE_TAGS)),
|
||||
)
|
||||
|
||||
# Returning the snapshot as ONE JSON string instead of a nested object is a big
|
||||
# win in the frozen desktop build: Playwright serializes a nested return value
|
||||
# node-by-node over many driver<->python protocol round trips, and each round
|
||||
# trip carries fixed overhead that is dramatically amplified in the frozen
|
||||
# bundle (a ~300-node tree can take 20s+). JSON.stringify in-page collapses it
|
||||
# to a single string transfer; Python then json.loads it. Behaviour identical.
|
||||
_SNAPSHOT_JS_STR = "() => JSON.stringify((%s)())" % _SNAPSHOT_JS.strip()
|
||||
|
||||
|
||||
_BROWSER_DEAD_HINTS = (
|
||||
"has been closed",
|
||||
@@ -846,7 +855,11 @@ class BrowserService:
|
||||
def _do_snapshot(self, selector: Optional[str] = None) -> str:
|
||||
page = self._page
|
||||
try:
|
||||
result = page.evaluate(_SNAPSHOT_JS)
|
||||
# Return a single JSON string (not a nested object) to avoid
|
||||
# Playwright's per-node serialization round trips, which are slow
|
||||
# in the frozen build. See _SNAPSHOT_JS_STR.
|
||||
raw = page.evaluate(_SNAPSHOT_JS_STR)
|
||||
result = json.loads(raw) if isinstance(raw, str) else raw
|
||||
except Exception as e:
|
||||
return f"[Snapshot error: {e}]"
|
||||
|
||||
|
||||
@@ -51,6 +51,28 @@ class ChromeLauncher:
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def _clear_stale_singleton_locks(self):
|
||||
"""Remove leftover Chrome Singleton* locks from a crashed/killed run.
|
||||
|
||||
Chrome allows only one instance per user_data_dir and enforces it with
|
||||
SingletonLock / SingletonSocket / SingletonCookie. On a clean exit these
|
||||
are removed, but a crash or force-quit leaves them behind — the next
|
||||
spawn then hands off to the (dead) "existing" instance and exits without
|
||||
opening the debug port, so CDP never comes up (a permanent, non
|
||||
self-healing failure). This profile is private to us, so clearing stale
|
||||
locks before launch is safe: if our own browser were truly alive, the
|
||||
service would still be connected and we wouldn't be re-launching.
|
||||
"""
|
||||
for name in ("SingletonLock", "SingletonSocket", "SingletonCookie"):
|
||||
p = os.path.join(self._user_data_dir, name)
|
||||
try:
|
||||
# These are symlinks; use lexists so a dangling link is caught.
|
||||
if os.path.lexists(p):
|
||||
os.remove(p)
|
||||
logger.info(f"[Browser] cleared stale Chrome lock: {name}")
|
||||
except OSError as e:
|
||||
logger.debug(f"[Browser] could not remove {name}: {e}")
|
||||
|
||||
def launch(self, ready_timeout: float = 25.0) -> str:
|
||||
"""Spawn Chrome and block until its CDP endpoint answers.
|
||||
|
||||
@@ -58,6 +80,7 @@ class ChromeLauncher:
|
||||
comes up (the child process is killed in that case).
|
||||
"""
|
||||
os.makedirs(self._user_data_dir, exist_ok=True)
|
||||
self._clear_stale_singleton_locks()
|
||||
self._port = self._free_port()
|
||||
|
||||
args = [
|
||||
@@ -99,9 +122,11 @@ class ChromeLauncher:
|
||||
)
|
||||
|
||||
if not self._wait_ready(ready_timeout):
|
||||
# Capture the port before close() clears it, so the error is useful.
|
||||
port = self._port
|
||||
self.close()
|
||||
raise RuntimeError(
|
||||
f"Chrome did not expose a CDP endpoint on port {self._port} "
|
||||
f"Chrome did not expose a CDP endpoint on port {port} "
|
||||
f"within {ready_timeout:.0f}s"
|
||||
)
|
||||
return self.endpoint
|
||||
|
||||
Reference in New Issue
Block a user