From: Eric Wong Date: 2018-01-24T22:01:43+00:00 Subject: [ruby-core:85082] Re: [Ruby trunk Feature#13618][Assigned] [PATCH] auto fiber schedule for rb_wait_for_single_fd and rb_waitpid > Thinking about this even more; I don't think it's possible to > preserve round-robin recv_io/accept behavior I want from > blocking on native threads when sharing descriptors between > multiple processes. ``` The following example hopefully clarifies why I care about maintaining blocking I/O behavior in some places despite relying on non-blocking I/O for light-weight threading. # With non-blocking accept; PIDs do not share fairly: $ NONBLOCK=1 ruby fairness_test.rb PID accept count 5240 55 5220 42 5216 36 5242 109 5230 57 5208 26 5227 53 5212 26 5223 46 5236 43 total: 493 # With blocking accept on Linux; each process gets a fair share: $ NONBLOCK=0 ruby fairness_test.rb PID accept count 5271 50 5278 50 5275 50 5282 49 5286 49 5290 49 5295 49 5298 49 5303 49 5306 49 total: 493 For servers which only handle one client-per-process (e.g. Apache prefork), unfairness is preferable because the busiest process will be hottest in CPU cache. For everything else that serves multiple clients in a single process, fair sharing is preferable. This will apply to Guilds in the future, too. More information about this behavior I rely on is here: http://www.citi.umich.edu/projects/linux-scalability/reports/accept.html require 'socket' require 'thread' require 'io/nonblock' Thread.abort_on_exception = STDOUT.sync = true host = '127.0.0.1' srv = TCPServer.new(host, 0) srv.nonblock = true if ENV['NONBLOCK'].to_i != 0 port = srv.addr[1] pipe = IO.pipe nr = 10 running = true trap(:INT) { running = false } pids = nr.times.map do fork do pipe[0].close q = Queue.new # per-process Queue Thread.new do # dedicated accept thread q.push(srv.accept) while running q.push(nil) end while accepted = q.pop # n.b. a real server would do processing, here, maybe spawning # a new Thread/Fiber/Threadlet pipe[1].write("#$$ #{accepted.fileno}\n") accepted.close end end end pipe[1].close sleep(1) # wait for children to start cleanup = SizedQueue.new(1024) Thread.new do cleanup.pop.close while true end Thread.new do loop do cleanup.push(TCPSocket.new(host, port)) sleep(0.01) rescue => e break end end Thread.new { sleep(5); running = false } counts = Hash.new(0) at_exit do tot = 0 puts "PID\taccept count" counts.each { |pid, n| puts "#{pid}\t#{n}"; tot += n } puts "total: #{tot}" end case line = pipe[0].gets when /\A(\d+) / counts[$1] += 1 else running = false Process.waitall end while running ``` Unsubscribe: