Skip to content

Commit 73263a8

Browse files
authored
Support non-blocking Process.run standard streams on Windows (#14941)
1 parent d2e8732 commit 73263a8

File tree

2 files changed

+53
-28
lines changed

2 files changed

+53
-28
lines changed

spec/std/process_spec.cr

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,20 @@ pending_interpreted describe: Process do
189189
Process.run(*stdin_to_stdout_command, error: closed_io)
190190
end
191191

192+
it "forwards non-blocking file" do
193+
with_tempfile("non-blocking-process-input.txt", "non-blocking-process-output.txt") do |in_path, out_path|
194+
File.open(in_path, "w+", blocking: false) do |input|
195+
File.open(out_path, "w+", blocking: false) do |output|
196+
input.puts "hello"
197+
input.rewind
198+
Process.run(*stdin_to_stdout_command, input: input, output: output)
199+
output.rewind
200+
output.gets_to_end.chomp.should eq("hello")
201+
end
202+
end
203+
end
204+
end
205+
192206
it "sets working directory with string" do
193207
parent = File.dirname(Dir.current)
194208
command = {% if flag?(:win32) %}

src/process.cr

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -291,33 +291,20 @@ class Process
291291

292292
private def stdio_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
293293
case stdio
294-
when IO::FileDescriptor
295-
stdio
296-
when IO
297-
if stdio.closed?
298-
if dst_io == STDIN
299-
return File.open(File::NULL, "r").tap(&.close)
300-
else
301-
return File.open(File::NULL, "w").tap(&.close)
294+
in IO::FileDescriptor
295+
# on Windows, only async pipes can be passed to child processes, async
296+
# regular files will report an error and those require a separate pipe
297+
# (https://github.com/crystal-lang/crystal/pull/13362#issuecomment-1519082712)
298+
{% if flag?(:win32) %}
299+
unless stdio.blocking || stdio.info.type.pipe?
300+
return io_to_fd(stdio, for: dst_io)
302301
end
303-
end
304-
305-
if dst_io == STDIN
306-
fork_io, process_io = IO.pipe(read_blocking: true)
307-
308-
@wait_count += 1
309-
ensure_channel
310-
spawn { copy_io(stdio, process_io, channel, close_dst: true) }
311-
else
312-
process_io, fork_io = IO.pipe(write_blocking: true)
302+
{% end %}
313303

314-
@wait_count += 1
315-
ensure_channel
316-
spawn { copy_io(process_io, stdio, channel, close_src: true) }
317-
end
318-
319-
fork_io
320-
when Redirect::Pipe
304+
stdio
305+
in IO
306+
io_to_fd(stdio, for: dst_io)
307+
in Redirect::Pipe
321308
case dst_io
322309
when STDIN
323310
fork_io, @input = IO.pipe(read_blocking: true)
@@ -330,17 +317,41 @@ class Process
330317
end
331318

332319
fork_io
333-
when Redirect::Inherit
320+
in Redirect::Inherit
334321
dst_io
335-
when Redirect::Close
322+
in Redirect::Close
336323
if dst_io == STDIN
337324
File.open(File::NULL, "r")
338325
else
339326
File.open(File::NULL, "w")
340327
end
328+
end
329+
end
330+
331+
private def io_to_fd(stdio : Stdio, for dst_io : IO::FileDescriptor) : IO::FileDescriptor
332+
if stdio.closed?
333+
if dst_io == STDIN
334+
return File.open(File::NULL, "r").tap(&.close)
335+
else
336+
return File.open(File::NULL, "w").tap(&.close)
337+
end
338+
end
339+
340+
if dst_io == STDIN
341+
fork_io, process_io = IO.pipe(read_blocking: true)
342+
343+
@wait_count += 1
344+
ensure_channel
345+
spawn { copy_io(stdio, process_io, channel, close_dst: true) }
341346
else
342-
raise "BUG: Impossible type in stdio #{stdio.class}"
347+
process_io, fork_io = IO.pipe(write_blocking: true)
348+
349+
@wait_count += 1
350+
ensure_channel
351+
spawn { copy_io(process_io, stdio, channel, close_src: true) }
343352
end
353+
354+
fork_io
344355
end
345356

346357
# :nodoc:

0 commit comments

Comments
 (0)