Skip to content

Commit

Permalink
deprecate spawn(cmd) to run(cmd, wait=false)
Browse files Browse the repository at this point in the history
add `read` and `write` keyword arguments to `open` for processes

fixes #25965
  • Loading branch information
JeffBezanson committed Feb 20, 2018
1 parent a237986 commit 61bc0ef
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 65 deletions.
3 changes: 3 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,9 @@ end
link_pipe!(pipe, reader_supports_async = julia_only_read, writer_supports_async = julia_only_write),
false)

# issue #25965
@deprecate spawn(cmds::AbstractCmd) run(cmds, wait = false)

# Remember to delete the module when removing this
@eval Base.Math module JuliaLibm
Base.@deprecate log Base.log
Expand Down
1 change: 0 additions & 1 deletion base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,6 @@ export
process_running,
run,
setenv,
spawn,
success,
withenv,

Expand Down
2 changes: 0 additions & 2 deletions base/precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ precompile(Tuple{getfield(Base, Symbol("#kw##printstyled")), NamedTuple{(:bold,
precompile(Tuple{getfield(Base, Symbol("#kw##printstyled")), NamedTuple{(:bold, :color), Tuple{Bool, Symbol}}, typeof(Base.printstyled), REPL.Terminals.TTYTerminal, String})
precompile(Tuple{getfield(Base, Symbol("#kw##printstyled")), NamedTuple{(:color,), Tuple{Symbol}}, typeof(Base.printstyled), Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, String})
precompile(Tuple{getfield(Base, Symbol("#kw##show_trace_entry")), NamedTuple{(:prefix,), Tuple{String}}, typeof(Base.show_trace_entry), Base.IOContext{REPL.Terminals.TTYTerminal}, Base.StackTraces.StackFrame, Int64})
precompile(Tuple{getfield(Base, Symbol("#kw##spawn")), NamedTuple{(:chain,), Tuple{Nothing}}, typeof(Base.spawn), Base.Cmd, Tuple{Base.Pipe, Base.TTY, Base.IOStream}})
precompile(Tuple{getfield(Base, Symbol("#kw##unindent")), NamedTuple{(:tabwidth,), Tuple{Int64}}, typeof(Base.unindent), String, Int64})
precompile(Tuple{getfield(Base, Symbol("#kw##with_output_color")), NamedTuple{(:bold,), Tuple{Bool}}, typeof(Base.with_output_color), typeof(Base.print), Symbol, Base.IOContext{Base.GenericIOBuffer{Array{UInt8, 1}}}, String})
precompile(Tuple{getfield(Base, Symbol("#kw##with_output_color")), NamedTuple{(:bold,), Tuple{Bool}}, typeof(Base.with_output_color), typeof(Base.print), Symbol, REPL.Terminals.TTYTerminal, String})
Expand Down Expand Up @@ -579,7 +578,6 @@ precompile(Tuple{typeof(Base.similar), Array{Float64, 1}})
precompile(Tuple{typeof(Base.sort!), Array{Int64, 1}, Base.Sort.QuickSortAlg, Base.Order.Perm{Base.Order.ForwardOrdering, Array{Tuple{Float64, Int64}, 1}}})
precompile(Tuple{typeof(Base.sort!), Array{Int64, 1}, Base.Sort.QuickSortAlg, Base.Order.Perm{Base.Order.ForwardOrdering, Array{Tuple{Int64, Float64}, 1}}})
precompile(Tuple{typeof(Base.sort), Array{Float64, 1}})
precompile(Tuple{typeof(Base.spawn), Base.CmdRedirect, Tuple{Base.Pipe, Base.TTY, Base.IOStream}})
precompile(Tuple{typeof(Base.start), Array{AbstractString, 1}})
precompile(Tuple{typeof(Base.start), Array{Expr, 1}})
precompile(Tuple{typeof(Base.start), Array{Function, 1}})
Expand Down
95 changes: 54 additions & 41 deletions base/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -397,37 +397,37 @@ function _uv_hook_close(proc::Process)
notify(proc.closenotify)
end

function spawn(redirect::CmdRedirect, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
spawn(redirect.cmd,
(redirect.stream_no == STDIN_NO ? redirect.handle : stdios[1],
redirect.stream_no == STDOUT_NO ? redirect.handle : stdios[2],
redirect.stream_no == STDERR_NO ? redirect.handle : stdios[3]),
function _spawn(redirect::CmdRedirect, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
_spawn(redirect.cmd,
(redirect.stream_no == STDIN_NO ? redirect.handle : stdios[1],
redirect.stream_no == STDOUT_NO ? redirect.handle : stdios[2],
redirect.stream_no == STDERR_NO ? redirect.handle : stdios[3]),
chain=chain)
end

function spawn(cmds::OrCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
function _spawn(cmds::OrCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
if chain === nothing
chain = ProcessChain(stdios)
end
in_pipe, out_pipe = link_pipe(false, false)
try
spawn(cmds.a, (stdios[1], out_pipe, stdios[3]), chain=chain)
spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
_spawn(cmds.a, (stdios[1], out_pipe, stdios[3]), chain=chain)
_spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
finally
close_pipe_sync(out_pipe)
close_pipe_sync(in_pipe)
end
return chain
end

function spawn(cmds::ErrOrCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
function _spawn(cmds::ErrOrCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
if chain === nothing
chain = ProcessChain(stdios)
end
in_pipe, out_pipe = link_pipe(false, false)
try
spawn(cmds.a, (stdios[1], stdios[2], out_pipe), chain=chain)
spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
_spawn(cmds.a, (stdios[1], stdios[2], out_pipe), chain=chain)
_spawn(cmds.b, (in_pipe, stdios[2], stdios[3]), chain=chain)
finally
close_pipe_sync(out_pipe)
close_pipe_sync(in_pipe)
Expand Down Expand Up @@ -503,7 +503,7 @@ function setup_stdio(anon::Function, stdio::StdIOSet)
nothing
end

function spawn(cmd::Cmd, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
function _spawn(cmd::Cmd, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
if isempty(cmd.exec)
throw(ArgumentError("cannot spawn empty command"))
end
Expand All @@ -519,13 +519,13 @@ function spawn(cmd::Cmd, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=n
return pp
end

function spawn(cmds::AndCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
function _spawn(cmds::AndCmds, stdios::StdIOSet; chain::Union{ProcessChain, Nothing}=nothing)
if chain === nothing
chain = ProcessChain(stdios)
end
setup_stdio(stdios) do stdios
spawn(cmds.a, stdios, chain=chain)
spawn(cmds.b, stdios, chain=chain)
_spawn(cmds.a, stdios, chain=chain)
_spawn(cmds.b, stdios, chain=chain)
end
return chain
end
Expand All @@ -548,68 +548,75 @@ spawn_opts_inherit(stdios::StdIOSet) = (stdios,)
spawn_opts_inherit(in::Redirectable=RawFD(0), out::Redirectable=RawFD(1), err::Redirectable=RawFD(2), args...) =
((in, out, err), args...)

"""
spawn(command)
Run a command object asynchronously, returning the resulting `Process` object.
"""
spawn(cmds::AbstractCmd, args...; chain::Union{ProcessChain, Nothing}=nothing) =
spawn(cmds, spawn_opts_swallow(args...)...; chain=chain)
_spawn(cmds::AbstractCmd, args...; chain::Union{ProcessChain, Nothing}=nothing) =
_spawn(cmds, spawn_opts_swallow(args...)...; chain=chain)

function eachline(cmd::AbstractCmd; chomp=nothing, keep::Bool=false)
if chomp !== nothing
keep = !chomp
depwarn("The `chomp=$chomp` argument to `eachline` is deprecated in favor of `keep=$keep`.", :eachline)
end
stdout = Pipe()
processes = spawn(cmd, (DevNull,stdout,STDERR))
processes = _spawn(cmd, (DevNull,stdout,STDERR))
close(stdout.in)
out = stdout.out
# implicitly close after reading lines, since we opened
return EachLine(out, keep=keep,
ondone=()->(close(out); success(processes) || pipeline_error(processes)))::EachLine
end

function open(cmds::AbstractCmd, mode::AbstractString, other::Redirectable=DevNull)
if mode == "r+" || mode == "w+"
return open(cmds, other, read = true, write = true)
elseif mode == "r"
return open(cmds, other)
elseif mode == "w"
return open(cmds, other, write = true)
else
throw(ArgumentError("mode must be \"r\" or \"w\", not \"$mode\""))
end
end

# return a Process object to read-to/write-from the pipeline
"""
open(command, mode::AbstractString="r", stdio=DevNull)
open(command, stdio=DevNull; write::Bool = false, read::Bool = !write)
Start running `command` asynchronously, and return a tuple `(stream,process)`. If `mode` is
`"r"`, then `stream` reads from the process's standard output and `stdio` optionally
specifies the process's standard input stream. If `mode` is `"w"`, then `stream` writes to
Start running `command` asynchronously, and return a tuple `(stream,process)`. If `read` is
true, then `stream` reads from the process's standard output and `stdio` optionally
specifies the process's standard input stream. If `write` is true, then `stream` writes to
the process's standard input and `stdio` optionally specifies the process's standard output
stream.
"""
function open(cmds::AbstractCmd, mode::AbstractString="r", other::Redirectable=DevNull)
if mode == "r+" || mode == "w+"
other === DevNull || throw(ArgumentError("no other stream for mode rw+"))
function open(cmds::AbstractCmd, other::Redirectable=DevNull; write::Bool = false, read::Bool = !write)
if read && write
other === DevNull || throw(ArgumentError("no other stream can be specified in read-write mode"))
in = Pipe()
out = Pipe()
processes = spawn(cmds, (in,out,STDERR))
processes = _spawn(cmds, (in,out,STDERR))
close(in.out)
close(out.in)
elseif mode == "r"
elseif read
in = other
out = Pipe()
processes = spawn(cmds, (in,out,STDERR))
processes = _spawn(cmds, (in,out,STDERR))
close(out.in)
if isa(processes, ProcessChain) # for open(cmd) deprecation
processes = ProcessChain(processes, :out)
else
processes.openstream = :out
end
elseif mode == "w"
elseif write
in = Pipe()
out = other
processes = spawn(cmds, (in,out,STDERR))
processes = _spawn(cmds, (in,out,STDERR))
close(in.out)
if isa(processes, ProcessChain) # for open(cmd) deprecation
processes = ProcessChain(processes, :in)
else
processes.openstream = :in
end
else
throw(ArgumentError("mode must be \"r\" or \"w\", not \"$mode\""))
processes = _spawn(cmds)
end
return processes
end
Expand Down Expand Up @@ -645,14 +652,20 @@ end
read(cmd::AbstractCmd, ::Type{String}) = String(read(cmd))

"""
run(command, args...)
run(command, args...; wait::Bool = true)
Run a command object, constructed with backticks. Throws an error if anything goes wrong,
including the process exiting with a non-zero status.
If `wait` is false, the process runs asynchronously. You can later wait for it and check
its exit status by calling `success` on the returned process object.
"""
function run(cmds::AbstractCmd, args...)
ps = spawn(cmds, spawn_opts_inherit(args...)...)
success(ps) ? nothing : pipeline_error(ps)
function run(cmds::AbstractCmd, args...; wait::Bool = true)
ps = _spawn(cmds, spawn_opts_inherit(args...)...)
if wait
success(ps) || pipeline_error(ps)
end
return ps
end

# some common signal numbers that are usually available on all platforms
Expand Down Expand Up @@ -686,7 +699,7 @@ success(procs::ProcessChain) = success(procs.processes)
Run a command object, constructed with backticks, and tell whether it was successful (exited
with a code of 0). An exception is raised if the process cannot be started.
"""
success(cmd::AbstractCmd) = success(spawn(cmd))
success(cmd::AbstractCmd) = success(_spawn(cmd))

function pipeline_error(proc::Process)
if !proc.cmd.ignorestatus
Expand Down
2 changes: 1 addition & 1 deletion base/stream.jl
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ julia> err = Pipe()
# After this `err` will be initialized and you may read `foo`'s
# stderr from the `err` pipe.
julia> spawn(pipeline(pipeline(`foo`, stderr=err), `cat`))
julia> run(pipeline(pipeline(`foo`, stderr=err), `cat`), wait=false)
```
"""
Pipe() = Pipe(PipeEndpoint(), PipeEndpoint())
Expand Down
1 change: 0 additions & 1 deletion doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,6 @@ Base.skipmissing

```@docs
Base.run
Base.spawn
Base.DevNull
Base.success
Base.process_running
Expand Down
2 changes: 1 addition & 1 deletion stdlib/InteractiveUtils/src/editless.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function edit(path::AbstractString, line::Integer=0)
(Ptr{Cvoid}, Cwstring, Cwstring, Ptr{Cvoid}, Ptr{Cvoid}, Cint),
C_NULL, "open", path, C_NULL, C_NULL, 10) 32)
elseif background
spawn(pipeline(cmd, stderr=STDERR))
run(pipeline(cmd, stderr=STDERR), wait=false)
else
run(cmd)
end
Expand Down
4 changes: 2 additions & 2 deletions stdlib/LibGit2/test/libgit2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function challenge_prompt(cmd::Cmd, challenges; timeout::Integer=10, debug::Bool
end
out = IOBuffer()
with_fake_pty() do slave, master
p = spawn(detach(cmd), slave, slave, slave)
p = run(detach(cmd), slave, slave, slave, wait=false)

# Kill the process if it takes too long. Typically occurs when process is waiting
# for input.
Expand Down Expand Up @@ -2703,7 +2703,7 @@ mktempdir() do dir
# certificate. The minimal server can't actually serve a Git repository.
mkdir(joinpath(root, "Example.jl"))
pobj = cd(root) do
spawn(`openssl s_server -key $key -cert $cert -WWW -accept $port`)
run(`openssl s_server -key $key -cert $cert -WWW -accept $port`, wait=false)
end

errfile = joinpath(root, "error")
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ let exename = Base.julia_cmd()
TestHelpers.with_fake_pty() do slave, master
nENV = copy(ENV)
nENV["TERM"] = "dumb"
p = spawn(setenv(`$exename --startup-file=no -q`,nENV),slave,slave,slave)
p = run(setenv(`$exename --startup-file=no -q`,nENV),slave,slave,slave,wait=false)
output = readuntil(master,"julia> ",keep=true)
if ccall(:jl_running_on_valgrind,Cint,()) == 0
# If --trace-children=yes is passed to valgrind, we will get a
Expand Down
6 changes: 3 additions & 3 deletions test/cmdlineargs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ end
function readchomperrors(exename::Cmd)
out = Base.PipeEndpoint()
err = Base.PipeEndpoint()
p = spawn(exename, DevNull, out, err)
p = run(exename, DevNull, out, err, wait=false)
o = @async(readchomp(out))
e = @async(readchomp(err))
return (success(p), fetch(o), fetch(e))
Expand Down Expand Up @@ -388,7 +388,7 @@ let exename = joinpath(Sys.BINDIR, Base.julia_exename()),
"$sysname.nonexistent",
)
let stderr = Pipe(),
p = spawn(pipeline(`$exename --sysimage=$nonexist_image`, stderr=stderr))
p = run(pipeline(`$exename --sysimage=$nonexist_image`, stderr=stderr), wait=false)
close(stderr.in)
let s = read(stderr, String)
@test contains(s, "ERROR: could not load library \"$nonexist_image\"\n")
Expand All @@ -401,7 +401,7 @@ let exename = joinpath(Sys.BINDIR, Base.julia_exename()),
end
end
let stderr = Pipe(),
p = spawn(pipeline(`$exename --sysimage=$libjulia`, stderr=stderr))
p = run(pipeline(`$exename --sysimage=$libjulia`, stderr=stderr), wait=false)
close(stderr.in)
let s = read(stderr, String)
@test s == "ERROR: System image file failed consistency check: maybe opened the wrong version?\n"
Expand Down
19 changes: 7 additions & 12 deletions test/spawn.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ end

@test read(`$echocmd hello \| sort`, String) == "hello | sort\n"
@test read(pipeline(`$echocmd hello`, sortcmd), String) == "hello\n"
@test length(spawn(pipeline(`$echocmd hello`, sortcmd)).processes) == 2
@test length(run(pipeline(`$echocmd hello`, sortcmd), wait=false).processes) == 2

out = read(`$echocmd hello` & `$echocmd world`, String)
@test contains(out,"world")
Expand All @@ -60,7 +60,7 @@ Sys.isunix() && run(pipeline(yescmd, `head`, DevNull))
let a, p
a = Base.Condition()
@schedule begin
p = spawn(pipeline(yescmd,DevNull))
p = run(pipeline(yescmd,DevNull), wait=false)
Base.notify(a,p)
@test !success(p)
end
Expand Down Expand Up @@ -166,7 +166,7 @@ let r, t
try
wait(r)
end
p = spawn(`$sleepcmd 1`); wait(p)
p = run(`$sleepcmd 1`, wait=false); wait(p)
@test p.exitcode == 0
return true
end
Expand Down Expand Up @@ -217,14 +217,14 @@ if valgrind_off
# valgrind banner here, not "Hello World\n".
@test read(pipeline(`$exename --startup-file=no -e 'println(STDERR,"Hello World")'`, stderr=catcmd), String) == "Hello World\n"
out = Pipe()
proc = spawn(pipeline(`$exename --startup-file=no -e 'println(STDERR,"Hello World")'`, stderr = out))
proc = run(pipeline(`$exename --startup-file=no -e 'println(STDERR,"Hello World")'`, stderr = out), wait=false)
close(out.in)
@test read(out, String) == "Hello World\n"
@test success(proc)
end

# setup_stdio for AbstractPipe
let out = Pipe(), proc = spawn(pipeline(`$echocmd "Hello World"`, stdout=IOContext(out,STDOUT)))
let out = Pipe(), proc = run(pipeline(`$echocmd "Hello World"`, stdout=IOContext(out,STDOUT)), wait=false)
close(out.in)
@test read(out, String) == "Hello World\n"
@test success(proc)
Expand Down Expand Up @@ -445,11 +445,6 @@ end
@test_throws ArgumentError run(Base.AndCmds(``, `$truecmd`))
@test_throws ArgumentError run(Base.AndCmds(`$truecmd`, ``))

@test_throws ArgumentError spawn(Base.Cmd(``))
@test_throws ArgumentError spawn(Base.AndCmds(``, ``))
@test_throws ArgumentError spawn(Base.AndCmds(``, `$echocmd test`))
@test_throws ArgumentError spawn(Base.AndCmds(`$echocmd test`, ``))

# tests for reducing over collection of Cmd
@test_throws ArgumentError reduce(&, Base.AbstractCmd[])
@test_throws ArgumentError reduce(&, Base.Cmd[])
Expand Down Expand Up @@ -507,7 +502,7 @@ end
#let stdout = Pipe(), stdin = Pipe()
# Base.link_pipe!(stdout, reader_supports_async=true)
# Base.link_pipe!(stdin, writer_supports_async=true)
# p = spawn(pipeline(catcmd, stdin=stdin, stdout=stdout, stderr=DevNull))
# p = run(pipeline(catcmd, stdin=stdin, stdout=stdout, stderr=DevNull), wait=false)
# @async begin # feed cat with 2 MB of data (zeros)
# write(stdin, zeros(UInt8, 1048576 * 2))
# close(stdin)
Expand All @@ -519,7 +514,7 @@ end
#end

# `kill` error conditions
let p = spawn(`$sleepcmd 100`)
let p = run(`$sleepcmd 100`, wait=false)
# Should throw on invalid signals
@test_throws Base.UVError kill(p, typemax(Cint))
kill(p)
Expand Down

0 comments on commit 61bc0ef

Please sign in to comment.