Ruby
Rails

Rubyのat_exitとParallel(マルチプロセス処理)でおかしなことを起こしてしまった話


at_exitとは

RubyにはKernel#at_exitというメソッドがあります。

これは、Rubyの処理の終了時に実行する処理を追加出来るようなメソッドで、使い方によっては非常に便利に使えるものです。

(よく調べてないですが結構色々なGemで使用されているらしいです)


Parallelとは

Rubyにおいてマルチプロセス/スレッドの処理を簡単に使えるようにしてくれるGemです。

Rubyで並列処理を行いたい場合はよく使うと思います。


今回起きちゃった問題

at_exitとParallelによるマルチプロセス処理を同時に使ったことで、おかしな挙動が置きました。

具体的にはこんな感じ↓


test.rb

at_exit do

puts "run at_exit!"
end

Parallel.each(1..4, in_processes: 4) do |num|
sleep num
puts "sleep done! #{num}"
end


このファイルを実行した結果が↓

$ ruby at_exit_and_multi_process_test.rb

sleep done! 1
run at_exit!
sleep done! 2
run at_exit!
sleep done! 3
run at_exit!
sleep done! 4
run at_exit!
run at_exit!

at_exitが5回も走ってる!!:fearful:

基本的にat_exitを使用する場合、処理の最後に1回だけ走って欲しいという感じの使い方をすると思うのですが、これだとよろしくないですね。

原因はまあなんとなく察しがつくと思います。

at_exit内の処理はrubyの処理の終了時にフックされるわけですが、その「rubyの処理の終了」が、各子プロセスで発生してしまい、親プロセスの状態をforkしている子プロセスでも同じat_exitの処理が走ってしまったという話ですね。(多分)

さらに親プロセスの終了分まで含めて5回も走ってしまったわけです。

ちなみにマルチスレッド処理では問題なく動作し、結果は下記のようになります↓

$ ruby at_exit_and_multi_thread_test.rb

sleep done! 1
sleep done! 2
sleep done! 3
sleep done! 4
run at_exit!

多分at_exitとParallelを同時に使用している人があまりいないのか、ググってもあまりヒットしなかったので記事にしてみました。

考えてみれば全然わかることではあるのですが、特に意識せずにやってしまって挙動がおかしくなってしまったので皆様ご注意を:wave:


参考

https://docs.ruby-lang.org/ja/latest/method/Kernel/m/at_exit.html

https://techracho.bpsinc.jp/hachi8833/2018_05_22/56171

https://qiita.com/sekido-ts/items/5dc9c88d877bb2a1df0b

https://qiita.com/Kohei909Otsuka/items/26be74de803d195b37bd