From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-3.0 required=3.0 tests=AWL,BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,RCVD_IN_DNSWL_MED,SPF_PASS,T_RP_MATCHES_RCVD shortcircuit=no autolearn=ham autolearn_force=no version=3.4.0 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 318811F404 for ; Wed, 24 Jan 2018 22:01:51 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id 77A2F120B32; Thu, 25 Jan 2018 07:01:49 +0900 (JST) Received: from dcvr.yhbt.net (dcvr.yhbt.net [64.71.152.64]) by neon.ruby-lang.org (Postfix) with ESMTPS id 45EA912098A for ; Thu, 25 Jan 2018 07:01:45 +0900 (JST) Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id 9DF311F404; Wed, 24 Jan 2018 22:01:43 +0000 (UTC) Date: Wed, 24 Jan 2018 22:01:43 +0000 From: Eric Wong To: ruby-core@ruby-lang.org Message-ID: <20180124220143.GA5600@80x24.org> References: <20180123173133.GB14355@starla> <20180124215113.GA29994@starla> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20180124215113.GA29994@starla> X-ML-Name: ruby-core X-Mail-Count: 85082 Subject: [ruby-core:85082] Re: [Ruby trunk Feature#13618][Assigned] [PATCH] auto fiber schedule for rb_wait_for_single_fd and rb_waitpid X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" > 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 ```