【问题标题】:Can't get ActionController::Live to play nice with ActiveSupport::Notifications无法让 ActionController::Live 与 ActiveSupport::Notifications 配合得很好
【发布时间】:2014-01-30 11:07:17
【问题描述】:

我一直在寻找在不使用轮询的情况下使用 ActionController::Live 触发服务器端事件的方法。似乎唯一记录在案的 Live 发布/订阅解决方案是使用 Redis,但我真的很想使用我目前拥有的堆栈。我遇到了 ActiveSupport::Notifications,这似乎正是我想要的。

但是,当我几乎在 Redis 的位置使用它时,Live 会抛出一个 IOError 并且流被关闭。我看不出有什么正当理由说明为什么会发生这种情况。

例如,这应该是有效的:

def stream
    response.headers['Content-Type'] = 'text/event-stream'
    redis = Redis.new
    redis.subscribe('namespaced:stream') do |on|
        response.stream.write "hello world\n"
    end
    rescue IOError
        # Client Disconnected
    ensure
        response.stream.close
end

但是当我尝试这个时,我得到一个 IO 错误:

def stream
    response.headers['Content-Type'] = 'text/event-stream'
    ActiveSupport::Notifications.subscribe("process_action.action_controller") do |*args|
        response.stream.write "hello world\n"
    end
    rescue IOError
        # Client Disconnected
    ensure
        response.stream.close
end

我真的不知道为什么会发生这种情况。服务器日志中没有任何内容表明存在实际问题,否则通知订阅工作;我可以说出来,因为即使我收到 IO 错误,我仍然可以订阅以在“process_action.action_controller”通知发生时将字符串放入控制台。

当通知没有发生时,连接工作得很好,但是一旦有通知(我认为应该只是将“hello world”写入流),我就会收到那个愚蠢的 IO 错误。

顺便说一句,我确实计划检测自己的通知;仅使用现有通知进行测试更容易。

哦,这是我访问使用流控制器方法的路由时在终端中发生的情况的副本:

IOError (closed stream):
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_dispatch/http/response.rb:76:in `write'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/live.rb:47:in `write'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/live.rb:135:in `rescue in block in process'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/live.rb:145:in `block in process'



IOError (closed stream):
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_dispatch/http/response.rb:76:in `write'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/live.rb:47:in `write'
  /home/benjamin/Desktop/workspace/fremote/app/controllers/remotes_controller.rb:25:in `block in show'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/fanout.rb:125:in `call'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/fanout.rb:125:in `finish'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/fanout.rb:40:in `block in finish'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/fanout.rb:40:in `each'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/fanout.rb:40:in `finish'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/instrumenter.rb:36:in `finish'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications/instrumenter.rb:25:in `instrument'
  /usr/local/lib/ruby/gems/2.0.0/gems/activesupport-4.0.2/lib/active_support/notifications.rb:159:in `instrument'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/instrumentation.rb:30:in `process_action'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/params_wrapper.rb:245:in `process_action'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/abstract_controller/base.rb:136:in `process'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/abstract_controller/rendering.rb:44:in `process'
  /usr/local/lib/ruby/gems/2.0.0/gems/actionpack-4.0.2/lib/action_controller/metal/live.rb:132:in `block in process'

如果我有任何术语错误,我深表歉意,因为我是 SSE 和 Rails 的新手,所以我非常感谢任何帮助。

编辑:如果您需要知道,我正在使用以下内容:

  • Rails 4.0.2
  • Ruby 2.0.0p353(2013-11-22 修订版 43784)[i686-linux]

【问题讨论】:

    标签: ruby-on-rails events stream notifications actioncontroller


    【解决方案1】:

    这里有一篇关于这个主题的好文章:http://37signals.com/svn/posts/3091-pssst-your-rails-application-has-a-secret-to-tell-you

    你确定 Redis 和 ActiveSupport::Notifications 有相同的订阅方法吗?

    所以这可能是导致 Rails 出现问题的原因:

      def close
        @response.commit!
        @closed = true
      end
    
    
      def write(string)
        raise IOError, "closed stream" if closed?
    
        @response.commit!
        @buf.push string
      end
    

    您确保流已关闭。当编写它调用时,它会通过一个 IOError if closed?,你要确保它发生。我不能 100% 确定一切都在发生,但这似乎是最有可能的罪魁祸首。

    所以我回去看看一些东西:

    在 Response 的初始化中我们有:

      self.body, self.header, self.status = body, header, status
    

    这里是body=

    def body=(body)
      @blank = true if body == EMPTY
    
      if body.respond_to?(:to_path)
        @stream = body
      else
        synchronize do
          @stream = build_buffer self, munge_body_object(body)
        end
      end
    end
    

    将@stream = 设置为新的Buffer

    def build_buffer(response, body)
      Buffer.new response, body
    end
    

    所以我认为你的罪魁祸首实际上是你确保response.stream.close。现在,在调用 write 之前如何调用它,我不确定。你是先做 Redis,然后测试 Live 写,他们是否使用相同的响应?如果是这样,您可能正在将响应设置为关闭,然后尝试使用它来写入,这将通过 IOerror。

    【讨论】:

    • 非常感谢您的回复。我非常感谢。我认为您所说的可能与我遇到问题的原因有关。但在回答您的问题时,我发现通知的订阅方法与 Redis 的行为有点不同。同样,我没有使用过 Redis,但在我看过的视频中,订阅方法似乎像一个循环,而通知只是订阅并继续。通过在 ActiveSupport 订阅下添加睡眠命令,我能够发送“hello world”。我计划尽快发布我的解决方案。
    • 我从不喜欢使用睡眠作为解决方案。您使用的是thin 或puma 之类的线程服务器吗?如果您使用的是 Unicorn,则需要切换到其中之一。 cmets有一些很好的解释:ngauthier.com/2013/02/rails-4-sse-notify-listen.html
    • 是的,我正在使用 Puma。只要睡眠是定时的而不是无限期的,为了让旧线程死亡,我实际上已经让我的解决方案工作了。这不是很好(仍在寻找杀死线程的更好方法),但我没有看到使用睡眠有什么特别不好的地方,因为 Redis 基本上做同样的事情(阻塞直到触发订阅然后连续阻塞)。我已经考虑过您发布的解决方案,只有我使用的是 MongoDB。它可能具有类似的功能,但我更喜欢与数据库无关的东西。然而,cmets 仍然有一些不错的信息。
    猜你喜欢
    • 2010-09-11
    • 1970-01-01
    • 2016-10-04
    • 2011-11-27
    • 1970-01-01
    • 2014-04-07
    • 2019-05-18
    • 1970-01-01
    • 2011-10-24
    相关资源
    最近更新 更多