From 6e3c16825b70a7322cb2a58e7b03ae87921de3a2 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 15 Sep 2025 10:47:26 +0200 Subject: [PATCH] Fix race condition in `rake parse:topgems` `block.call(queue.shift) until queue.empty?` isn't thread safe. If there is one element left in the queue, two or more thread may see `queue.empty?` return `false` and call the blocking `shift` ending in a deadlock. The proper way to synchronize threads with a queue is to close the queue once no more elements need to be added. This should fix the occasional `fatal: No live threads left. Deadlock? (fatal)` failures on CI. --- rakelib/lex.rake | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rakelib/lex.rake b/rakelib/lex.rake index 327339f75d..23807a81b6 100644 --- a/rakelib/lex.rake +++ b/rakelib/lex.rake @@ -62,11 +62,10 @@ module Prism def parallelize(items, &block) Thread.abort_on_exception = true - queue = Queue.new - items.each { |item| queue << item } + queue = Queue.new(items).close workers = - ENV.fetch("WORKERS") { 16 }.to_i.times.map do + ENV.fetch("WORKERS", "16").to_i.times.map do parallelize_thread(queue, &block) end @@ -77,7 +76,11 @@ module Prism # Create a new thread with a minimal number of locals that it can access. def parallelize_thread(queue, &block) - Thread.new { block.call(queue.shift) until queue.empty? } + Thread.new do + while item = queue.pop + block.call(item) + end + end end end end