fix(connection,adb_client): commit the F-NEW1..3 wiring lost in the amend cycle — no-idle-timeout transport budget with in-code rationale, config accessor consumption, wrapped-line buffer

This commit is contained in:
James Mackay committed 2026-10-09 06:20:46 +01:00
1 parent 9b3142874f
commit e51545262e
2 files changed
+42 -11

No files matched your search

+28 -11
View File
@@ -619,22 +619,27 @@ class ShieldConnectionManager:
from adb_shell.adb_device_async import AdbDeviceTcpAsync from adb_shell.adb_device_async import AdbDeviceTcpAsync
from adb_shell.auth.sign_pythonrsa import PythonRSASigner from adb_shell.auth.sign_pythonrsa import PythonRSASigner
config = poller.config # public accessor (t27 F-NEW3)
async def _lines(): async def _lines():
key_path = getattr(poller, "_key_path", None) or getattr(
poller, "key_path", None
)
host = getattr(poller, "_host", None) or getattr(poller, "host", None)
port = getattr(poller, "_port", None) or getattr(poller, "port", None)
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
signer = await loop.run_in_executor( signer = await loop.run_in_executor(
None, PythonRSASigner.FromRSAKeyPath, key_path None, PythonRSASigner.FromRSAKeyPath, config["key_path"]
) )
device = AdbDeviceTcpAsync(host, port, default_transport_timeout_s=10.0) device = AdbDeviceTcpAsync(config["host"], config["port"])
connected = await device.connect(rsa_keys=[signer], auth_timeout_s=10.0) connected = await device.connect(rsa_keys=[signer], auth_timeout_s=10.0)
if not connected: if not connected:
raise RuntimeError("dedicated logcat connection failed") raise RuntimeError("dedicated logcat connection failed")
# F-NEW1 (HIGH): quiet logcat is NORMAL on a bored device — the
# TCP transport timeout must NOT be the idle detector or every
# ≥10 s gap becomes a TcpTimeoutException streak that ends in
# the session auto-disable in the field. transport_timeout_s=None
# makes bulk_read block indefinitely; real loss (dead socket) is
# the ladder's job (reconnect with backoff), and async_stop is
# the cleanup path.
async for line in device.streaming_shell( async for line in device.streaming_shell(
"logcat -v brief ActivityTaskManager:*" "logcat -v brief ActivityTaskManager:*",
transport_timeout_s=None,
): ):
yield line yield line
@@ -660,11 +665,23 @@ class ShieldConnectionManager:
async def _logcat_pass(self) -> None: async def _logcat_pass(self) -> None:
"""One full consumption of the logcat line-stream (P5-3). """One full consumption of the logcat line-stream (P5-3).
Returns when the stream ends (the dedicated connection dropped); Yields lines are buffered 3-deep (t27 F-NEW2) before parsing so a
errors propagate to the caller's ladder. START record whose cmp= continuation spans stream lines is not
missed. Returns when the stream ends (the dedicated connection
dropped); errors propagate to the caller's ladder.
""" """
buffer: list[str] = []
async for line in self._open_logcat_stream(): async for line in self._open_logcat_stream():
package = foreground_from_logcat([str(line)]) buffer.append(str(line))
if len(buffer) < 3: # small wrap window (t27 F-NEW2)
continue
package = foreground_from_logcat(buffer)
if package:
self._on_logcat_app_start(package)
buffer = buffer[-2:] # keep the tail for a pending continuation
# flush the tail buffer once the stream ends
if buffer:
package = foreground_from_logcat(buffer)
if package: if package:
self._on_logcat_app_start(package) self._on_logcat_app_start(package)
raise RuntimeError("stream ended (device closed)") raise RuntimeError("stream ended (device closed)")
@@ -80,6 +80,20 @@ class AdbClientBase:
def connected(self) -> bool: def connected(self) -> bool:
return self._connected return self._connected
@property
def config(self) -> dict:
"""Public connection config: {host, port, key_path} (t27 F-NEW3).
Replaces the private-attr reach-ins for secondary streams — callers
(the dedicated logcat stream) share the signer material without
touching the poller's socket or its private attributes.
"""
return {
"host": self._host,
"port": self._port,
"key_path": self._key_path,
}
@property @property
def last_info(self) -> MediaSessionInfo | None: def last_info(self) -> MediaSessionInfo | None:
return self._last_info return self._last_info