diff --git a/pathview/app.py b/pathview/app.py index 7a2fbda..9a757f4 100644 --- a/pathview/app.py +++ b/pathview/app.py @@ -63,11 +63,22 @@ def send_message(self, msg: dict) -> None: self.process.stdin.flush() def read_line(self) -> dict | None: - """Read one JSON line from the subprocess stdout.""" - line = self.process.stdout.readline() - if not line: - return None - return json.loads(line.strip()) + """Read one JSON line from the subprocess stdout. + + Skips blank or non-JSON lines that may leak from native C/C++ + libraries writing directly to the OS-level stdout fd. + """ + while True: + line = self.process.stdout.readline() + if not line: + return None + stripped = line.strip() + if not stripped: + continue + try: + return json.loads(stripped) + except json.JSONDecodeError: + continue def read_line_timeout(self, timeout: float = EXEC_TIMEOUT) -> dict | None: """Read one JSON line with a timeout. Returns None on EOF or timeout. diff --git a/pathview/cli.py b/pathview/cli.py index 03a4ee3..89e6753 100644 --- a/pathview/cli.py +++ b/pathview/cli.py @@ -46,6 +46,8 @@ def open_browser_when_ready(): if args.debug: app.run(host=args.host, port=args.port, debug=True, threaded=True) else: + import logging + logging.getLogger("waitress.queue").setLevel(logging.ERROR) from waitress import serve serve(app, host=args.host, port=args.port, threads=4) except KeyboardInterrupt: diff --git a/pathview/worker.py b/pathview/worker.py index ec9fbf8..9b8e71d 100644 --- a/pathview/worker.py +++ b/pathview/worker.py @@ -15,6 +15,7 @@ """ import sys +import os import json import subprocess import threading @@ -26,7 +27,15 @@ _stdout_lock = threading.Lock() # Keep a reference to the real stdout pipe — protocol messages go here. -_real_stdout = sys.stdout +# We dup() fd 1 so that _real_stdout survives the fd-level redirect below. +_real_stdout_fd = os.dup(1) +_real_stdout = os.fdopen(_real_stdout_fd, "w") + +# Redirect OS-level fd 1 to devnull so that C/C++ libraries (e.g. jsbsim) +# writing directly to stdout via printf/cout cannot corrupt the JSON protocol. +_devnull_fd = os.open(os.devnull, os.O_WRONLY) +os.dup2(_devnull_fd, 1) +os.close(_devnull_fd) # Worker state _namespace = {}