This commit is contained in:
2026-02-04 20:51:54 -08:00
parent b714d477a6
commit d806ebad85
9 changed files with 257 additions and 63 deletions

View File

@@ -872,13 +872,20 @@ class MPVIPCClient:
try:
if self.is_windows:
# BinaryIO pipe from open('\\\\.\\pipe\\...')
pipe = cast(BinaryIO, self.sock)
pipe.write(payload.encode("utf-8"))
pipe.flush()
try:
pipe.write(payload.encode("utf-8"))
pipe.flush()
except OSError as e:
# Windows Errno 22 (EINVAL) often means the pipe handle is now invalid/closed
if getattr(e, "errno", 0) == 22:
raise BrokenPipeError(str(e))
raise
else:
sock_obj = cast(socket.socket, self.sock)
sock_obj.sendall(payload.encode("utf-8"))
except (OSError, IOError, BrokenPipeError) as exc:
except (OSError, IOError, BrokenPipeError, ConnectionResetError) as exc:
# Pipe became invalid (disconnected, corrupted, etc.).
# Disconnect and attempt one reconnection.
if not self.silent:
@@ -889,12 +896,17 @@ class MPVIPCClient:
try:
if self.is_windows:
pipe = cast(BinaryIO, self.sock)
pipe.write(payload.encode("utf-8"))
pipe.flush()
try:
pipe.write(payload.encode("utf-8"))
pipe.flush()
except OSError as e:
if getattr(e, "errno", 0) == 22:
raise BrokenPipeError(str(e))
raise
else:
sock_obj = cast(socket.socket, self.sock)
sock_obj.sendall(payload.encode("utf-8"))
except (OSError, IOError, BrokenPipeError) as retry_exc:
except (OSError, IOError, BrokenPipeError, ConnectionResetError) as retry_exc:
self.disconnect()
raise MPVIPCError(f"Pipe write failed after reconnect: {retry_exc}") from retry_exc
else:
@@ -912,7 +924,7 @@ class MPVIPCClient:
try:
pipe = cast(BinaryIO, self.sock)
return pipe.readline()
except (OSError, IOError, BrokenPipeError) as exc:
except (OSError, IOError, BrokenPipeError, ConnectionResetError) as exc:
# Pipe error; try to reconnect once
if not self.silent:
debug(f"Pipe readline failed: {exc}")

View File

@@ -28,6 +28,7 @@ import os
import sys
import tempfile
import time
import threading
import logging
import re
import hashlib
@@ -134,8 +135,16 @@ def _run_pipeline(pipeline_text: str, *, seeds: Any = None) -> Dict[str, Any]:
"rows": rows_payload
}
start_time = time.time()
runner = PipelineRunner()
result = runner.run_pipeline(pipeline_text, seeds=seeds)
duration = time.time() - start_time
try:
_append_helper_log(
f"[pipeline] run_pipeline completed in {duration:.2f}s pipeline={pipeline_text[:64]}"
)
except Exception:
pass
table_payload = None
try:
@@ -152,6 +161,31 @@ def _run_pipeline(pipeline_text: str, *, seeds: Any = None) -> Dict[str, Any]:
}
def _run_pipeline_background(pipeline_text: str, *, seeds: Any, req_id: str) -> None:
def _target() -> None:
try:
result = _run_pipeline(pipeline_text, seeds=seeds)
status = "success" if result.get("success") else "failed"
_append_helper_log(
f"[pipeline async {req_id}] {status} error={result.get('error')}"
)
except Exception as exc: # pragma: no cover - best-effort logging
_append_helper_log(
f"[pipeline async {req_id}] exception: {type(exc).__name__}: {exc}"
)
thread = threading.Thread(
target=_target,
name=f"pipeline-async-{req_id}",
daemon=True,
)
thread.start()
def _is_load_url_pipeline(pipeline_text: str) -> bool:
return str(pipeline_text or "").lstrip().lower().startswith(".mpv -url")
def _run_op(op: str, data: Any) -> Dict[str, Any]:
"""Run a helper-only operation.
@@ -1030,13 +1064,29 @@ def main(argv: Optional[list[str]] = None) -> int:
except Exception:
pass
async_dispatch = False
try:
if op:
run = _run_op(op, data)
else:
if not pipeline_text:
continue
run = _run_pipeline(pipeline_text, seeds=seeds)
if _is_load_url_pipeline(pipeline_text):
async_dispatch = True
run = {
"success": True,
"stdout": "",
"stderr": "",
"error": "",
"table": None,
}
_run_pipeline_background(
pipeline_text,
seeds=seeds,
req_id=req_id,
)
else:
run = _run_pipeline(pipeline_text, seeds=seeds)
resp = {
"id": req_id,
@@ -1050,6 +1100,8 @@ def main(argv: Optional[list[str]] = None) -> int:
}
if "choices" in run:
resp["choices"] = run.get("choices")
if async_dispatch:
resp["info"] = "queued asynchronously"
except Exception as exc:
resp = {
"id": req_id,

View File

@@ -1,7 +1,7 @@
# uosc provides seeking & volume indicators (via flash-timeline and flash-volume commands)
# if you decide to use them, you don't need osd-bar
osd-bar=no
ytdl=yes
# uosc will draw its own window controls and border if you disable window border
border=no