Skip to content

Commit 246e408

Browse files
authored
Fix JULIA_EXCLUSIVE setting affinity on non-worker threads (#57136)
With JULIA_EXCLUSIVE=1, we would try to give both worker and interactive threads an exclusive CPU, causing --threads=auto to produce a "Too many threads requested" error. Fixes #50702.
1 parent 899d2f5 commit 246e408

File tree

5 files changed

+40
-22
lines changed

5 files changed

+40
-22
lines changed

doc/src/manual/environment-variables.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,11 @@ during image compilation. Defaults to 0.
397397
### [`JULIA_EXCLUSIVE`](@id JULIA_EXCLUSIVE)
398398

399399
If set to anything besides `0`, then Julia's thread policy is consistent with
400-
running on a dedicated machine: the master thread is on proc 0, and threads are
401-
affinitized. Otherwise, Julia lets the operating system handle thread policy.
400+
running on a dedicated machine: each thread in the default threadpool is
401+
affinitized. [Interactive threads](@ref man-threadpools) remain under the
402+
control of the operating system scheduler.
403+
404+
Otherwise, Julia lets the operating system handle thread policy.
402405

403406
## Garbage Collection
404407

src/init.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,8 +907,8 @@ static NOINLINE void _finish_julia_init(JL_IMAGE_SEARCH rel, jl_ptls_t ptls, jl_
907907
jl_n_markthreads = 0;
908908
jl_n_sweepthreads = 0;
909909
jl_n_gcthreads = 0;
910-
jl_n_threads_per_pool[0] = 0; // Interactive threadpool
911-
jl_n_threads_per_pool[1] = 1; // Default threadpool
910+
jl_n_threads_per_pool[JL_THREADPOOL_ID_INTERACTIVE] = 0;
911+
jl_n_threads_per_pool[JL_THREADPOOL_ID_DEFAULT] = 1;
912912
} else {
913913
post_image_load_hooks();
914914
}

src/julia.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2053,6 +2053,9 @@ extern JL_DLLIMPORT _Atomic(int) jl_n_threads;
20532053
extern JL_DLLIMPORT int jl_n_gcthreads;
20542054
extern int jl_n_markthreads;
20552055
extern int jl_n_sweepthreads;
2056+
2057+
#define JL_THREADPOOL_ID_INTERACTIVE 0
2058+
#define JL_THREADPOOL_ID_DEFAULT 1
20562059
extern JL_DLLIMPORT int *jl_n_threads_per_pool;
20572060

20582061
// environment entries

src/threading.c

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -778,9 +778,9 @@ void jl_init_threading(void)
778778
}
779779

780780
jl_all_tls_states_size = nthreads + nthreadsi + ngcthreads;
781-
jl_n_threads_per_pool = (int*)malloc_s(2 * sizeof(int));
782-
jl_n_threads_per_pool[0] = nthreadsi;
783-
jl_n_threads_per_pool[1] = nthreads;
781+
jl_n_threads_per_pool = (int*)calloc_s(jl_n_threadpools * sizeof(int));
782+
jl_n_threads_per_pool[JL_THREADPOOL_ID_INTERACTIVE] = nthreadsi;
783+
jl_n_threads_per_pool[JL_THREADPOOL_ID_DEFAULT] = nthreads;
784784
assert(jl_all_tls_states_size > 0);
785785
jl_atomic_store_release(&jl_all_tls_states, (jl_ptls_t*)calloc(jl_all_tls_states_size, sizeof(jl_ptls_t)));
786786
jl_atomic_store_release(&jl_n_threads, jl_all_tls_states_size);
@@ -793,7 +793,10 @@ uv_barrier_t thread_init_done;
793793
void jl_start_threads(void)
794794
{
795795
int nthreads = jl_atomic_load_relaxed(&jl_n_threads);
796-
int ngcthreads = jl_n_gcthreads;
796+
int ninteractive_threads = jl_n_threads_per_pool[JL_THREADPOOL_ID_INTERACTIVE];
797+
int ndefault_threads = jl_n_threads_per_pool[JL_THREADPOOL_ID_DEFAULT];
798+
int nmutator_threads = nthreads - jl_n_gcthreads;
799+
797800
int cpumasksize = uv_cpumask_size();
798801
char *cp;
799802
int i, exclusive;
@@ -808,36 +811,43 @@ void jl_start_threads(void)
808811
if (cp && strcmp(cp, "0") != 0)
809812
exclusive = 1;
810813

811-
// exclusive use: affinitize threads, master thread on proc 0, rest
812-
// according to a 'compact' policy
814+
// exclusive use: affinitize threads, master thread on proc 0, threads in
815+
// default pool according to a 'compact' policy
813816
// non-exclusive: no affinity settings; let the kernel move threads about
814817
if (exclusive) {
815-
if (nthreads > jl_cpu_threads()) {
818+
if (ndefault_threads > jl_cpu_threads()) {
816819
jl_printf(JL_STDERR, "ERROR: Too many threads requested for %s option.\n", MACHINE_EXCLUSIVE_NAME);
817820
exit(1);
818821
}
819822
memset(mask, 0, cpumasksize);
820-
mask[0] = 1;
821-
uvtid = uv_thread_self();
822-
uv_thread_setaffinity(&uvtid, mask, NULL, cpumasksize);
823-
mask[0] = 0;
823+
824+
// If there are no interactive threads, the master thread is in the
825+
// default pool and we must affinitize it
826+
if (ninteractive_threads == 0) {
827+
mask[0] = 1;
828+
uvtid = uv_thread_self();
829+
uv_thread_setaffinity(&uvtid, mask, NULL, cpumasksize);
830+
mask[0] = 0;
831+
}
824832
}
825833

826834
// create threads
827835
uv_barrier_init(&thread_init_done, nthreads);
828836

829837
// GC/System threads need to be after the worker threads.
830-
int nmutator_threads = nthreads - ngcthreads;
831-
832838
for (i = 1; i < nmutator_threads; ++i) {
833839
jl_threadarg_t *t = (jl_threadarg_t *)malloc_s(sizeof(jl_threadarg_t)); // ownership will be passed to the thread
834840
t->tid = i;
835841
t->barrier = &thread_init_done;
836842
uv_thread_create(&uvtid, jl_threadfun, t);
837-
if (exclusive) {
838-
mask[i] = 1;
843+
844+
// Interactive pool threads get the low IDs, so check if this is a
845+
// default pool thread. The master thread is already on CPU 0.
846+
if (exclusive && i >= ninteractive_threads) {
847+
assert(i - ninteractive_threads < cpumasksize);
848+
mask[i - ninteractive_threads] = 1;
839849
uv_thread_setaffinity(&uvtid, mask, NULL, cpumasksize);
840-
mask[i] = 0;
850+
mask[i - ninteractive_threads] = 0;
841851
}
842852
uv_thread_detach(&uvtid);
843853
}

test/threads.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,11 @@ if AFFINITY_SUPPORTED
123123
end
124124
end
125125

126-
function get_nthreads(options = ``; cpus = nothing)
126+
function get_nthreads(options = ``; cpus = nothing, exclusive = false)
127127
cmd = `$(Base.julia_cmd()) --startup-file=no $(options)`
128128
cmd = `$cmd -e "print(Threads.threadpoolsize())"`
129-
cmd = addenv(cmd, "JULIA_EXCLUSIVE" => "0", "JULIA_NUM_THREADS" => "auto")
129+
cmd = addenv(cmd, "JULIA_EXCLUSIVE" => exclusive ? "1" : "0",
130+
"JULIA_NUM_THREADS" => "auto")
130131
if cpus !== nothing
131132
cmd = setcpuaffinity(cmd, cpus)
132133
end
@@ -138,6 +139,7 @@ end
138139
allowed_cpus = findall(uv_thread_getaffinity())
139140
if length(allowed_cpus) 2
140141
@test get_nthreads() 2
142+
@test get_nthreads(exclusive = true) 2
141143
@test get_nthreads(cpus = allowed_cpus[1:1]) == 1
142144
@test get_nthreads(cpus = allowed_cpus[2:2]) == 1
143145
@test get_nthreads(cpus = allowed_cpus[1:2]) == 2

0 commit comments

Comments
 (0)