Skip to content

Commit 67c02d9

Browse files
authored
Merge pull request #260 from pathsim/fix/native-stdout-protocol
Fix native stdout protocol
2 parents 6bb8e66 + 2cc86e6 commit 67c02d9

File tree

3 files changed

+28
-6
lines changed

3 files changed

+28
-6
lines changed

pathview/app.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,22 @@ def send_message(self, msg: dict) -> None:
6363
self.process.stdin.flush()
6464

6565
def read_line(self) -> dict | None:
66-
"""Read one JSON line from the subprocess stdout."""
67-
line = self.process.stdout.readline()
68-
if not line:
69-
return None
70-
return json.loads(line.strip())
66+
"""Read one JSON line from the subprocess stdout.
67+
68+
Skips blank or non-JSON lines that may leak from native C/C++
69+
libraries writing directly to the OS-level stdout fd.
70+
"""
71+
while True:
72+
line = self.process.stdout.readline()
73+
if not line:
74+
return None
75+
stripped = line.strip()
76+
if not stripped:
77+
continue
78+
try:
79+
return json.loads(stripped)
80+
except json.JSONDecodeError:
81+
continue
7182

7283
def read_line_timeout(self, timeout: float = EXEC_TIMEOUT) -> dict | None:
7384
"""Read one JSON line with a timeout. Returns None on EOF or timeout.

pathview/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def open_browser_when_ready():
4646
if args.debug:
4747
app.run(host=args.host, port=args.port, debug=True, threaded=True)
4848
else:
49+
import logging
50+
logging.getLogger("waitress.queue").setLevel(logging.ERROR)
4951
from waitress import serve
5052
serve(app, host=args.host, port=args.port, threads=4)
5153
except KeyboardInterrupt:

pathview/worker.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
import sys
18+
import os
1819
import json
1920
import subprocess
2021
import threading
@@ -26,7 +27,15 @@
2627
_stdout_lock = threading.Lock()
2728

2829
# Keep a reference to the real stdout pipe — protocol messages go here.
29-
_real_stdout = sys.stdout
30+
# We dup() fd 1 so that _real_stdout survives the fd-level redirect below.
31+
_real_stdout_fd = os.dup(1)
32+
_real_stdout = os.fdopen(_real_stdout_fd, "w")
33+
34+
# Redirect OS-level fd 1 to devnull so that C/C++ libraries (e.g. jsbsim)
35+
# writing directly to stdout via printf/cout cannot corrupt the JSON protocol.
36+
_devnull_fd = os.open(os.devnull, os.O_WRONLY)
37+
os.dup2(_devnull_fd, 1)
38+
os.close(_devnull_fd)
3039

3140
# Worker state
3241
_namespace = {}

0 commit comments

Comments
 (0)