Skip to content

Commit cfe48ea

Browse files
committed
fixes process tests on windows
1 parent f34db1b commit cfe48ea

1 file changed

Lines changed: 26 additions & 10 deletions

File tree

lang/libs/process/win/win.ch

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public func CreatePipe(
3333
nSize : DWORD
3434
) : BOOL;
3535

36+
@dllimport @extern @stdcall
37+
public func SetHandleInformation(hObject : HANDLE, dwMask : DWORD, dwFlags : DWORD) : BOOL;
38+
3639
// ---------------------------------------------------------------------------
3740
// Constants
3841
// ---------------------------------------------------------------------------
@@ -42,6 +45,7 @@ comptime const CREATE_NO_WINDOW : DWORD = 0x08000000 as DWORD;
4245
comptime const WAIT_OBJECT_0 : DWORD = 0 as DWORD;
4346
comptime const WAIT_TIMEOUT : DWORD = 258 as DWORD;
4447
comptime const INFINITE : DWORD = 0xFFFFFFFF as DWORD;
48+
comptime const HANDLE_FLAG_INHERIT : DWORD = 0x00000001 as DWORD;
4549

4650
// ---------------------------------------------------------------------------
4751
// Internal helpers
@@ -64,11 +68,9 @@ func win_read_all(h : HANDLE, data : *mut vector<u8>) : bool {
6468
return true
6569
}
6670

67-
/// Build a command line string from args: "cmd /c arg1 arg2 arg3"
68-
/// The result is a null-terminated mutable buffer for CreateProcessA.
71+
/// Build a command line string from args with "cmd /c" wrapper.
72+
/// Used by win_execute where stdin piping is not needed.
6973
func win_build_cmdline(args : *vector<string>) : string {
70-
// Always use cmd /c to ensure cmd.exe builtins and PATH resolution work.
71-
// Quote args after -c flags so sh receives the full command string.
7274
var cmd = string("cmd /c ")
7375
var i : size_t = 0
7476
var prev_was_flag = false
@@ -114,6 +116,7 @@ public func win_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool {
114116
if(CreatePipe(&raw mut stdout_read, &raw mut stdout_write, &raw mut sa, 0) == 0) {
115117
return false
116118
}
119+
SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)
117120
}
118121

119122
// Create stderr pipe (not needed when merging into stdout)
@@ -125,6 +128,7 @@ public func win_execute(cfg : *ProcessConfig, out : *mut ProcessResult) : bool {
125128
}
126129
return false
127130
}
131+
SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)
128132
}
129133

130134
// Build command line
@@ -242,6 +246,11 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
242246
if(CreatePipe(&raw mut stdout_read, &raw mut stdout_write, &raw mut sa, 0) == 0) {
243247
return false
244248
}
249+
// Prevent the parent-side read handle from being inherited by the child.
250+
// Without this, CreateProcessA duplicates ALL inheritable handles, so the
251+
// parent's copy of stdout_read would keep the pipe alive even after the
252+
// real read end is closed — blocking win_read_all forever.
253+
SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)
245254
}
246255

247256
// Create stderr pipe
@@ -253,6 +262,7 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
253262
}
254263
return false
255264
}
265+
SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0)
256266
}
257267

258268
// Create stdin pipe
@@ -261,6 +271,10 @@ public func win_spawn(cfg : *ProcessConfig, child : *mut ChildProcess) : bool {
261271
if(cfg.capture_stderr) { CloseHandle(stderr_read); CloseHandle(stderr_write) }
262272
return false
263273
}
274+
// Prevent the parent-side write handle from being inherited by the child.
275+
// Without this, closing stdin_write in the parent doesn't send EOF because
276+
// the child still has its own inherited copy of the write handle.
277+
SetHandleInformation(stdin_write, HANDLE_FLAG_INHERIT, 0)
264278

265279
// Build command line
266280
var cmd = win_build_cmdline(&raw mut cfg.args)
@@ -331,6 +345,14 @@ public func win_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bool
331345
var stdout_data = vector<u8>()
332346
var stderr_data = vector<u8>()
333347

348+
// Close stdin first — the child may be blocking on stdin (e.g. cat,
349+
// shell). Closing the write end sends EOF so the child can exit and
350+
// close its stdout/stderr, preventing a deadlock in win_read_all.
351+
if(child.win.h_stdin_write != null) {
352+
CloseHandle(child.win.h_stdin_write)
353+
child.win.h_stdin_write = null
354+
}
355+
334356
// Read stdout
335357
if(child.win.h_stdout_read != null) {
336358
win_read_all(child.win.h_stdout_read, &raw mut stdout_data)
@@ -345,12 +367,6 @@ public func win_wait(child : *mut ChildProcess, out : *mut ProcessResult) : bool
345367
child.win.h_stderr_read = null
346368
}
347369

348-
// Close stdin
349-
if(child.win.h_stdin_write != null) {
350-
CloseHandle(child.win.h_stdin_write)
351-
child.win.h_stdin_write = null
352-
}
353-
354370
// Wait for process
355371
WaitForSingleObject(child.win.h_process, INFINITE)
356372

0 commit comments

Comments
 (0)