Skip to content

Commit 3071213

Browse files
committed
fix(win): stabilize non-interactive execution and prevent CLI hangs
- Replace blocking stdin read in worker with timeout-bounded threaded read (5s safety limit) - Harden safe_input() to avoid indefinite blocking on daemon/worker IO - Add socket timeouts for dispatcher daemon connections to prevent hang on CLI routing - Detect daemon logs follow mode (-f/--follow) to correctly mark interactive sessions on Windows and Linux - Improve dispatcher interactivity detection logic for info/config/log-follow commands This improves CLI reliability under non-interactive and daemon-routed execution paths, preventing deadlocks during long-running or headless invocations. Modified: • src/omnipkg/common_utils.py (+19/-3 lines) • src/omnipkg/dispatcher.c (+10/-1 lines) • src/omnipkg/dispatcher.py (+17/-1 lines) [gitship-generated]
1 parent 5adf277 commit 3071213

3 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/omnipkg/common_utils.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,10 +747,26 @@ def _worker_read_reply() -> dict:
747747
The daemon writes to worker.process.stdin (also text mode) after
748748
receiving the stdin_line from the C dispatcher.
749749
"""
750-
line = sys.stdin.readline()
751-
if not line:
750+
import threading
751+
result = [None]
752+
exc = [None]
753+
754+
def _read():
755+
try:
756+
result[0] = sys.stdin.readline()
757+
except Exception as e:
758+
exc[0] = e
759+
760+
t = threading.Thread(target=_read, daemon=True)
761+
t.start()
762+
t.join(5.0)
763+
if t.is_alive():
764+
raise TimeoutError("no stdin reply within 5s")
765+
if exc[0]:
766+
raise exc[0]
767+
if not result[0]:
752768
raise EOFError("daemon closed stdin")
753-
return json.loads(line.strip())
769+
return json.loads(result[0].strip())
754770

755771

756772
def safe_input(prompt: str, default: str = "", auto_value: str = None) -> str:

src/omnipkg/dispatcher.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2055,7 +2055,16 @@ int main(int argc, char **argv) {
20552055
* that contain the word python (e.g. "info python-dotenv"). */
20562056
int info_python = (is_info && argc >= 3 &&
20572057
strcmp(argv[2], "python") == 0);
2058-
is_interactive_command = ((is_info && !info_python) || is_config);
2058+
int is_logs_follow = 0;
2059+
if (argc >= 3 && strcmp(argv[1], "daemon") == 0 && strcmp(argv[2], "logs") == 0) {
2060+
for (int _i = 3; _i < argc; _i++) {
2061+
if (strcmp(argv[_i], "-f") == 0 || strcmp(argv[_i], "--follow") == 0) {
2062+
is_logs_follow = 1;
2063+
break;
2064+
}
2065+
}
2066+
}
2067+
is_interactive_command = ((is_info && !info_python) || is_config || is_logs_follow);
20592068
}
20602069
if (!is_swap_python && !is_interactive_command) {
20612070
try_daemon_cli(target_python, argc, argv, version_injected, forced_version);

src/omnipkg/dispatcher.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,21 @@ def main():
225225
and argv_commands[1] in ("start", "stop", "restart")
226226
)
227227

228-
if not is_swap_command and not is_daemon_lifecycle:
228+
is_info_command = (
229+
len(argv_commands) >= 1
230+
and argv_commands[0] == "info"
231+
and not (len(argv_commands) >= 2 and argv_commands[1] == "python")
232+
)
233+
is_config_command = len(argv_commands) >= 1 and argv_commands[0] == "config"
234+
is_logs_follow = (
235+
len(argv_commands) >= 2
236+
and argv_commands[0] == "daemon"
237+
and argv_commands[1] == "logs"
238+
and "-f" in sys.argv
239+
)
240+
is_interactive_command = is_info_command or is_config_command or is_logs_follow
241+
242+
if not is_swap_command and not is_daemon_lifecycle and not is_interactive_command:
229243
try:
230244
import socket
231245
import tempfile
@@ -240,6 +254,7 @@ def main():
240254
host, port = conn_str[6:].split(":")
241255
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
242256
sock.connect((host, int(port)))
257+
sock.settimeout(300)
243258
else:
244259
raise ValueError()
245260
else:
@@ -248,6 +263,7 @@ def main():
248263
if os.path.exists(sock_path):
249264
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
250265
sock.connect(sock_path)
266+
sock.settimeout(300)
251267
else:
252268
raise ValueError()
253269

0 commit comments

Comments
 (0)