Forward stdin through --connect to compilation server#12734
Forward stdin through --connect to compilation server#12734Simn merged 7 commits intodevelopmentfrom
--connect to compilation server#12734Conversation
…e forwarding Add stdin forwarding support for `haxe --connect` so that piped input (e.g. `echo "data" | haxe --connect port --main Main --interp`) is forwarded to the compilation server. Changes: - compilationContext.ml: Add `stdin` field to `communication` type - server.ml: Client (`do_connect`) starts a select-based thread that reads local stdin and sends to the server socket. Server (`init_wait_socket`) creates a pipe + forwarding thread that reads stdin data from the client socket and feeds it to the compiler. Fixed null-terminator detection in read_loop to handle overflow. Added SIGPIPE ignore (signal 13) in wait_loop. - compiler.ml: Use `comm.stdin` when available instead of empty pipe Known issue: The server-side forwarding thread blocks on recv(sin) which can delay connection cleanup for requests that don't use stdin (e.g. --version). This needs further investigation - potentially using the socket fd directly via Unix.dup instead of a forwarding thread. Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
--connect to compilation server
|
@copilot A few follow-up questions. I always struggle with Unix pipes and sockets so bear with me!
|
…etRequest module, refactor tuples to records
- Point 2: Move current_stdin and current_stdin_pipe from global refs into
ServerCompilationContext.t. parse_file now takes sctx instead of cs.
init_wait_socket stores stdin pipe in a local ref returned via get_stdin.
- Point 3: Replace client-side stdin forwarding thread with Unix.select
loop that multiplexes reading from both server socket and local stdin
in a single thread.
- Point 4: Extract null-terminated socket reading into SocketRequest module
with a clean read function that returns {data; overflow}.
- Refactor server_accept from tuple to server_connection record type with
named fields (support_nonblock, read, write, close, get_stdin).
Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
Here's what I did for points 2-4, and my analysis for point 1: Points 2-4 are addressed in commit e4bc032:
Also refactored Point 1 — threading This would be a meaningful architectural improvement. With The "one compilation at a time" constraint makes this tractable. However, there's substantial global mutable state to consider:
With one-at-a-time and a mutex, most of these are safe since they're only accessed during compilation. The tricky part is the gap between compilation finishing and cleanup running — the main thread would need to ensure cleanup happens before the next compilation starts. I think this is better suited for a follow-up PR since it's a larger refactor orthogonal to stdin forwarding. It would also resolve the known limitation where the server-side forwarding thread blocks on |
|
@copilot I have pushed two refactoring commits. Please look at each of them and trace that I didn't change any behavior! And then, we'll have to find a way to add at least some basic tests for this. I don't know the best approach for that, so please investigate! |
…onnect.poll The "split" refactoring accidentally moved the final process_response() call to before the select loop (where it's a no-op on an empty buffer) instead of keeping it after the loop. This would cause data loss when the server sends a final response chunk that doesn't end with '\n' before closing the connection. Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
…integration tests - Fix: Move process_response() from before the select loop (no-op on empty buffer) to after it, restoring the original behavior that flushes any remaining response data when the server closes the connection. - Add tests/misc/connect_stdin/ with three integration tests: - stdin line forwarding through --connect - stdin multiline forwarding through --connect - no-stdin request completes without hanging - Register connect_stdin tests in CI (Macro target, Linux) Co-authored-by: Simn <634365+Simn@users.noreply.github.com>
I traced both refactoring commits: "move" (467347f): ✅ Pure behavioral refactor. Moves the stdin pipe+thread setup from inline in "split" (cba7977): For testing, I added
These run as |
|
No way this works... Edit: It works! |
|
There's more work to be done here for sure, but all the ugly parts are nicely isolated for now so I'll go ahead and merge this as-is because I have some big refactoring on top of this coming up. |
haxe --connectblocks on recv without forwarding stdin to the server, so programs executed via--interp/--runthrough the compilation server cannot receive piped input.Changes
compilationContext.ml: Addstdin : in_channel optiontocommunicationrecord. Refactorserver_acceptfrom a tuple to aserver_connectionrecord with named fields (support_nonblock,read,write,close,get_stdin) for readability.serverCompilationContext.ml: Addcurrent_stdinandcurrent_stdin_pipefields toServerCompilationContext.t, replacing the former global refs.server.ml(client —Connectmodule):Connectmodule withpollanddo_connectpolluses a singleUnix.selectloop to multiplex reading from both the server socket and local stdin — no separate thread neededshutdown SHUTDOWN_SENDto signal completionexit 0/exit 1to ensure clean process terminationserver.ml(server —SocketRequestmodule):SocketRequestmodulereadreturns{data; stdin}wherestdinis a pipe fed by a forwarding thread viasetup_client_stdin_forward\000anywhere in recv buffer instead of only checking the last byte — handles TCP segmentation where stdin data arrives in the same segment as the args terminatorserver.ml(server —init_wait_socket):SocketRequest.read, store the returned stdin channel in a connection-local ref exposed viaget_stdinon theserver_connectionrecordSys.sigpipeis-8in OCaml 5, unusable)server.ml(server —parse_file): Takessctxinstead ofcsto accesssctx.current_stdindirectly rather than a global ref.compiler.ml(create_compilation_context): Usecomm.stdinwhen present instead of creating an immediately-EOF pipeTesting
tests/misc/connect_stdin/: Integration tests usingsys.io.Processto orchestrate a--waitserver and--connectclients:--connect--connecttests/runci/targets/Macro.hxKnown limitation
The server-side forwarding thread blocks on
recv(sin)until the connection closes, which can delay cleanup for requests that don't consume stdin (e.g.--version). A follow-up could runprocessin a separate thread so the main thread owns the stdin forwarding lifecycle directly, or replace the pipe+thread withUnix.dupon the socket fd.💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.