From 103263c358286ec04f5c36a17149e7ee4bf43968 Mon Sep 17 00:00:00 2001 From: Sarvesh Mishra <191090264+Sarvesh-Mishra1981@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:21:28 +0530 Subject: [PATCH 1/2] Fixed Issue 359 --- backend/staticfiles/synthesis/main.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/backend/staticfiles/synthesis/main.py b/backend/staticfiles/synthesis/main.py index 47e75263..af338c37 100644 --- a/backend/staticfiles/synthesis/main.py +++ b/backend/staticfiles/synthesis/main.py @@ -50,6 +50,17 @@ def clean_shared_memory(signum, frame, names, processes): sys.exit(0) +def process_wrapper(method, inputs, outputs, parameters, sync): + import sys + import os + try: + # Restore standard input in the spawned process (important for macOS where spawn is default) + sys.stdin = os.fdopen(0) + except Exception: + pass + method(inputs, outputs, parameters, sync) + + def main(): """ Main function @@ -117,14 +128,17 @@ def main(): for block_id, block in blocks.items(): name = BLOCK_DIRECTORY + "." + block["name"] mod = importlib.import_module(name) - method = method = getattr(mod, FUNCTION_NAME) + method = getattr(mod, FUNCTION_NAME) inputs = Inputs(block_data[block_id]["inputs"]) outputs = Outputs(block_data[block_id]["outputs"]) parameters = Parameters(block_data[block_id]["parameters"]) freq = block_data[block_id]["frequency"] processes.append( - multiprocessing.Process(target=method, args=(inputs, outputs, parameters, Synchronise(1 / (freq if freq != 0 else 30)))) + multiprocessing.Process( + target=process_wrapper, + args=(method, inputs, outputs, parameters, Synchronise(1 / (freq if freq != 0 else 30))) + ) ) # Register handler for Ctrl+C From 5d3a8bd0a1841150b5b1716da1f522f9f9ebea15 Mon Sep 17 00:00:00 2001 From: Sarvesh Mishra <191090264+Sarvesh-Mishra1981@users.noreply.github.com> Date: Thu, 14 May 2026 13:57:27 +0200 Subject: [PATCH 2/2] Update the Issue --- backend/staticfiles/synthesis/main.py | 43 ++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/backend/staticfiles/synthesis/main.py b/backend/staticfiles/synthesis/main.py index af338c37..49aed123 100644 --- a/backend/staticfiles/synthesis/main.py +++ b/backend/staticfiles/synthesis/main.py @@ -51,13 +51,36 @@ def clean_shared_memory(signum, frame, names, processes): def process_wrapper(method, inputs, outputs, parameters, sync): + """ + Wrapper that restores sys.stdin before running a block's main(). + + On macOS / Windows, multiprocessing uses 'spawn' which starts a fresh + Python interpreter where sys.stdin is None. Calling input() in that + state raises EOFError immediately (Issue #359). + + Strategy: + 1. If fd 0 is a real TTY, open /dev/tty so the block gets a proper + interactive terminal (works even inside Docker with -it). + 2. Otherwise fall back to os.fdopen(0) for piped / non-TTY contexts. + 3. If both fail, leave sys.stdin unchanged and let the block handle it. + """ import sys import os - try: - # Restore standard input in the spawned process (important for macOS where spawn is default) - sys.stdin = os.fdopen(0) - except Exception: - pass + + if os.isatty(0): + # fd 0 is connected to a real terminal — give the block full TTY access + try: + sys.stdin = open('/dev/tty', 'r') + except OSError: + # /dev/tty not available (rare); fall back to wrapping fd 0 directly + try: + sys.stdin = os.fdopen(0) + except OSError: + pass # leave sys.stdin as-is; block may not need input() + # If fd 0 is NOT a tty (pipe / redirect / Docker without -it), + # we do NOT restore stdin — input() will raise EOFError, which is the + # correct, expected behaviour in a non-interactive environment. + method(inputs, outputs, parameters, sync) @@ -130,13 +153,19 @@ def main(): mod = importlib.import_module(name) method = getattr(mod, FUNCTION_NAME) + # Ensure block_data entry exists even for blocks with no wires / parameters + block_data[block_id] = block_data.get( + block_id, {"inputs": {}, "outputs": {}, "parameters": {}} + ) + inputs = Inputs(block_data[block_id]["inputs"]) outputs = Outputs(block_data[block_id]["outputs"]) parameters = Parameters(block_data[block_id]["parameters"]) - freq = block_data[block_id]["frequency"] + # Default frequency to 30 Hz if not specified by any wire / synchronize_frequency entry + freq = block_data[block_id].get("frequency", 30) processes.append( multiprocessing.Process( - target=process_wrapper, + target=process_wrapper, args=(method, inputs, outputs, parameters, Synchronise(1 / (freq if freq != 0 else 30))) ) )