Skip to content

Commit d3c8eb9

Browse files
authored
gc scheduler synchronization fixes to 1.10 (#53661)
Cherry-pick the parts of #53355 which are relevant to the 1.10 GC.
1 parent 2a49cfe commit d3c8eb9

File tree

1 file changed

+24
-38
lines changed

1 file changed

+24
-38
lines changed

src/gc.c

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,9 +2719,7 @@ void gc_mark_and_steal(jl_ptls_t ptls)
27192719
jl_gc_markqueue_t *mq = &ptls->mark_queue;
27202720
jl_gc_markqueue_t *mq_master = NULL;
27212721
int master_tid = jl_atomic_load(&gc_master_tid);
2722-
if (master_tid == -1) {
2723-
return;
2724-
}
2722+
assert(master_tid != -1);
27252723
mq_master = &gc_all_tls_states[master_tid]->mark_queue;
27262724
void *new_obj;
27272725
jl_gc_chunk_t c;
@@ -2812,61 +2810,49 @@ size_t gc_count_work_in_queue(jl_ptls_t ptls) JL_NOTSAFEPOINT
28122810
* Correctness argument for the mark-loop termination protocol.
28132811
*
28142812
* Safety properties:
2815-
* - No work items shall be in any thread's queues when `gc_mark_loop_barrier` observes
2813+
* - No work items shall be in any thread's queues when `gc_should_mark` observes
28162814
* that `gc_n_threads_marking` is zero.
28172815
*
28182816
* - No work item shall be stolen from the master thread (i.e. mutator thread which started
28192817
* GC and which helped the `jl_n_markthreads` - 1 threads to mark) after
2820-
* `gc_mark_loop_barrier` observes that `gc_n_threads_marking` is zero. This property is
2818+
* `gc_should_mark` observes that `gc_n_threads_marking` is zero. This property is
28212819
* necessary because we call `gc_mark_loop_serial` after marking the finalizer list in
28222820
* `_jl_gc_collect`, and want to ensure that we have the serial mark-loop semantics there,
28232821
* and that no work is stolen from us at that point.
28242822
*
28252823
* Proof:
2826-
* - Suppose the master thread observes that `gc_n_threads_marking` is zero in
2827-
* `gc_mark_loop_barrier` and there is a work item left in one thread's queue at that point.
2828-
* Since threads try to steal from all threads' queues, this implies that all threads must
2829-
* have tried to steal from the queue which still has a work item left, but failed to do so,
2830-
* which violates the semantics of Chase-Lev's work-stealing queue.
2831-
*
2832-
* - Let E1 be the event "master thread writes -1 to gc_master_tid" and E2 be the even
2833-
* "master thread observes that `gc_n_threads_marking` is zero". Since we're using
2834-
* sequentially consistent atomics, E1 => E2. Now suppose one thread which is spinning in
2835-
* `gc_should_mark` tries to enter the mark-loop after E2. In order to do so, it must
2836-
* increment `gc_n_threads_marking` to 1 in an event E3, and then read `gc_master_tid` in an
2837-
* event E4. Since we're using sequentially consistent atomics, E3 => E4. Since we observed
2838-
* `gc_n_threads_marking` as zero in E2, then E2 => E3, and we conclude E1 => E4, so that
2839-
* the thread which is spinning in `gc_should_mark` must observe that `gc_master_tid` is -1
2840-
* and therefore won't enter the mark-loop.
2824+
* - If a thread observes that `gc_n_threads_marking` is zero inside `gc_should_mark`, that
2825+
* means that no thread has work on their queue, this is guaranteed because a thread may only exit
2826+
* `gc_mark_and_steal` when its own queue is empty, this information is synchronized by the
2827+
* seq-cst fetch_add to a thread that is in `gc_should_mark`. `gc_queue_observer_lock`
2828+
* guarantees that once `gc_n_threads_marking` reaches zero, no thread will increment it again,
2829+
* because incrementing is only legal from inside the lock. Therefore, no thread will reenter
2830+
* the mark-loop after `gc_n_threads_marking` reaches zero.
28412831
*/
28422832

2843-
int gc_should_mark(jl_ptls_t ptls)
2833+
int gc_should_mark(void)
28442834
{
28452835
int should_mark = 0;
2846-
int n_threads_marking = jl_atomic_load(&gc_n_threads_marking);
2847-
// fast path
2848-
if (n_threads_marking == 0) {
2849-
return 0;
2850-
}
28512836
uv_mutex_lock(&gc_queue_observer_lock);
28522837
while (1) {
2853-
int tid = jl_atomic_load(&gc_master_tid);
2854-
// fast path
2855-
if (tid == -1) {
2856-
break;
2857-
}
2858-
n_threads_marking = jl_atomic_load(&gc_n_threads_marking);
2859-
// fast path
2838+
int n_threads_marking = jl_atomic_load(&gc_n_threads_marking);
28602839
if (n_threads_marking == 0) {
28612840
break;
28622841
}
2842+
int tid = jl_atomic_load_relaxed(&gc_master_tid);
2843+
assert(tid != -1);
28632844
size_t work = gc_count_work_in_queue(gc_all_tls_states[tid]);
28642845
for (tid = gc_first_tid; tid < gc_first_tid + jl_n_markthreads; tid++) {
2865-
work += gc_count_work_in_queue(gc_all_tls_states[tid]);
2846+
jl_ptls_t ptls2 = gc_all_tls_states[tid];
2847+
if (ptls2 == NULL) {
2848+
continue;
2849+
}
2850+
work += gc_count_work_in_queue(ptls2);
28662851
}
28672852
// if there is a lot of work left, enter the mark loop
28682853
if (work >= 16 * n_threads_marking) {
2869-
jl_atomic_fetch_add(&gc_n_threads_marking, 1);
2854+
jl_atomic_fetch_add(&gc_n_threads_marking, 1); // A possibility would be to allow a thread that found lots
2855+
// of work to increment this
28702856
should_mark = 1;
28712857
break;
28722858
}
@@ -2878,22 +2864,22 @@ int gc_should_mark(jl_ptls_t ptls)
28782864

28792865
void gc_wake_all_for_marking(jl_ptls_t ptls)
28802866
{
2881-
jl_atomic_store(&gc_master_tid, ptls->tid);
28822867
uv_mutex_lock(&gc_threads_lock);
2883-
jl_atomic_fetch_add(&gc_n_threads_marking, 1);
28842868
uv_cond_broadcast(&gc_threads_cond);
28852869
uv_mutex_unlock(&gc_threads_lock);
28862870
}
28872871

28882872
void gc_mark_loop_parallel(jl_ptls_t ptls, int master)
28892873
{
28902874
if (master) {
2875+
jl_atomic_store(&gc_master_tid, ptls->tid);
2876+
jl_atomic_fetch_add(&gc_n_threads_marking, 1);
28912877
gc_wake_all_for_marking(ptls);
28922878
gc_mark_and_steal(ptls);
28932879
jl_atomic_fetch_add(&gc_n_threads_marking, -1);
28942880
}
28952881
while (1) {
2896-
int should_mark = gc_should_mark(ptls);
2882+
int should_mark = gc_should_mark();
28972883
if (!should_mark) {
28982884
break;
28992885
}

0 commit comments

Comments
 (0)