Repository: JuliaLang/julia
File: src/gc.c (C)

Code before:
    double old_ratio = (double)promoted_bytes/(double)heap_size;
    if (heap_size > max_total_memory * 0.8 || old_ratio > 0.15)
        next_sweep_full = 1;
    else
        next_sweep_full = 0;
    if (heap_size > max_total_memory * 0.8 || thrashing)
        under_pressure = 1;
    // sweeping is over
    // 7. if it is a quick sweep, put back the remembered objects in queued state
    // so that we don't trigger the barrier again on them.
    assert(gc_n_threads);
    for (int t_i = 0; t_i < gc_n_threads; t_i++) {
        jl_ptls_t ptls2 = gc_all_tls_states[t_i];
        if (ptls2 == NULL)
            continue;
        if (!sweep_full) {
            for (int i = 0; i < ptls2->heap.remset->len; i++) {
                void *ptr = ptls2->heap.remset->items[i];
                jl_astaggedvalue(ptr)->bits.gc = GC_MARKED;
            }
        }
        else {
            ptls2->heap.remset->len = 0;
        }
        // free empty GC state for threads that have exited
        if (jl_atomic_load_relaxed(&ptls2->current_task) == NULL) {
            jl_thread_heap_t *heap = &ptls2->heap;
            if (heap->weak_refs.len == 0)
                small_arraylist_free(&heap->weak_refs);
            if (heap->live_tasks.len == 0)
                small_arraylist_free(&heap->live_tasks);
            if (heap->remset->len == 0)
                arraylist_free(heap->remset);
            if (heap->last_remset->len == 0)
                arraylist_free(heap->last_remset);
            if (ptls2->finalizers.len == 0)
                arraylist_free(&ptls2->finalizers);
            if (ptls2->sweep_objs.len == 0)
                arraylist_free(&ptls2->sweep_objs);
            gc_move_to_global_page_pool(&ptls2->page_metadata_buffered);
            if (ptls2->page_metadata_allocd.bottom == NULL) {
                free(ptls2);
                gc_all_tls_states[t_i] = NULL;
            }
        }
    }

#ifdef __GLIBC__
    if (sweep_full) {
        // issue #30653
        // empirically, the malloc runaway seemed to occur within a growth gap

Review comment (suggestion):
We probably need to check that the thread for which we're doing the cleanup is actually a mutator thread (i.e. `ptls2->tid < gc_first_tid  || ptls2->tid >= gc_first_tid + jl_n_gcthreads`).

Note that, since GC threads have no tasks associated with them, this version is also doing the cleanup for the GC threads, and will set `gc_all_tls_states[<SOME_GC_THREAD_TID>]` to `NULL`, causing us to "lose" that GC thread.

Code after:

    // We separate the update of the graph from the update of live_bytes here
    // so that the sweep shows a downward trend in memory usage.
    jl_timing_counter_inc(JL_TIMING_COUNTER_HeapSize, gc_num.allocd);

    jl_gc_markqueue_t *mq = &ptls->mark_queue;

    uint64_t gc_start_time = jl_hrtime();
    uint64_t mutator_time = gc_end_time == 0 ? old_mut_time : gc_start_time - gc_end_time;
    uint64_t before_free_heap_size = jl_atomic_load_relaxed(&gc_heap_stats.heap_size);
    int64_t last_perm_scanned_bytes = perm_scanned_bytes;
    uint64_t start_mark_time = jl_hrtime();
    JL_PROBE_GC_MARK_BEGIN();
    {
        JL_TIMING(GC, GC_Mark);

        // 1. fix GC bits of objects in the remset.
        assert(gc_n_threads);
        for (int t_i = 0; t_i < gc_n_threads; t_i++) {
            jl_ptls_t ptls2 = gc_all_tls_states[t_i];
            if (ptls2 != NULL)
                gc_premark(ptls2);
        }

        assert(gc_n_threads);
        int single_threaded_mark = (jl_n_markthreads == 0 || gc_heap_snapshot_enabled);
        for (int t_i = 0; t_i < gc_n_threads; t_i++) {
            jl_ptls_t ptls2 = gc_all_tls_states[t_i];
            jl_ptls_t ptls_dest = ptls;
            jl_gc_markqueue_t *mq_dest = mq;
            if (!single_threaded_mark) {
                ptls_dest = gc_all_tls_states[gc_first_tid + t_i % jl_n_markthreads];
                mq_dest = &ptls_dest->mark_queue;
            }
            if (ptls2 != NULL) {
                // 2.1. mark every thread local root
                gc_queue_thread_local(mq_dest, ptls2);
                // 2.2. mark any managed objects in the backtrace buffer
                // TODO: treat these as roots for gc_heap_snapshot_record
                gc_queue_bt_buf(mq_dest, ptls2);
                // 2.3. mark every object in the `last_remsets` and `rem_binding`
                gc_queue_remset(ptls_dest, ptls2);
            }
        }

        // 3. walk roots
        gc_mark_roots(mq);
        if (gc_cblist_root_scanner) {
            gc_invoke_callbacks(jl_gc_cb_root_scanner_t,
                gc_cblist_root_scanner, (collection));
        }