終了したスレッドの process_instances からの削除漏れを修正
Solid Queue 1.3.0以降で発生していた、スレッド終了時の管理データ不整合によるクラッシュが解消されました。replace_thread メソッドが process_instances からエントリを削除しないため、同じ終了スレッドが繰り返し検出され NoMethodError が発生していた問題に対処しています。
背景
Solid Queue 1.3.0で導入された非同期モードにおいて、スレッドが異常終了した際に NoMethodError: undefined method 'instantiate' for nil が発生する問題が報告されていました(#710)。この問題は、スレッド管理の2つのデータ構造である configured_processes と process_instances の同期が取れていないことが原因でした。
スレッドが終了すると、replace_thread は configured_processes からエントリを削除していましたが、process_instances には終了したスレッドのエントリが残り続けていました。次の監視ループで check_and_replace_terminated_processes が同じ終了スレッドを再度検出し、replace_thread を呼び出すと、既に削除済みの configured_processes から nil が返されてクラッシュしていました。
技術的な変更
AsyncSupervisor#replace_thread メソッドが、ForkSupervisor#replace_fork と同じパターンに統一されました。
変更前:
def replace_thread(thread_id, instance)
SolidQueue.instrument(:replace_thread, supervisor_pid: ::Process.pid) do |payload|
payload[:thread] = instance
error = Processes::ThreadTerminatedError.new(instance.name)
release_claimed_jobs_by(instance, with_error: error)
start_process(configured_processes.delete(thread_id))
end
end
変更後:
def replace_thread(thread_id)
SolidQueue.instrument(:replace_thread, supervisor_pid: ::Process.pid) do |payload|
if (instance = process_instances.delete(thread_id))
payload[:thread] = instance
error = Processes::ThreadTerminatedError.new(instance.name)
release_claimed_jobs_by(instance, with_error: error)
start_process(configured_processes.delete(thread_id))
end
end
end
process_instances.delete(thread_id) を最初に実行し、その戻り値を if 文でガードすることで、エントリが既に削除されている場合は何もしません。これにより、同じ終了スレッドが2回目以降に検出されても安全に処理がスキップされます。
また、replace_thread の引数から instance パラメータが削除され、thread_id のみを受け取るようになりました。呼び出し側の check_and_replace_terminated_processes も対応して変更されています:
terminated_threads.each { |thread_id, _| replace_thread(thread_id) }
設計判断
ForkSupervisor との一貫性 を重視した変更です。PR本文で言及されているように、ForkSupervisor#replace_fork は既に同じパターンを採用していました。プロセスフォーク版とスレッド版の監視機構で、終了したワーカーの置き換えロジックを統一することで、コードの保守性と理解しやすさが向上しています。
process_instances から先に削除する順序も重要です。configured_processes を先に削除すると、process_instances に残ったエントリが次のループで再検出され、今回修正した問題と同じ状況が発生します。削除の原子性を delete の戻り値チェックで保証することで、競合状態に対する防御も実現しています。
まとめ
本PRは、スレッド終了時の管理データ構造の同期を確立した修正です。process_instances からのエントリ削除を追加し、既存の ForkSupervisor と同じガードパターンを適用することで、Solid Queue 1.3.0以降で報告されていたクラッシュを解消しています。プロセスフォーク版とスレッド版の実装を揃えたことで、今後の保守においても一貫性が保たれます。