Rails中的后台线程无法查看实例变量

时间:2013-07-17 20:18:11

标签: ruby ruby-on-rails-3

我需要从rails应用程序中收集一些数据,将其聚合,然后定期将其发送到远程服务器。我在application.rb中的全局变量(我知道,我知道)中实例化我的聚合类。

在我的聚合类中,我启动一个休眠10秒的线程,然后查看队列,处理数据并发送它。队列是存储在类的实例变量中的哈希。

在rails控制器中,我调用聚合器类中的方法来对哈希中的数据进行排队。当然,这与读取队列的后台任务不同。问题是后台任务永远不会在哈希中看到任何数据。在我的日志中,我在写入它时(从控制器线程)和当我从它(从后台线程)读取时打印出hash的object_id。散列#object_id匹配两个线程,但后台线程永远不会看到数据。

什么杀死我是这在rails之外运行良好。我已经设置了许多线程的测试,这些线程确实很重要,并且它工作正常(有一些线程保护,我没有显示清晰度)。有人知道object_id的匹配方式,但内容不一致吗?

class Aggregator

def initialize
  @q = {}
  @timer = nil
end

def start
  @timer = Thread.new do
    loop do
      sleep(10)
      flush_q
    end
  end
end

def flush_q
  logger.debug "flush: q.object_id = #{@q.object_id}"  # matches what I get below
  logger.debug "flush: q.length = #{@q.length}"   # always zero!
  @q.each_pair do |k,v|
    # pack it up and send it
  end
  @q.clear
end

def add(item)
  logger.debug "add: q.object_id = #{@q.object_id}"  # matches what I get above
  @q[item.name] ||= item
  logger.debug "add: q.length = #{@q.length}"   # increases with each add
  # not actually that simple, but not relevant
end

end

1 个答案:

答案 0 :(得分:0)

我将走出困境并假设您的代码是使用分叉应用服务器(例如独角兽或乘客)部署的。

这意味着您的应用程序已加载一次,然后从该主实例分叉新实例。分叉很便宜,这意味着应用程序的新实例可以非常快速地启动/关闭。

我相信您的聚合器实例正在此主进程中创建/启动。当这个分叉时,进程的整个内存空间被复制(所以在新进程中有一个聚合器实例,具有相同的对象id等等。)

但是,当仅分叉当前线程时,所以聚合器刷新仅发生在主进程中,但所有附加都发生在子进程中。您可以通过将Proccess.pid添加到您记录的内容来确认这一点 - 您应该看到您的日志记录来自2个不同的进程。

解决此问题的一种方法是在子进程分叉后启动/重新启动线程。如何执行此操作取决于应用程序的提供方式。使用独角兽,您可以通过after_fork方法在独角兽配置中执行此操作。有乘客你做

PhusionPassenger.on_event(:starting_worker_process) do |forked|
  if forked
    ...
  end
end