| 758 | } |
| 759 | |
| 760 | static void extract_worker(int worker_id, void *ctx_ptr) { |
| 761 | extract_ctx_t *ec = ctx_ptr; |
| 762 | extract_worker_state_t *ws = &ec->workers[worker_id]; |
| 763 | |
| 764 | /* Lazy gbuf creation */ |
| 765 | if (!ws->local_gbuf) { |
| 766 | ws->local_gbuf = cbm_gbuf_new_shared_ids(ec->project_name, ec->repo_path, ec->shared_ids); |
| 767 | } |
| 768 | |
| 769 | /* Pull files from shared atomic counter */ |
| 770 | while (SKIP_ONE) { |
| 771 | int sort_pos = |
| 772 | atomic_fetch_add_explicit(&ec->next_file_idx, SKIP_ONE, memory_order_relaxed); |
| 773 | if (sort_pos >= ec->file_count) { |
| 774 | break; |
| 775 | } |
| 776 | if (atomic_load_explicit(ec->cancelled, memory_order_relaxed)) { |
| 777 | break; |
| 778 | } |
| 779 | |
| 780 | /* Memory back-pressure (large repos): if the process is over its RSS |
| 781 | * budget, reclaim this thread's freed pages and nap so peer workers can |
| 782 | * finish their current file and return memory before this worker adds |
| 783 | * another parse working set. Caps the concurrent extraction transient |
| 784 | * near the budget instead of letting all workers parse their biggest |
| 785 | * files at once. Self-disabling when the budget is unset (tests) or RSS |
| 786 | * is under budget; bounded spins avoid deadlock when the resident graph |
| 787 | * is itself near budget (then proceed with a soft overshoot). |
| 788 | * |
| 789 | * Futility latch: when a FULL nap cycle ends still over budget, the |
| 790 | * resident floor — not transients — holds the memory; napping again on |
| 791 | * the next pull cannot reclaim it and only idles workers (linux kernel: |
| 792 | * one full cycle per pull ≈ 390 s at 79% avg CPU). Latch bp_futile and |
| 793 | * proceed with the soft overshoot; the over-budget probe below re-arms |
| 794 | * the gate as soon as RSS drains under budget. */ |
| 795 | if (cbm_mem_budget() > 0) { |
| 796 | bool over = cbm_mem_over_budget(); |
| 797 | bool futile = atomic_load_explicit(&ec->bp_futile, memory_order_relaxed) != 0; |
| 798 | if (over && !futile) { |
| 799 | cbm_mem_collect(); |
| 800 | atomic_fetch_add_explicit(&g_bp_nap_cycles, SKIP_ONE, memory_order_relaxed); |
| 801 | int bp = 0; |
| 802 | for (; bp < PP_BACKPRESSURE_MAX_SPINS && cbm_mem_over_budget() && |
| 803 | !atomic_load_explicit(ec->cancelled, memory_order_relaxed); |
| 804 | bp++) { |
| 805 | struct timespec nap = {0, PP_BACKPRESSURE_NAP_NS}; |
| 806 | cbm_nanosleep(&nap, NULL); |
| 807 | } |
| 808 | if (bp == PP_BACKPRESSURE_MAX_SPINS && cbm_mem_over_budget()) { |
| 809 | /* Log only the 0→1 transition: all workers race into the |
| 810 | * gate before anyone latches, so a plain store would WARN |
| 811 | * once per worker (12 lines per latch event). */ |
| 812 | if (atomic_exchange_explicit(&ec->bp_futile, 1, memory_order_relaxed) == 0) { |
| 813 | cbm_log_warn("mem.backpressure.futile", "action", "soft_overshoot"); |
| 814 | } |
| 815 | } |
| 816 | } else if (!over && futile) { |
| 817 | atomic_store_explicit(&ec->bp_futile, 0, memory_order_relaxed); |
nothing calls this directly
no test coverage detected