🎃
[Feature #19694] Regexp のオブジェクトごとにタイムアウトを設定できるようにする提案
[Feature #19694] Add Regexp#timeout= setter
- Ruby 3.2 で次のように正規表現マッチのタイムアウトを設定することができるようになった
# 正規表現マッチのタイムアウトを設定する
# 0.2秒以上かかったら例外が発生する
Regexp.timeout = 0.2
begin
# めっちゃ時間がかかる正規表現マッチ
"a" * 1000000 + "x" =~ /^(a*)\1b?a*$/
rescue => e
pp e
# => #<Regexp::TimeoutError: regexp match timeout>
end
- このタイムアウトをインスタンスオブジェクトごとに設定する
Regexp#timeout=
を追加したいという提案 - モチベーションとしてはアプリケーション上で存在しているグローバルな正規表現に対して個々でタイムアウトを設定したいとのこと
- 以下のようなイメージ
# 正規表現リテラルは暗黙的に freeze されているので dup する必要がある
emoji_filter_pattern = %r{
(?<!#{Regexp.quote(ZERO_WIDTH_JOINER)})
#{EmojiFilter.unicodes_pattern}
(?!#{Regexp.union(EmojiFilter::MODIFIER_CHAR_MAP.keys.map { |k| Regexp.quote k })})
}x.dup
emoji_filter_pattern.timeout = 1.0
# timeout を設定した後に不変にする
emoji_filter_pattern.freeze
- 以下のように
Regexp.new
にtimeout:
キーワード引数を渡して定義すればいいんじゃないか、とコメントされてている- https://bugs.ruby-lang.org/issues/19694#note-1
- これは Ruby 3.2 でも動作する
emoji_filter_pattern = %r{
(?<!#{Regexp.quote(ZERO_WIDTH_JOINER)})
#{EmojiFilter.unicodes_pattern}
(?!#{Regexp.union(EmojiFilter::MODIFIER_CHAR_MAP.keys.map { |k| Regexp.quote k })})
}x
emoji_filter_pattern = Regexp.new(emoji_filter_pattern, timeout: 1.0).freeze
- 別の話としてそもそも正規表現自体を線形にしたほうがよいのではないか、みたいなコメントもある
- 実行するタイミングや CPU の使用率、スケジューリングなどに依存する可能性があるので
- その場合は
Regexp.timeout=
でも不十分になる - https://bugs.ruby-lang.org/issues/19694#note-4
Discussion