使用`ActiveRecord with_connection do`&ActionController :: Live时出现线程错误

but*_*ton 33 ruby multithreading connection-pooling ruby-on-rails actioncontroller

主要编辑:自从最初发现这个问题以来,我已将其削减到下面.我认为现在对这个问题的描述略微准确.因此,对OP的评论可能不完全相关.

编辑在rails/puma项目中发布的轻微修改版本:https://github.com/rails/rails/issues/21209,https://github.com/puma/puma/issues/758

编辑现在转载OS X和Rainbows

简介: 当使用Puma并运行长时间运行的连接时,我一直收到与跨越线程的ActiveRecord连接相关的错误.这表现在消息 message type 0x## arrived from server while idle 和锁定(崩溃)服务器中.

设置:

  • Ubuntu 15/OSX Yosemite
  • PostgreSQL(9.4)/ MySQL(mysqld 5.6.25-0ubuntu0.15.04.1)
  • Ruby - MRI 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]/ Rubiniusrbx-2.5.8
  • 导轨(4.2.3,4.2.1)
  • Puma(2.12.2,2.11)
  • pg(pg-0.18.2)/ mysql2

请注意,并非所有上述版本的组合都已尝试过.首先列出的版本是我目前正在测试的版本.

  • rails new issue-test
  • 添加路线 get 'events' => 'streaming#events'
  • 添加控制器 streaming_controller.rb
  • 设置数据库的东西(pool: 2但看到不同的池大小)

码:

class StreamingController < ApplicationController

  include ActionController::Live

  def events
    begin
      response.headers["Content-Type"] = "text/event-stream"
      sse = SSE.new(response.stream)
      sse.write( {:data => 'starting'} , {:event => :version_heartbeat})
      ActiveRecord::Base.connection_pool.release_connection
      while true do
        ActiveRecord::Base.connection_pool.with_connection do |conn|
          ActiveRecord::Base.connection.query_cache.clear
          logger.info 'START'
          conn.execute 'SELECT pg_sleep(3)'
          logger.info 'FINISH'
          sse.write( {:data => 'continuing'}, {:event => :version_heartbeat})
          sleep 0.5
         end
      end
    rescue IOError
    rescue ClientDisconnected
    ensure
      logger.info 'Ensuring event stream is closed'
      sse.close
    end
    render nothing: true
  end
end
Run Code Online (Sandbox Code Playgroud)

Puma配置:

workers 1
threads 2, 2
#...
bind "tcp://0.0.0.0:9292"

#...
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end
Run Code Online (Sandbox Code Playgroud)
  • 运行服务器 puma -e production -C path/to/puma/config/production.rb

测试脚本:

#!/bin/bash

timeout 30 curl -vS http://0.0.0.0/events &
timeout 5 curl -vS http://0.0.0.0/events &
timeout 30 curl -vS http://0.0.0.0/events
Run Code Online (Sandbox Code Playgroud)

这相当一致地导致应用程序服务器的完全锁定(在PostgreSQL中,请参阅注释).可怕的消息来自libpq:

message type 0x44 arrived from server while idle
message type 0x43 arrived from server while idle
message type 0x5a arrived from server while idle
message type 0x54 arrived from server while idle
Run Code Online (Sandbox Code Playgroud)

在"现实世界"中,我有相当多的额外元素,这个问题随机出现.我的研究表明,这条消息来自libpq"通信问题,可能是在不同线程中使用连接"的潜台词.最后,在写这篇文章时,我在任何日志中都没有一条消息就锁定了服务器.

那么,问题是:

  1. 我所遵循的模式在某种程度上是不合法的吗?我错了什么[sed |理解]?
  2. 在这里处理数据库连接的"标准"是什么,应该避免这些问题?
  3. 你能看到一种可靠再现的方法吗?

要么

  1. 这里的根本问题是什么,我该如何解决?

MySQL的

如果运行MySQL,消息有点不同,应用程序恢复(虽然我不确定它是否处于某种未定义状态):

F, [2015-07-30T14:12:07.078215 #15606] FATAL -- : 
ActiveRecord::StatementInvalid (Mysql2::Error: This connection is in use by: #<Thread:0x007f563b2faa88@/home/dev/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/actionpack-4.2.3/lib/action_controller/metal/live.rb:269 sleep>: SELECT  `tasks`.* FROM `tasks`  ORDER BY `tasks`.`id` ASC LIMIT 1):
Run Code Online (Sandbox Code Playgroud)

but*_*ton 1

警告:将“答案”解读为“似乎有所作为”


如果我将控制器块更改为如下所示,我不会看到问题发生:

begin
  #...
  while true do
    t = Thread.new do #<<<<<<<<<<<<<<<<<
        ActiveRecord::Base.connection_pool.with_connection do |conn|
            #...
        end
     end
     t.join #<<<<<<<<<<<<<<<<<
  end
  #...
rescue IOError
#...
Run Code Online (Sandbox Code Playgroud)

但我不知道这是否真的解决了问题,还是只是让问题变得极不可能。我也无法真正理解为什么这会产生影响。

将此作为解决方案发布,以防有​​帮助,但仍在深入研究该问题。