http连接断了之后,服务器怎么办

Database and Ruby, Python, History


和同事遇到一个问题,发现 gateway 报超时错误。但是查看应用的日志,却是一切正常。但是我记得之前遇到过因为超时,无法返回日志的情况。

Puma 是如何工作的

Puma 官网有文档,以及视频介绍 Puma 是如何工作的。Puma 会有多个进程,每个进程有多个线程,监听 3000 端口。

当有请求来的时候,会建立一个 socket,然后将请求转发给应用(Rails application)处理,处理完了以后再写回 socket。

当超时的时候

我搭了一个 API,这个 API 会在 90 秒后返回结果。

# controller.rb
  def index
    sleep(90)

    render json: "ok"
  end

然后在 terminal 里面调用 API,并设置 30 秒后 timeout。

curl -m 30 -v http://localhost:3001/api

30 秒之后,curl 断开连接。

* Operation timed out after 30002 milliseconds with 0 bytes received
* Closing connection 0
curl: (28) Operation timed out after 30002 milliseconds with 0 bytes received

观察 TCP 连接开始 4 次分手了。

netstat -tln | grep 3001
tcp4      14      0  127.0.0.1.3001                                127.0.0.1.51756                               CLOSE_WAIT
tcp4       0      0  127.0.0.1.51756                               127.0.0.1.3001                                FIN_WAIT_2

再过了 30 秒,连接彻底断开,netstat已经查不到这个端口了。

30 秒之后,应用返回 200 response。

Completed 200 OK in 90001ms (Views: 0.2ms | ActiveRecord: 0.0ms | Allocations: 597)

难道不报错?

再去看 Puma 的代码。

request.rbhandle_request中,Puma 会调用应用处理请求,并返回 response。整个请求做了异常处理,而且在我们的场景里面,这里不会报错。

status, headers, res_body = @thread_pool.with_force_shutdown do
            @app.call(env)
          end

这之后,Puma 会把res_body写入socket

fast_write io, lines.to_s

fast_write没有作异常处理,但是在handle_request里面却又一个ensure将整个异常给”吃”掉,不处理fast_write中出现的任何错误。

如果在ensure的前面插入rescue Exception => e,并将exception打印出来,就会看到下面的错误。可以看到,Puma 不能够正常写回 response,但是却不做任何处理。所以应用端对于 TCP 连接断开是无感知的,web server 这端虽然报错了,但不打印出来。

Socket timeout writing data
/Users/pzhong/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.7/lib/puma/request.rb:241:in `rescue in fast_write'
/Users/pzhong/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.7/lib/puma/request.rb:232:in `fast_write'
/Users/pzhong/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.7/lib/puma/request.rb:154:in `handle_request'
/Users/pzhong/.rbenv/versions/3.1.1/lib/ruby/gems/3.1.0/gems/puma-5.6.7/lib/puma/server.rb:445:in `process_client'