这个 Ruby 代码会在 Puma 下使用非阻塞 I/O 吗?

Will this Ruby code use non-blocking I/O under Puma?

我正在调查遗留 Rails 应用程序中的一些瓶颈。除其他外,它向(防火墙)后端服务器发出一些 HTTP 请求,并将响应流式传输到客户端,使用以下 class 的对象作为控制器响应主体:

class Streamer
  def initialize(url)
    @url = url
  end

  def each 
    client = HTTPClient.new
    client.receive_timeout = 7200
    client.send_timeout = 3600
    client.connect_timeout = 7200
    client.keep_alive_timeout = 3600

    client.get_content(@url) { |chunk|
      yield chunk
    }

  end
end

我是 Ruby I/O 和线程方面的新手,我也不是 Rails 方面的专家。设计假设似乎是这个(运行 在 MRI 上)只会为每个块锁定一次解释器,并且其他线程可以在数据从 HTTPClient 传入或去往时执行输出到浏览器——这个假设是否有效?还是这段代码会让 Puma 的其他线程饿死?

答案既是又不是,主要是因为问题中的术语有些不清楚。

Blocking IO表示IO在等待数据。 HTTPClient 使用阻塞 IO。它等待数据可用,并且仅在接收到数据后 returns。

非阻塞 IO 意味着 IO 层 return 立即执行,即使没有数据存在(通常出现错误 EAGAIN 或 EWOULDBLOCK)。

HTTPClient 模块阻塞控制流,等待 HTTP 服务器的响应。

但是,这不会阻止其他线程在 "parallel" 中 运行ning(或者,在 Ruby MRI 的情况下,交错)。

The design assumption seems to be that this (running on MRI) is only going to lock the interpreter once per chunk, and that other threads can execute while data is either coming in from the HTTPClient, or going out to the browser -- is that assumption valid?

总的来说,假设是正确的(或者足够接近以至于无关紧要)。

Ruby MRI 中的 GIL(全局解释器锁)强制执行 Ruby 代码的单线程。它阻止了真正的并行性,让人想起单核 CPU 机器使用的多线程模型。

然而,由于 IO 代码是在 GIL 之外执行的,其他线程将 运行 而 IO 正在等待传入数据(阻塞在 writereadselect).

一般来说,Ruby 将管理线程调度以交错 Ruby 线程执行,允许并发执行(尽管不是并行执行)。

Or is this code going to starve Puma's other threads?

我不太清楚你所说的饥饿是什么意思...

该代码将使 Puma 的线程 饿死,但线程本身将继续 运行 不受阻碍。

澄清一下:

Puma 有一个用于 HTTP 请求和任务处理的线程池。线程数量有限

HTTPClient 线程将 "stuck",等待 IO 完成,不会 return 到线程池,减少池(作为一个整体)并可能导致资源匮乏。

例如,如果所有线程都在等待 HTTPClient 响应,则在线程再次可用之前不会处理任何请求(HTTPClient 和其余工作已完成)。

另一方面,线程本身将允许其他线程并发运行,使用交错调度。所以,其他线程不会饿死,但整个 Puma 可能会饿死。