Rubyのバッチ処理でFile.deleteでたくさんのファイルを消していたらSystemStackError: stack level too deep
という見慣れないエラーが起きました。
再帰などしていないのに、スタックを使い切るのはなぜでしょう?
File.delete(*to_delete)
こんな感じで呼び出していたので、配列展開になにかあるのかな?と思って試してみました。
irb(main):041:0> p(*(1..130795).to_a)
(irb):41:in `p': stack level too deep (SystemStackError)
from (irb):41:in `<main>'
... 8 levels...
from /Users/shigerunakajima/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `load'
from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'
ところが、次のような関数呼び出しでない配列展開は
[*(1..130795).to_a]
時間は掛かりますが、正常に動きます。
原因はよくわからないのですが、簡易な再現手順がわかったので、ruby-jpで聞いてみました。
結論としては、引数の数が多すぎるそうです。
id:ku-ma-me さんに教えてもらいました。
p(*(1..130795).to_a)
の字面的に、引数が13万個あるように見えないのと、引数はスタックに積むものなので、言われてなるほどでした。
その他の面白情報
irbを使わないと限界数が232増えます。
~ ruby -e 'p(*(1..131027).to_a)'
-e:1:in `p': stack level too deep (SystemStackError)
from -e:1:in `<main>'
id:Pockeさん情報です。
実際に13万個引数がある関数を定義して呼び出すと、SystemStackErrorが起きます。
irb(main):001:0> eval "def hoge(arg0,#{(1..130794).map { |i| "arg#{i} = nil" }.j
oin(",") }); puts arg0; end"
=> :hoge
irb(main):002:0> hoge 0
(eval):1:in `hoge': stack level too deep (SystemStackError)
from (irb):2:in `<main>'
... 8 levels...
from /Users/shigerunakajima/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `load'
from /Users/shigerunakajima/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'
id:wakaba260yenさん情報です。
実行時間が長いので注意です。
JavaScriptでも同様に大量の引数をつけるとスタックオーバーフローが起きます。
> Math.min(...new Array(120615).fill(0))
Uncaught RangeError: Maximum call stack size exceeded
at Math.min (<anonymous>)
id:tompngさん情報です。