Issue #6154 has been updated by headius (Charles Nutter).


I am prepared to commit this fix if there's no other commentary.

A quick benchmark to show it in action: https://gist.github.com/headius/5315475

    $ ./ruby -I lib:ext:. nonblock_bench.rb 
      0.940000   0.170000   1.110000 (  1.113261)
      0.940000   0.170000   1.110000 (  1.104994)
      0.940000   0.160000   1.100000 (  1.108880)
      0.950000   0.170000   1.120000 (  1.107059)
      0.970000   0.170000   1.140000 (  1.148363)
    
    $ ./ruby -I lib:ext:.:ext/socket nonblock_bench.rb 
      0.660000   0.140000   0.800000 (  0.808189)
      0.680000   0.150000   0.830000 (  0.814997)
      0.670000   0.140000   0.810000 (  0.811901)
      0.670000   0.140000   0.810000 (  0.804447)
      0.670000   0.140000   0.810000 (  0.817231)
----------------------------------------
Feature #6154: Eliminate extending WaitReadable/Writable at runtime
https://bugs.ruby-lang.org/issues/6154#change-38238

Author: headius (Charles Nutter)
Status: Assigned
Priority: Normal
Assignee: headius (Charles Nutter)
Category: core
Target version: next minor


The nonblocking IO operations started extending WaitReadable or WaitWritable into the Errno::EAGAIN instance some time during the 1.9 series. This has a rather high cost, since a singleton class must be created and the global method cache must be flushed.

The attached patch instead creates two new classes of the following form, and raises them rather than raising a singleton EAGAIN:

class IO::EAGAINReadable < Errno::EAGAIN
  include WaitReadable
end

class IO::EAGAINWritable < Errno::EAGAIN
  include WaitWritable
end

The performance of repeatedly doing unsuccessful nonblocking reads improves by about 20%:

BEFORE:

system ~/projects/ruby $ ./ruby2.0.0 -rbenchmark -rsocket -e "sock = TCPSocket.new('localhost', 22); 10.times { puts Benchmark.measure { 100_000.times { begin; sock.read_nonblock(10); rescue IO::WaitReadable; end } } }"
  1.210000   0.110000   1.320000 (  1.328921)
  1.220000   0.120000   1.340000 (  1.326136)
  1.220000   0.110000   1.330000 (  1.334026)
  1.230000   0.110000   1.340000 (  1.349927)
  1.310000   0.130000   1.440000 (  1.426608)
  1.210000   0.110000   1.320000 (  1.333530)
  1.220000   0.120000   1.340000 (  1.330352)
  1.230000   0.110000   1.340000 (  1.350455)
  1.220000   0.120000   1.340000 (  1.327550)
  1.220000   0.110000   1.330000 (  1.337785)

AFTER:

system ~/projects/ruby $ ./ruby2.0.0 -rbenchmark -rsocket -e "sock = TCPSocket.new('localhost', 22); 10.times { puts Benchmark.measure { 100_000.times { begin; sock.read_nonblock(10); rescue IO::WaitReadable; end } } }"
  0.980000   0.110000   1.090000 (  1.092166)
  1.010000   0.120000   1.130000 (  1.129877)
  1.090000   0.120000   1.210000 (  1.202066)
  0.960000   0.110000   1.070000 (  1.076274)
  0.970000   0.100000   1.070000 (  1.078000)
  0.970000   0.110000   1.080000 (  1.078156)
  0.970000   0.110000   1.080000 (  1.078005)
  0.970000   0.110000   1.080000 (  1.078266)
  0.980000   0.110000   1.090000 (  1.093039)
  1.000000   0.110000   1.110000 (  1.112519)

This benchmark does not show the hidden cost of constantly invalidating the global method cache.

I also modified a similar case in OpenSSL, where it previously created an SSLError and extended WaitReadable into it.


-- 
http://bugs.ruby-lang.org/