Skip to content

Commit

Permalink
change setup_stdio default method to support any IO (JuliaLang#40780)
Browse files Browse the repository at this point in the history
  • Loading branch information
vtjnash authored and Amit Shirodkar committed Jun 9, 2021
1 parent 5f04cb8 commit b7e2804
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 36 deletions.
1 change: 1 addition & 0 deletions base/cmd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ rawhandle(x::OS_HANDLE) = x
if OS_HANDLE !== RawFD
rawhandle(x::RawFD) = Libc._get_osfhandle(x)
end
setup_stdio(stdio::Union{DevNull,OS_HANDLE,RawFD}, ::Bool) = (stdio, false)

const Redirectable = Union{IO, FileRedirect, RawFD, OS_HANDLE}
const StdIOSet = NTuple{3, Redirectable}
Expand Down
3 changes: 2 additions & 1 deletion base/filesystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import .Base:
IOError, _UVError, _sizeof_uv_fs, check_open, close, eof, eventloop, fd, isopen,
bytesavailable, position, read, read!, readavailable, seek, seekend, show,
skip, stat, unsafe_read, unsafe_write, write, transcode, uv_error,
rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize
setup_stdio, rawhandle, OS_HANDLE, INVALID_OS_HANDLE, windowserror, filesize

import .Base.RefValue

Expand Down Expand Up @@ -76,6 +76,7 @@ if OS_HANDLE !== RawFD
end

rawhandle(file::File) = file.handle
setup_stdio(file::File, ::Bool) = (file, false)

# Filesystem.open, not Base.open
function open(path::AbstractString, flags::Integer, mode::Integer=0)
Expand Down
66 changes: 31 additions & 35 deletions base/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,28 @@ const SpawnIOs = Vector{Any} # convenience name for readability
# handle marshalling of `Cmd` arguments from Julia to C
@noinline function _spawn_primitive(file, cmd::Cmd, stdio::SpawnIOs)
loop = eventloop()
iohandles = Tuple{Cint, UInt}[ # assuming little-endian layout
let h = rawhandle(io)
h === C_NULL ? (0x00, UInt(0)) :
h isa OS_HANDLE ? (0x02, UInt(cconvert(@static(Sys.iswindows() ? Ptr{Cvoid} : Cint), h))) :
h isa Ptr{Cvoid} ? (0x04, UInt(h)) :
error("invalid spawn handle $h from $io")
end
for io in stdio]
handle = Libc.malloc(_sizeof_uv_process)
disassociate_julia_struct(handle) # ensure that data field is set to C_NULL
err = ccall(:jl_spawn, Int32,
(Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid},
Ptr{Tuple{Cint, UInt}}, Int,
UInt32, Ptr{Cstring}, Cstring, Ptr{Cvoid}),
file, cmd.exec, loop, handle,
iohandles, length(iohandles),
cmd.flags,
cmd.env === nothing ? C_NULL : cmd.env,
isempty(cmd.dir) ? C_NULL : cmd.dir,
@cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32)))
GC.@preserve stdio begin
iohandles = Tuple{Cint, UInt}[ # assuming little-endian layout
let h = rawhandle(io)
h === C_NULL ? (0x00, UInt(0)) :
h isa OS_HANDLE ? (0x02, UInt(cconvert(@static(Sys.iswindows() ? Ptr{Cvoid} : Cint), h))) :
h isa Ptr{Cvoid} ? (0x04, UInt(h)) :
error("invalid spawn handle $h from $io")
end
for io in stdio]
handle = Libc.malloc(_sizeof_uv_process)
disassociate_julia_struct(handle) # ensure that data field is set to C_NULL
err = ccall(:jl_spawn, Int32,
(Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid},
Ptr{Tuple{Cint, UInt}}, Int,
UInt32, Ptr{Cstring}, Cstring, Ptr{Cvoid}),
file, cmd.exec, loop, handle,
iohandles, length(iohandles),
cmd.flags,
cmd.env === nothing ? C_NULL : cmd.env,
isempty(cmd.dir) ? C_NULL : cmd.dir,
@cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32)))
end
if err != 0
ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), handle) # will call free on handle eventually
throw(_UVError("could not spawn " * repr(cmd), err))
Expand Down Expand Up @@ -209,10 +211,10 @@ function setup_stdio(stdio::PipeEndpoint, child_readable::Bool)
rd, wr = link_pipe(!child_readable, child_readable)
try
open_pipe!(stdio, child_readable ? wr : rd)
catch ex
catch
close_pipe_sync(rd)
close_pipe_sync(wr)
rethrow(ex)
rethrow()
end
child = child_readable ? rd : wr
return (child, true)
Expand Down Expand Up @@ -251,18 +253,18 @@ function setup_stdio(stdio::FileRedirect, child_readable::Bool)
return (io, true)
end

# incrementally move data between an IOBuffer and a system Pipe
# incrementally move data between an arbitrary IO and a system Pipe
# TODO: probably more efficient (when valid) to use `stdio` directly as the
# PipeEndpoint buffer field in some cases
function setup_stdio(stdio::Union{IOBuffer, BufferStream}, child_readable::Bool)
function setup_stdio(stdio::IO, child_readable::Bool)
parent = PipeEndpoint()
rd, wr = link_pipe(!child_readable, child_readable)
try
open_pipe!(parent, child_readable ? wr : rd)
catch ex
catch
close_pipe_sync(rd)
close_pipe_sync(wr)
rethrow(ex)
rethrow()
end
child = child_readable ? rd : wr
try
Expand All @@ -271,24 +273,18 @@ function setup_stdio(stdio::Union{IOBuffer, BufferStream}, child_readable::Bool)
@async try
write(in, out)
catch ex
@warn "Process error" exception=(ex, catch_backtrace())
@warn "Process I/O error" exception=(ex, catch_backtrace())
finally
close(parent)
end
end
catch ex
catch
close_pipe_sync(child)
rethrow(ex)
rethrow()
end
return (child, true)
end

function setup_stdio(io, child_readable::Bool)
# if there is no specialization,
# assume that rawhandle is defined for it
return (io, false)
end

close_stdio(stdio::OS_HANDLE) = close_pipe_sync(stdio)
close_stdio(stdio) = close(stdio)

Expand Down
1 change: 1 addition & 0 deletions base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ end
lock(s::LibuvStream) = lock(s.lock)
unlock(s::LibuvStream) = unlock(s.lock)

setup_stdio(stream::LibuvStream) = (stream, false)
rawhandle(stream::LibuvStream) = stream.handle
unsafe_convert(::Type{Ptr{Cvoid}}, s::Union{LibuvStream, LibuvServer}) = s.handle

Expand Down
8 changes: 8 additions & 0 deletions test/spawn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,15 @@ let text = "input-test-text"
@test read(proc, String) == string(length(text), '\n')
@test success(proc)
@test String(take!(b)) == text

out = PipeBuffer()
proc = run(catcmd, IOBuffer(SubString(text)), out)
@test success(proc)
@test proc.out === proc.err === proc.in === devnull
@test String(take!(out)) == text
end


@test repr(Base.CmdRedirect(``, devnull, 0, false)) == "pipeline(``, stdin>Base.DevNull())"
@test repr(Base.CmdRedirect(``, devnull, 1, true)) == "pipeline(``, stdout<Base.DevNull())"
@test repr(Base.CmdRedirect(``, devnull, 11, true)) == "pipeline(``, 11<Base.DevNull())"
Expand Down

0 comments on commit b7e2804

Please sign in to comment.