• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #38182

15 Aug 2025 03:55AM UTC coverage: 77.87% (-0.4%) from 78.28%
#38182

push

local

web-flow
🤖 [master] Bump the SparseArrays stdlib from 30201ab to bb5ecc0 (#59263)

Stdlib: SparseArrays
URL: https://github.com/JuliaSparse/SparseArrays.jl.git
Stdlib branch: main
Julia branch: master
Old commit: 30201ab
New commit: bb5ecc0
Julia version: 1.13.0-DEV
SparseArrays version: 1.13.0
Bump invoked by: @ViralBShah
Powered by:
[BumpStdlibs.jl](https://github.com/JuliaLang/BumpStdlibs.jl)

Diff:
https://github.com/JuliaSparse/SparseArrays.jl/compare/30201abcb...bb5ecc091

```
$ git log --oneline 30201ab..bb5ecc0
bb5ecc0 fast quadratic form for dense matrix, sparse vectors (#640)
34ece87 Extend 3-arg `dot` to generic `HermOrSym` sparse matrices (#643)
095b685 Exclude unintended complex symmetric sparse matrices from 3-arg `dot` (#642)
8049287 Fix signature for 2-arg matrix-matrix `dot` (#641)
cff971d Make cond(::SparseMatrix, 1 / Inf) discoverable from 2-norm error (#629)
```

Co-authored-by: ViralBShah <744411+ViralBShah@users.noreply.github.com>

48274 of 61993 relevant lines covered (77.87%)

9571166.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.51
/base/process.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
mutable struct Process <: AbstractPipe
4
    cmd::Cmd
5
    handle::Ptr{Cvoid}
6
    in::IO
7
    out::IO
8
    err::IO
9
    syncd::Vector{Task}
10
    exitcode::Int64
11
    termsignal::Int32
12
    exitnotify::ThreadSynchronizer
13
    function Process(cmd::Cmd, handle::Ptr{Cvoid}, syncd::Vector{Task})
3✔
14
        this = new(cmd, handle, devnull, devnull, devnull, syncd,
745✔
15
                   typemin(fieldtype(Process, :exitcode)),
16
                   typemin(fieldtype(Process, :termsignal)),
17
                   ThreadSynchronizer())
18
        finalizer(uvfinalize, this)
745✔
19
        return this
745✔
20
    end
21
end
22
pipe_reader(p::Process) = p.out
1,738,086✔
23
pipe_writer(p::Process) = p.in
175,004✔
24

25
# Represents a whole pipeline of any number of related processes
26
# so the entire pipeline can be treated as one entity
27
mutable struct ProcessChain <: AbstractPipe
28
    processes::Vector{Process}
29
    in::IO
30
    out::IO
31
    err::IO
32
    function ProcessChain()
33
        return new(Process[], devnull, devnull, devnull)
23✔
34
    end
35
end
36
pipe_reader(p::ProcessChain) = p.out
×
37
pipe_writer(p::ProcessChain) = p.in
×
38

39
# a lightweight pair of a child OS_HANDLE and associated Task that will
40
# complete only after all content has been read from it for synchronizing
41
# state without the kernel to aide
42
struct SyncCloseFD
43
    fd
12✔
44
    t::Task
45
end
46
rawhandle(io::SyncCloseFD) = rawhandle(io.fd)
11✔
47

48
# release ownership of the libuv handle
49
function uvfinalize(proc::Process)
717✔
50
    if proc.handle != C_NULL
717✔
51
        iolock_begin()
3✔
52
        if proc.handle != C_NULL
3✔
53
            disassociate_julia_struct(proc.handle)
3✔
54
            ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), proc.handle)
3✔
55
            proc.handle = C_NULL
3✔
56
        end
57
        iolock_end()
3✔
58
    end
59
    nothing
717✔
60
end
61

62
# called when the process dies
63
function uv_return_spawn(p::Ptr{Cvoid}, exit_status::Int64, termsignal::Int32)
750✔
64
    data = ccall(:jl_uv_process_data, Ptr{Cvoid}, (Ptr{Cvoid},), p)
750✔
65
    data == C_NULL && return
750✔
66
    proc = unsafe_pointer_to_objref(data)::Process
750✔
67
    proc.exitcode = exit_status
750✔
68
    proc.termsignal = termsignal
750✔
69
    disassociate_julia_struct(proc.handle) # ensure that data field is set to C_NULL
750✔
70
    ccall(:jl_close_uv, Cvoid, (Ptr{Cvoid},), proc.handle)
750✔
71
    proc.handle = C_NULL
750✔
72
    lock(proc.exitnotify)
750✔
73
    try
750✔
74
        notify(proc.exitnotify)
750✔
75
    finally
76
        unlock(proc.exitnotify)
750✔
77
    end
78
    nothing
750✔
79
end
80

81
# called when the libuv handle is destroyed
82
function _uv_hook_close(proc::Process)
×
83
    Libc.free(@atomicswap :not_atomic proc.handle = C_NULL)
×
84
    nothing
×
85
end
86

87
const SpawnIO  = Union{IO, RawFD, OS_HANDLE, SyncCloseFD} # internal copy of Redirectable, removing FileRedirect and adding SyncCloseFD
88
const SpawnIOs = Memory{SpawnIO} # convenience name for readability (used for dispatch also to clearly distinguish from Vector{Redirectable})
89

90
function as_cpumask(cpus::Vector{UInt16})
×
91
    n = max(Int(maximum(cpus)), Int(ccall(:uv_cpumask_size, Cint, ())))
×
92
    cpumask = zeros(Bool, n)
×
93
    for i in cpus
×
94
        cpumask[i] = true
×
95
    end
×
96
    return cpumask
×
97
end
98

99
# handle marshalling of `Cmd` arguments from Julia to C
100
@noinline function _spawn_primitive(file, cmd::Cmd, stdio::SpawnIOs)
745✔
101
    loop = eventloop()
745✔
102
    cpumask = cmd.cpus
745✔
103
    cpumask === nothing || (cpumask = as_cpumask(cpumask))
745✔
104
    GC.@preserve stdio begin
745✔
105
        iohandles = Tuple{Cint, UInt}[ # assuming little-endian layout
2,209✔
106
            let h = rawhandle(io)
107
                h === C_NULL     ? (0x00, UInt(0)) :
2,234✔
108
                h isa OS_HANDLE  ? (0x02, UInt(cconvert(@static(Sys.iswindows() ? Ptr{Cvoid} : Cint), h))) :
109
                h isa Ptr{Cvoid} ? (0x04, UInt(h)) :
110
                error("invalid spawn handle $h from $io")
111
            end
112
            for io in stdio]
113
        syncd = Task[io.t for io in stdio if io isa SyncCloseFD]
745✔
114
        handle = Libc.malloc(_sizeof_uv_process)
745✔
115
        disassociate_julia_struct(handle)
745✔
116
        (; exec, flags, env, dir) = cmd
745✔
117
        flags ⊻= UV_PROCESS_WINDOWS_DISABLE_EXACT_NAME # libuv inverts the default for this, so flip this bit now
745✔
118
        iolock_begin()
745✔
119
        err = ccall(:jl_spawn, Int32,
745✔
120
                  (Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid},
121
                   Ptr{Tuple{Cint, UInt}}, Int,
122
                   UInt32, Ptr{Cstring}, Cstring, Ptr{Bool}, Csize_t, Ptr{Cvoid}),
123
            file, exec, loop, handle,
124
            iohandles, length(iohandles),
125
            flags,
126
            env === nothing ? C_NULL : env,
127
            isempty(dir) ? C_NULL : dir,
128
            cpumask === nothing ? C_NULL : cpumask,
129
            cpumask === nothing ? 0 : length(cpumask),
130
            @cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32)))
131
        if err == 0
745✔
132
            pp = Process(cmd, handle, syncd)
745✔
133
            associate_julia_struct(handle, pp)
745✔
134
        else
135
            ccall(:jl_forceclose_uv, Cvoid, (Ptr{Cvoid},), handle) # will call free on handle eventually
×
136
        end
137
        iolock_end()
745✔
138
    end
139
    if err != 0
745✔
140
        throw(_UVError("could not spawn " * repr(cmd), err))
×
141
    end
142
    return pp
745✔
143
end
144

145
_spawn(cmds::AbstractCmd) = _spawn(cmds, SpawnIOs())
234✔
146

147
function _spawn(cmd::AbstractCmd, stdios::Vector{Redirectable})
3✔
148
    pp = setup_stdios(stdios) do stdios
2,424✔
149
        return _spawn(cmd, stdios)
753✔
150
    end
151
    return pp
1,377✔
152
end
153

154
# optimization: we can spawn `Cmd` directly without allocating the ProcessChain
155
function _spawn(cmd::Cmd, stdios::SpawnIOs)
3✔
156
    isempty(cmd.exec) && throw(ArgumentError("cannot spawn empty command"))
789✔
157
    return _spawn_primitive(cmd.exec[1], cmd, stdios)
789✔
158
end
159

160
# assume that having a ProcessChain means that the stdio are setup
161
function _spawn(cmds::AbstractCmd, stdios::SpawnIOs)
1✔
162
    return _spawn(cmds, stdios, ProcessChain())
23✔
163
end
164

165
# helper function for making a copy of a SpawnIOs, with replacement
166
function _stdio_copy(stdios::SpawnIOs, fd::Int, @nospecialize replace)
504✔
167
    nio = max(fd, length(stdios))
506✔
168
    new = SpawnIOs(undef, nio)
506✔
169
    copyto!(fill!(new, devnull), stdios)
999✔
170
    new[fd] = replace
986✔
171
    return new
506✔
172
end
173

174
function _spawn(redirect::CmdRedirect, stdios::SpawnIOs, args...)
506✔
175
    fdnum = redirect.stream_no + 1
506✔
176
    io, close_io = setup_stdio(redirect.handle, redirect.readable)
506✔
177
    try
506✔
178
        stdios = _stdio_copy(stdios, fdnum, io)
506✔
179
        return _spawn(redirect.cmd, stdios, args...)
507✔
180
    finally
181
        close_io && close_stdio(io)
506✔
182
    end
183
end
184

185
function _spawn(cmds::OrCmds, stdios::SpawnIOs, chain::ProcessChain)
1✔
186
    in_pipe, out_pipe = link_pipe(false, false)
1✔
187
    try
1✔
188
        stdios_left = _stdio_copy(stdios, 2, out_pipe)
2✔
189
        _spawn(cmds.a, stdios_left, chain)
1✔
190
        stdios_right = _stdio_copy(stdios, 1, in_pipe)
2✔
191
        _spawn(cmds.b, stdios_right, chain)
1✔
192
    finally
193
        close_pipe_sync(out_pipe)
1✔
194
        close_pipe_sync(in_pipe)
1✔
195
    end
196
    return chain
1✔
197
end
198

199
function _spawn(cmds::ErrOrCmds, stdios::SpawnIOs, chain::ProcessChain)
×
200
    in_pipe, out_pipe = link_pipe(false, false)
×
201
    try
×
202
        stdios_left = _stdio_copy(stdios, 3, out_pipe)
×
203
        _spawn(cmds.a, stdios_left, chain)
×
204
        stdios_right = _stdio_copy(stdios, 1, in_pipe)
×
205
        _spawn(cmds.b, stdios_right, chain)
×
206
    finally
207
        close_pipe_sync(out_pipe)
×
208
        close_pipe_sync(in_pipe)
×
209
    end
210
    return chain
×
211
end
212

213
function _spawn(cmds::AndCmds, stdios::SpawnIOs, chain::ProcessChain)
×
214
    _spawn(cmds.a, stdios, chain)
×
215
    _spawn(cmds.b, stdios, chain)
×
216
    return chain
×
217
end
218

219
function _spawn(cmd::Cmd, stdios::SpawnIOs, chain::ProcessChain)
2✔
220
    isempty(cmd.exec) && throw(ArgumentError("cannot spawn empty command"))
2✔
221
    pp = _spawn_primitive(cmd.exec[1], cmd, stdios)
2✔
222
    push!(chain.processes, pp)
2✔
223
    return chain
2✔
224
end
225

226

227
# open the child end of each element of `stdios`, and initialize the parent end
228
function setup_stdios(f, stdios::Vector{Redirectable})
753✔
229
    nstdio = length(stdios)
753✔
230
    open_io = SpawnIOs(undef, nstdio)
753✔
231
    close_io = falses(nstdio)
753✔
232
    try
753✔
233
        for i in 1:nstdio
1,482✔
234
            open_io[i], close_io[i] = setup_stdio(stdios[i], i == 1)
2,259✔
235
        end
3,759✔
236
        pp = f(open_io)
753✔
237
        return pp
750✔
238
    finally
239
        for i in 1:nstdio
1,482✔
240
            close_io[i] && close_stdio(open_io[i])
2,259✔
241
        end
2,259✔
242
    end
243
end
244

245
function setup_stdio(stdio::PipeEndpoint, child_readable::Bool)
709✔
246
    if stdio.status == StatusInit
709✔
247
        # if the PipeEndpoint isn't open, set it to the parent end
248
        # and pass the other end to the child
249
        rd, wr = link_pipe(!child_readable, child_readable)
461✔
250
        try
461✔
251
            open_pipe!(stdio, child_readable ? wr : rd)
461✔
252
        catch
253
            close_pipe_sync(rd)
×
254
            close_pipe_sync(wr)
×
255
            rethrow()
×
256
        end
257
        child = child_readable ? rd : wr
461✔
258
        return (child, true)
461✔
259
    end
260
    # if it's already open, assume that it's already the child end
261
    # (since we can't do anything else)
262
    return (stdio, false)
248✔
263
end
264

265
function setup_stdio(stdio::Pipe, child_readable::Bool)
138✔
266
    if stdio.in.status == StatusInit && stdio.out.status == StatusInit
139✔
267
        link_pipe!(stdio)
1✔
268
    end
269
    io = child_readable ? stdio.out : stdio.in
278✔
270
    return (io, false)
139✔
271
end
272

273
setup_stdio(stdio::AbstractPipe, readable::Bool) =
23✔
274
    setup_stdio(readable ? pipe_reader(stdio) : pipe_writer(stdio), readable)
275

276
function setup_stdio(stdio::IOStream, child_readable::Bool)
61✔
277
    io = RawFD(fd(stdio))
61✔
278
    return (io, false)
61✔
279
end
280

281
function setup_stdio(stdio::FileRedirect, child_readable::Bool)
2✔
282
    if child_readable
2✔
283
        attr = JL_O_RDONLY
×
284
        perm = zero(S_IRUSR)
×
285
    else
286
        attr = JL_O_WRONLY | JL_O_CREAT
2✔
287
        attr |= stdio.append ? JL_O_APPEND : JL_O_TRUNC
2✔
288
        perm = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
2✔
289
    end
290
    io = Filesystem.open(stdio.filename, attr, perm)
4✔
291
    return (io, true)
2✔
292
end
293

294
# incrementally move data between an arbitrary IO and a system Pipe,
295
# including copying the EOF (shutdown) when finished
296
# TODO: probably more efficient (when valid) to use `stdio` directly as the
297
#       PipeEndpoint buffer field in some cases
298
function setup_stdio(stdio::IO, child_readable::Bool)
12✔
299
    parent = PipeEndpoint()
12✔
300
    rd, wr = link_pipe(!child_readable, child_readable)
12✔
301
    try
12✔
302
        open_pipe!(parent, child_readable ? wr : rd)
12✔
303
    catch
304
        close_pipe_sync(rd)
×
305
        close_pipe_sync(wr)
×
306
        rethrow()
×
307
    end
308
    child = child_readable ? rd : wr
12✔
309
    try
12✔
310
        let in = (child_readable ? parent : stdio),
24✔
311
            out = (child_readable ? stdio : parent),
312
            t = @async try
107✔
313
                write(in, out)
107✔
314
            catch ex
315
                @warn "Process I/O error" exception=(ex, catch_backtrace())
×
316
                rethrow()
107✔
317
            finally
318
                close(parent)
107✔
319
            end
320
            return (SyncCloseFD(child, t), true)
12✔
321
        end
322
    catch
323
        close_pipe_sync(child)
×
324
        rethrow()
×
325
    end
326
end
327

328
close_stdio(stdio) = close(stdio)
290✔
329
close_stdio(stdio::OS_HANDLE) = close_pipe_sync(stdio)
498✔
330
close_stdio(stdio::SyncCloseFD) = close_stdio(stdio.fd)
11✔
331

332
# INTERNAL
333
# pad out stdio to have at least three elements,
334
# passing either `devnull` or the corresponding `stdio`
335
# A Redirectable can be any of:
336
#   - A system IO handle, to be passed to the child
337
#   - An uninitialized pipe, to be created
338
#   - devnull (to pass /dev/null for 0-2, or to leave undefined for fd > 2)
339
#   - An Filesystem.File or IOStream object to redirect the output to
340
#   - A FileRedirect, containing a string specifying a filename to be opened for the child
341

342
spawn_opts_swallow(stdios::StdIOSet) = Redirectable[stdios...]
×
343
spawn_opts_inherit(stdios::StdIOSet) = Redirectable[stdios...]
×
344
spawn_opts_swallow(in::Redirectable=devnull, out::Redirectable=devnull, err::Redirectable=devnull) =
84✔
345
    Redirectable[in, out, err]
346
# pass original descriptors to child processes by default, because we might
347
# have already exhausted and closed the libuv object for our standard streams.
348
# ref issue #8529
349
spawn_opts_inherit(in::Redirectable=RawFD(0), out::Redirectable=RawFD(1), err::Redirectable=RawFD(2)) =
1,086✔
350
    Redirectable[in, out, err]
351

352
function eachline(cmd::AbstractCmd; keep::Bool=false)
5✔
353
    out = PipeEndpoint()
4✔
354
    processes = _spawn(cmd, Redirectable[devnull, out, stderr])
4✔
355
    # if the user consumes all the data, also check process exit status for success
356
    ondone = () -> (success(processes) || pipeline_error(processes); nothing)
9✔
357
    return EachLine(out, keep=keep, ondone=ondone)::EachLine
4✔
358
end
359

360
"""
361
    open(command, mode::AbstractString, stdio=devnull)
362

363
Run `command` asynchronously. Like `open(command, stdio; read, write)` except specifying
364
the read and write flags via a mode string instead of keyword arguments.
365
Possible mode strings are:
366

367
| Mode | Description | Keywords                         |
368
|:-----|:------------|:---------------------------------|
369
| `r`  | read        | none                             |
370
| `w`  | write       | `write = true`                   |
371
| `r+` | read, write | `read = true, write = true`      |
372
| `w+` | read, write | `read = true, write = true`      |
373
"""
374
function open(cmds::AbstractCmd, mode::AbstractString, stdio::Redirectable=devnull)
829✔
375
    if mode == "r+" || mode == "w+"
1,728✔
376
        return open(cmds, stdio, read = true, write = true)
159✔
377
    elseif mode == "r"
672✔
378
        return open(cmds, stdio)
439✔
379
    elseif mode == "w"
233✔
380
        return open(cmds, stdio, write = true)
233✔
381
    else
382
        throw(ArgumentError("mode must be \"r\", \"w\", \"r+\", or \"w+\", not $(repr(mode))"))
×
383
    end
384
end
385

386
# return a Process object to read-to/write-from the pipeline
387
"""
388
    open(command, stdio=devnull; write::Bool = false, read::Bool = !write)
389

390
Start running `command` asynchronously, and return a `process::IO` object.  If `read` is
391
true, then reads from the process come from the process's standard output and `stdio` optionally
392
specifies the process's standard input stream.  If `write` is true, then writes go to
393
the process's standard input and `stdio` optionally specifies the process's standard output
394
stream.
395
The process's standard error stream is connected to the current global `stderr`.
396
"""
397
function open(cmds::AbstractCmd, stdio::Redirectable=devnull; write::Bool=false, read::Bool=!write)
4,555✔
398
    if read && write
2,005✔
399
        stdio === devnull || throw(ArgumentError("no stream can be specified for `stdio` in read-write mode"))
159✔
400
        in = PipeEndpoint()
159✔
401
        out = PipeEndpoint()
159✔
402
        processes = _spawn(cmds, Redirectable[in, out, stderr])
243✔
403
        processes.in = in
159✔
404
        processes.out = out
159✔
405
    elseif read
1,846✔
406
        out = PipeEndpoint()
1,418✔
407
        processes = _spawn(cmds, Redirectable[stdio, out, stderr])
1,418✔
408
        processes.out = out
1,418✔
409
    elseif write
428✔
410
        in = PipeEndpoint()
428✔
411
        processes = _spawn(cmds, Redirectable[in, stdio, stderr])
588✔
412
        processes.in = in
428✔
413
    else
414
        stdio === devnull || throw(ArgumentError("no stream can be specified for `stdio` in no-access mode"))
×
415
        processes = _spawn(cmds, Redirectable[devnull, devnull, stderr])
×
416
    end
417
    return processes
2,005✔
418
end
419

420
"""
421
    open(f::Function, command, args...; kwargs...)
422

423
Similar to `open(command, args...; kwargs...)`, but calls `f(stream)` on the
424
resulting process stream, then closes the input stream and waits for the process
425
to complete. Return the value returned by `f` on success. Throw an error if the
426
process failed, or if the process attempts to print anything to stdout.
427
"""
428
function open(f::Function, cmds::AbstractCmd, args...; kwargs...)
1,154✔
429
    P = open(cmds, args...; kwargs...)
577✔
430
    function waitkill(P::Union{Process,ProcessChain})
577✔
431
        close(P)
13✔
432
        # shortly after we hope it starts cleanup and dies (from closing
433
        # stdio), we kill the process with SIGTERM (15) so that we can proceed
434
        # with throwing the error and hope it will exit soon from that
435
        local t = Timer(2) do t
13✔
436
            process_running(P) && kill(P)
1✔
437
        end
438
        # pass false to indicate that we do not care about data-races on the
439
        # Julia stdio objects after this point, since we already know this is
440
        # an error path and the state of them is fairly unpredictable anyways
441
        # in that case. Since we closed P some of those should come crumbling
442
        # down already, and we don't want to throw that error here either.
443
        wait(P, false)
13✔
444
        close(t)
13✔
445
    end
446
    ret = try
577✔
447
        f(P)
583✔
448
    catch
449
        waitkill(P)
25✔
450
        rethrow()
25✔
451
    end
452
    close(P.in)
552✔
453
    closestdio = @async begin
1,311✔
454
        # wait for P to complete (including sync'd), then mark the output streams for EOF (if applicable to that stream type)
455
        wait(P)
552✔
456
        err = P.err
552✔
457
        applicable(closewrite, err) && closewrite(err)
552✔
458
        out = P.out
552✔
459
        applicable(closewrite, out) && closewrite(out)
552✔
460
        nothing
552✔
461
    end
462
    # now verify that the output stream is at EOF, and the user didn't fail to consume it successfully
463
    # (we do not currently verify the user dealt with the stderr stream)
464
    if !(eof(P.out)::Bool)
552✔
465
        waitkill(P)
×
466
        throw(_UVError("open(do)", UV_EPIPE))
×
467
    end
468
    # make sure to closestdio is completely done to avoid data-races later
469
    wait(closestdio)
552✔
470
    success(P) || pipeline_error(P)
553✔
471
    return ret
551✔
472
end
473

474
"""
475
    read(command::Cmd)
476

477
Run `command` and return the resulting output as an array of bytes.
478
"""
479
function read(cmd::AbstractCmd)
437✔
480
    procs = open(cmd, "r", devnull)
437✔
481
    bytes = read(procs.out)
437✔
482
    success(procs) || pipeline_error(procs)
457✔
483
    return bytes::Vector{UInt8}
431✔
484
end
485

486
"""
487
    read(command::Cmd, String)
488

489
Run `command` and return the resulting output as a `String`.
490
"""
491
read(cmd::AbstractCmd, ::Type{String}) = String(read(cmd))::String
480✔
492

493
"""
494
    run(command, args...; wait::Bool = true)
495

496
Run a command object, constructed with backticks (see the [Running External Programs](@ref)
497
section in the manual). Throws an error if anything goes wrong, including the process
498
exiting with a non-zero status (when `wait` is true).
499

500
The `args...` allow you to pass through file descriptors to the command, and are ordered
501
like regular unix file descriptors (eg `stdin, stdout, stderr, FD(3), FD(4)...`).
502

503
If `wait` is false, the process runs asynchronously. You can later [`wait`](@ref) for it
504
and check its exit status by calling `success` on the returned process object. If the
505
`command` spawns only a single process, a `Process` object is returned and the
506
exit code can be retrieved via the `exitcode` field; see [`wait`](@ref) for more details.
507

508
When `wait` is false, the process' I/O streams are directed to `devnull`.
509
When `wait` is true, I/O streams are shared with the parent process.
510
Use [`pipeline`](@ref) to control I/O redirection.
511

512
See also: [`Cmd`](@ref).
513
"""
514
function run(cmds::AbstractCmd, args...; wait::Bool = true)
1,173✔
515
    if wait
415✔
516
        ps = _spawn(cmds, spawn_opts_inherit(args...))
657✔
517
        success(ps) || pipeline_error(ps)
389✔
518
    else
519
        stdios = spawn_opts_swallow(args...)
38✔
520
        ps = _spawn(cmds, stdios)
26✔
521
        # for each stdio input argument, guess whether the user
522
        # passed a `stdio` placeholder object as input, and thus
523
        # might be able to use the return AbstractProcess as an IO object
524
        # (this really only applies to PipeEndpoint, Pipe, TCPSocket, or an AbstractPipe wrapping one of those)
525
        if length(stdios) > 0
26✔
526
            in = stdios[1]
26✔
527
            isa(in, IO) && (ps.in = in)
26✔
528
            if length(stdios) > 1
26✔
529
                out = stdios[2]
26✔
530
                isa(out, IO) && (ps.out = out)
26✔
531
                if length(stdios) > 2
26✔
532
                    err = stdios[3]
26✔
533
                    isa(err, IO) && (ps.err = err)
26✔
534
                end
535
            end
536
        end
537
    end
538
    return ps
410✔
539
end
540

541
# some common signal numbers that are usually available on all platforms
542
# and might be useful as arguments to `kill` or testing against `Process.termsignal`
543
const SIGHUP  = 1
544
const SIGINT  = 2
545
const SIGQUIT = 3 # !windows
546
const SIGKILL = 9
547
const SIGPIPE = 13 # !windows
548
const SIGTERM = 15
549

550
function test_success(proc::Process)
681✔
551
    @assert process_exited(proc)
681✔
552
    if proc.exitcode < 0
681✔
553
        #TODO: this codepath is not currently tested
554
        throw(_UVError("could not start process " * repr(proc.cmd), proc.exitcode))
×
555
    end
556
    return proc.exitcode == 0 && proc.termsignal == 0
681✔
557
end
558

559
function success(x::Process)
337✔
560
    wait(x)
1,487✔
561
    return test_success(x)
1,487✔
562
end
563
success(procs::Vector{Process}) = mapreduce(success, &, procs)
18✔
564
success(procs::ProcessChain) = success(procs.processes)
18✔
565

566
"""
567
    success(command)
568

569
Run a command object, constructed with backticks (see the [Running External Programs](@ref)
570
section in the manual), and tell whether it was successful (exited with a code of 0).
571
An exception is raised if the process cannot be started.
572
"""
573
success(cmd::AbstractCmd) = success(_spawn(cmd))
234✔
574

575

576
"""
577
    ProcessFailedException
578

579
Indicates problematic exit status of a process.
580
When running commands or pipelines, this is thrown to indicate
581
a nonzero exit code was returned (i.e. that the invoked process failed).
582
"""
583
struct ProcessFailedException <: Exception
584
    procs::Vector{Process}
9✔
585
end
586
ProcessFailedException(proc::Process) = ProcessFailedException([proc])
9✔
587

588
function showerror(io::IO, err::ProcessFailedException)
9✔
589
    if length(err.procs) == 1
9✔
590
        proc = err.procs[1]
8✔
591
        println(io, "failed process: ", proc, " [", proc.exitcode, "]")
8✔
592
    else
593
        println(io, "failed processes:")
1✔
594
        for proc in err.procs
1✔
595
            println(io, "  ", proc, " [", proc.exitcode, "]")
2✔
596
        end
2✔
597
    end
598
end
599

600
function pipeline_error(proc::Process)
×
601
    if !proc.cmd.ignorestatus
22✔
602
        throw(ProcessFailedException(proc))
9✔
603
    end
604
    nothing
×
605
end
606

607
function pipeline_error(procs::ProcessChain)
×
608
    failed = Process[]
×
609
    for p = procs.processes
×
610
        if !test_success(p) && !p.cmd.ignorestatus
×
611
            push!(failed, p)
×
612
        end
613
    end
×
614
    isempty(failed) && return nothing
×
615
    throw(ProcessFailedException(failed))
×
616
end
617

618
"""
619
    kill(p::Process, signum=Base.SIGTERM)
620

621
Send a signal to a process. The default is to terminate the process.
622
Returns successfully if the process has already exited, but throws an
623
error if killing the process failed for other reasons (e.g. insufficient
624
permissions).
625
"""
626
function kill(p::Process, signum::Integer=SIGTERM)
1✔
627
    iolock_begin()
19✔
628
    if process_running(p)
4✔
629
        @assert p.handle != C_NULL
4✔
630
        err = ccall(:uv_process_kill, Int32, (Ptr{Cvoid}, Int32), p.handle, signum)
4✔
631
        if err != 0 && err != UV_ESRCH
4✔
632
            throw(_UVError("kill", err))
1✔
633
        end
634
    end
635
    iolock_end()
3✔
636
    nothing
3✔
637
end
638
kill(ps::Vector{Process}, signum::Integer=SIGTERM) = for p in ps; kill(p, signum); end
×
639
kill(ps::ProcessChain, signum::Integer=SIGTERM) = kill(ps.processes, signum)
×
640

641
"""
642
    getpid(process)::Int32
643

644
Get the child process ID, if it still exists.
645

646
!!! compat "Julia 1.1"
647
    This function requires at least Julia 1.1.
648
"""
649
function Libc.getpid(p::Process)
×
650
    # TODO: due to threading, this method is only weakly synchronized with the user application
651
    iolock_begin()
×
652
    ppid = Int32(0)
×
653
    if p.handle != C_NULL # e.g. process_running
×
654
        ppid = ccall(:jl_uv_process_pid, Int32, (Ptr{Cvoid},), p.handle)
×
655
    end
656
    iolock_end()
×
657
    ppid <= 0 && throw(_UVError("getpid", UV_ESRCH))
×
658
    return ppid
×
659
end
660

661
## process status ##
662

663
"""
664
    process_running(p::Process)
665

666
Determine whether a process is currently running.
667
"""
668
process_running(s::Process) = s.handle != C_NULL
3,192✔
669
process_running(s::Vector{Process}) = any(process_running, s)
×
670
process_running(s::ProcessChain) = process_running(s.processes)
×
671

672
"""
673
    process_exited(p::Process)
674

675
Determine whether a process has exited.
676
"""
677
process_exited(s::Process) = !process_running(s)
2,149✔
678
process_exited(s::Vector{Process}) = all(process_exited, s)
×
679
process_exited(s::ProcessChain) = process_exited(s.processes)
×
680

681
process_signaled(s::Process) = (s.termsignal > 0)
96✔
682

683
function process_status(s::Process)
9✔
684
    return process_running(s) ? "ProcessRunning" :
64✔
685
           process_signaled(s) ? "ProcessSignaled(" * string(s.termsignal) * ")" :
686
           process_exited(s) ? "ProcessExited(" * string(s.exitcode) * ")" :
687
           error("process status error")
688
end
689

690
function wait(x::Process, syncd::Bool=true)
711✔
691
    if !process_exited(x)
2,837✔
692
        iolock_begin()
607✔
693
        if !process_exited(x)
607✔
694
            preserve_handle(x)
607✔
695
            lock(x.exitnotify)
607✔
696
            iolock_end()
607✔
697
            try
607✔
698
                wait(x.exitnotify)
607✔
699
            finally
700
                unlock(x.exitnotify)
607✔
701
                unpreserve_handle(x)
607✔
702
            end
703
        else
704
            iolock_end()
×
705
        end
706
    end
707
    # and make sure all sync'd Tasks are complete too
708
    syncd && for t in x.syncd
705✔
709
        wait(t)
11✔
710
    end
11✔
711
    nothing
705✔
712
end
713

714
wait(x::ProcessChain, syncd::Bool=true) = foreach(p -> wait(p, syncd), x.processes)
×
715

716
show(io::IO, p::Process) = print(io, "Process(", p.cmd, ", ", process_status(p), ")")
40✔
717

718
# allow the elements of the Cmd to be accessed as an array or iterator
719
for f in (:length, :firstindex, :lastindex, :keys, :first, :last, :iterate)
720
    @eval $f(cmd::Cmd) = $f(cmd.exec)
52✔
721
end
722
Iterators.reverse(cmd::Cmd) = Iterators.reverse(cmd.exec)
×
723
eltype(::Type{Cmd}) = eltype(fieldtype(Cmd, :exec))
×
724
for f in (:iterate, :getindex)
725
    @eval $f(cmd::Cmd, i) = $f(cmd.exec, i)
16✔
726
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc