Skip to content

Commit d501456

Browse files
committed
Support running commands with cpumask
1 parent 44ed4bd commit d501456

File tree

6 files changed

+85
-12
lines changed

6 files changed

+85
-12
lines changed

base/cmd.jl

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,30 @@ const UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = UInt8(1 << 2)
77
const UV_PROCESS_DETACHED = UInt8(1 << 3)
88
const UV_PROCESS_WINDOWS_HIDE = UInt8(1 << 4)
99

10+
const RawCPUMask = Union{Nothing,Vector{Cchar}}
11+
1012
struct Cmd <: AbstractCmd
1113
exec::Vector{String}
1214
ignorestatus::Bool
1315
flags::UInt32 # libuv process flags
1416
env::Union{Vector{String},Nothing}
1517
dir::String
18+
cpumask::RawCPUMask
1619
Cmd(exec::Vector{String}) =
17-
new(exec, false, 0x00, nothing, "")
18-
Cmd(cmd::Cmd, ignorestatus, flags, env, dir) =
20+
new(exec, false, 0x00, nothing, "", nothing)
21+
Cmd(cmd::Cmd, ignorestatus, flags, env, dir, cpumask = nothing) =
1922
new(cmd.exec, ignorestatus, flags, env,
20-
dir === cmd.dir ? dir : cstr(dir))
23+
dir === cmd.dir ? dir : cstr(dir), cpumask)
2124
function Cmd(cmd::Cmd; ignorestatus::Bool=cmd.ignorestatus, env=cmd.env, dir::AbstractString=cmd.dir,
25+
cpumask::RawCPUMask = cmd.cpumask,
2226
detach::Bool = 0 != cmd.flags & UV_PROCESS_DETACHED,
2327
windows_verbatim::Bool = 0 != cmd.flags & UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS,
2428
windows_hide::Bool = 0 != cmd.flags & UV_PROCESS_WINDOWS_HIDE)
2529
flags = detach * UV_PROCESS_DETACHED |
2630
windows_verbatim * UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
2731
windows_hide * UV_PROCESS_WINDOWS_HIDE
2832
new(cmd.exec, ignorestatus, flags, byteenv(env),
29-
dir === cmd.dir ? dir : cstr(dir))
33+
dir === cmd.dir ? dir : cstr(dir), cpumask)
3034
end
3135
end
3236

@@ -287,6 +291,25 @@ function addenv(cmd::Cmd, env::Vector{<:AbstractString}; inherit::Bool = true)
287291
return addenv(cmd, Dict(k => v for (k, v) in eachsplit.(env, "=")); inherit)
288292
end
289293

294+
"""
295+
setcpus(original_command::Cmd, cpus) -> command::Cmd
296+
297+
Set the CPU affinity of the `command` by a list of CPU IDs (1-based) `cpus`. Passing
298+
`cpus = nothing` means to unset the CPU affinity if the `original_command` has any.
299+
300+
This is supported on Unix and Windows but not in macOS.
301+
"""
302+
function setcpus end
303+
setcpus(cmd::Cmd, ::Nothing) = Cmd(cmd; cpumask = nothing)
304+
function setcpus(cmd::Cmd, cpus::AbstractVector{<:Integer})
305+
n = max(maximum(cpus), ccall(:uv_cpumask_size, Cint, ()))
306+
cpumask = zeros(Cchar, n)
307+
for i in cpus
308+
cpumask[i] = true
309+
end
310+
return Cmd(cmd; cpumask)
311+
end
312+
290313
(&)(left::AbstractCmd, right::AbstractCmd) = AndCmds(left, right)
291314
redir_out(src::AbstractCmd, dest::AbstractCmd) = OrCmds(src, dest)
292315
redir_err(src::AbstractCmd, dest::AbstractCmd) = ErrOrCmds(src, dest)

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,7 @@ export
941941
run,
942942
setenv,
943943
addenv,
944+
setcpus,
944945
success,
945946
withenv,
946947

base/process.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ const SpawnIOs = Vector{Any} # convenience name for readability
8989
err = ccall(:jl_spawn, Int32,
9090
(Cstring, Ptr{Cstring}, Ptr{Cvoid}, Ptr{Cvoid},
9191
Ptr{Tuple{Cint, UInt}}, Int,
92-
UInt32, Ptr{Cstring}, Cstring, Ptr{Cvoid}),
92+
UInt32, Ptr{Cstring}, Cstring, Ptr{Cchar}, Csize_t, Ptr{Cvoid}),
9393
file, exec, loop, handle,
9494
iohandles, length(iohandles),
9595
flags,
9696
env === nothing ? C_NULL : env,
9797
isempty(dir) ? C_NULL : dir,
98+
cmd.cpumask === nothing ? C_NULL : cmd.cpumask,
99+
cmd.cpumask === nothing ? 0 : length(cmd.cpumask),
98100
@cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32)))
99101
end
100102
if err != 0

src/jl_uv.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ JL_DLLEXPORT void jl_uv_disassociate_julia_struct(uv_handle_t *handle)
290290
JL_DLLEXPORT int jl_spawn(char *name, char **argv,
291291
uv_loop_t *loop, uv_process_t *proc,
292292
uv_stdio_container_t *stdio, int nstdio,
293-
uint32_t flags, char **env, char *cwd, uv_exit_cb cb)
293+
uint32_t flags, char **env, char *cwd, char* cpumask,
294+
size_t cpumask_size, uv_exit_cb cb)
294295
{
295296
uv_process_options_t opts = {0};
296297
opts.stdio = stdio;
@@ -300,8 +301,8 @@ JL_DLLEXPORT int jl_spawn(char *name, char **argv,
300301
// unused fields:
301302
//opts.uid = 0;
302303
//opts.gid = 0;
303-
//opts.cpumask = NULL;
304-
//opts.cpumask_size = 0;
304+
opts.cpumask = cpumask;
305+
opts.cpumask_size = cpumask_size;
305306
opts.cwd = cwd;
306307
opts.args = argv;
307308
opts.stdio_count = nstdio;

test/print_process_affinity.jl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
const pthread_t = Culong
4+
const uv_thread_t = pthread_t
5+
6+
function uv_thread_getaffinity()
7+
masksize = ccall(:uv_cpumask_size, Cint, ())
8+
self = ccall(:uv_thread_self, uv_thread_t, ())
9+
selfref = Ref(self)
10+
cpumask = zeros(Cchar, masksize)
11+
err = ccall(
12+
:uv_thread_getaffinity,
13+
Cint,
14+
(Ptr{uv_thread_t}, Ptr{Cchar}, Cssize_t),
15+
selfref,
16+
cpumask,
17+
masksize,
18+
)
19+
@assert err == 0
20+
n = findlast(isone, cpumask)
21+
resize!(cpumask, n)
22+
return cpumask
23+
end
24+
25+
function print_process_affinity()
26+
isfirst = true
27+
for (i, m) in enumerate(uv_thread_getaffinity())
28+
if m != 0
29+
if isfirst
30+
isfirst = false
31+
else
32+
print(",")
33+
end
34+
print(i)
35+
end
36+
end
37+
println()
38+
end
39+
40+
if abspath(PROGRAM_FILE) == @__FILE__
41+
print_process_affinity()
42+
end

test/threads.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ let cmd = `$(Base.julia_cmd()) --depwarn=error --rr-detach --startup-file=no thr
1010
end
1111
end
1212

13+
function run_with_affinity(cpus)
14+
script = joinpath(@__DIR__, "print_process_affinity.jl")
15+
return readchomp(setcpus(`$(Base.julia_cmd()) $script`, cpus))
16+
end
17+
1318
# issue #34415 - make sure external affinity settings work
1419
if Sys.islinux()
1520
const SYS_rrcall_check_presence = 1008
1621
global running_under_rr() = 0 == ccall(:syscall, Int,
1722
(Int, Int, Int, Int, Int, Int, Int),
1823
SYS_rrcall_check_presence, 0, 0, 0, 0, 0, 0)
19-
if Sys.CPU_THREADS > 1 && Sys.which("taskset") !== nothing && !running_under_rr()
20-
run_with_affinity(spec) = readchomp(`taskset -c $spec $(Base.julia_cmd()) -e "run(\`taskset -p \$(getpid())\`)"`)
21-
@test endswith(run_with_affinity("1"), "2")
22-
@test endswith(run_with_affinity("0,1"), "3")
24+
if Sys.CPU_THREADS > 1 && !running_under_rr()
25+
@test run_with_affinity([2]) == "2"
26+
@test run_with_affinity([1, 2]) == "1,2"
2327
end
2428
end
2529

0 commit comments

Comments
 (0)