2021/11/05 今回の気になった bugs.ruby のチケット
今週は Proc#bind_call(obj)
を追加する提案などがありました。
[Bug #18282] Rails CI raises Segmentation fault with ruby 3.1.0dev supporting Class#descendants
class C end 100000.times { Class.new(C) } p C.descendants
[Bug #18283] Creating a subclass in Ractor dumps core
- 上の [Bug #18282] 関連のバグ報告
- 以下のコードでも Ruby がクラッシュする
class C end (1..10).map { Ractor.new { 100000.times { Class.new(C) } } }.each {|r| r.take }
- Bug #18282 と同じ原因なんですかね
[Bug #18243] Ractor.make_shareable does not freeze the receiver of a Proc but allows accessing ivars of it
- 次のように
Ractor
内で別のRactor
のオブジェクトが書き換えられてしまうバグ報告
class C attr_accessor :foo def setter_proc Ractor.make_shareable(-> v { @foo = v }) end end c = C.new c.foo = 1 p c # => #<C:0x0000559bf1df5880 @foo=1> # インスタンス変数を書き換える proc を生成 # c 自体は freeze されてない proc = c.setter_proc p c.frozen? # => false # Ractor 内で setter_proc を呼び出すと c のオブジェクトが書き換えら得てしまう # これがバグ Ractor.new(proc) { |s| s.call(42) }.take p c # => #<C:0x0000559bf1df5880 @foo=42>
ostruct
を Ractor 対応している時に見つけたらしい
[Feature #18276] Proc#bind_call(obj)
same as obj.instance_exec(..., &proc_obj)
obj.instance_exec(..., &proc_obj)
と同じ意味のProc#bind_call(obj)
を追加する提案proc_obj.bind_call(...)
がobj.instance_exec(..., &proc_obj)
と同じ意味になる
- [Bug #18243] への対応として
Ractor
の共有可能オブジェクトのProc#call
を禁止してobj.instance_exec(..., &proc_obj)
で呼び出すようにすることも考慮しているとのこと- そのショートカットとして
Proc#bind_call
を使用する想定
- そのショートカットとして
Proc#bind_call
を利用すると以下のようにblock
オブジェクトも渡せるようようになるので個人的には普通にほしい
pr = proc { |*args, &block| # ... block.call(something) } # pr に対してブロック引数を渡すことができない obj.instance_exec(arg, &pr) # bind_call だと pr に対してブロック引数を渡すことができる pr.bind_call(obj, arg) do |something| # ... end
- これを読んで昔同じようなことをする gem つくったのを思い出した
require "proc/unbind" using Proc::Unbind class X attr_accessor :value end x = X.new set = proc { |x| @value = x } # Proc#rebind で特定のオブジェクトに bind する set.rebind(x).call(42) p x.value # => 42
[Misc #18285] NoMethodError#message uses a lot of CPU/is really expensive to call
NoMethodError#message
がパフォーマンス的にコストがかかるというチケットdd-trace-rb
の内部テストで問題になったらしい- the unexpected cost of ruby's NoMethodError exception
- 実際にチケットで提示されているベンチマーク
require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'benchmark-ips' end puts RUBY_DESCRIPTION class GemInformation def get_no_method_error method_does_not_exist rescue => e e end def get_runtime_error raise 'Another Error' rescue => e e end # NoMethodError#message でこのメソッドが呼び出される # Rails の Controller だと複雑な #inspect が実装されておりそれがボトルネックになっているらしい def inspect Gem::Specification._all.inspect end end NO_METHOD_ERROR_INSTANCE = GemInformation.new.get_no_method_error RUNTIME_ERROR_INSTANCE = GemInformation.new.get_runtime_error Benchmark.ips do |x| x.config(:time => 5, :warmup => 2) x.report("no method error message cost") { NO_METHOD_ERROR_INSTANCE.message } x.report("runtime error message cost") { RUNTIME_ERROR_INSTANCE.message } x.compare! end __END__ output: ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux] Warming up -------------------------------------- no method error message cost 20.000 i/100ms runtime error message cost 1.620M i/100ms Calculating ------------------------------------- no method error message cost 249.716 (± 6.0%) i/s - 1.260k in 5.068273s runtime error message cost 16.046M (± 1.1%) i/s - 81.020M in 5.049800s Comparison: runtime error message cost: 16045983.5 i/s no method error message cost: 249.7 i/s - 64256.99x (± 0.00) slower
- この例だとめっちゃ遅いですね
- これは
NoMethodError#message
が#inspect
を呼び出しているのが原因らしい
class NoInspect end begin NoInspect.new.hoge rescue => e pp e.message # => "undefined method `hoge' for #<NoInspect:0x00005644abd35978>" end class WithInspect def inspect "My WithInspect" end end begin WithInspect.new.hoge rescue => e # メッセージに WithInspect#inspect も含められる # error: undefined method `hoge' for My WithInspect:WithInspect (NoMethodError) pp e.message # => "undefined method `hoge' for My WithInspect:WithInspect" end
- ちなみに Ruby 3.0 以前は
#inspect
の文字数が66文字以上だと#message
に含まれていなかった
class WithInspect def inspect "A" * 66 end end begin WithInspect.new.hoge rescue => e pp e.message # Ruby 2.7 => "undefined method `hoge' for #<WithInspect:0x000055a784c8a560>" # Ruby 3.0 => "undefined method `hoge' for AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:WithInspect" end
[Feature #18273] Class#subclasses
Class#descendants
に関連して『自身の子クラスのみ』を返すClass#subclasses
を追加する提案
class Class def subclasses descendants.select { |descendant| descendant.superclass == self } end end class A; end class B < A; end class C < B; end class D < A; end # 継承リストに A が含まれているクラスをすべて返す pp A.descendants # => [B, C] # A を直接継承しているクラスのみ返す pp A.subclasses # => [D, B]
- 確かにほしくなりそう