ruby-on-rails - 在中间件中运行的线程正在使用旧版本的父实例变量

标签 ruby-on-rails multithreading ruby-on-rails-4 puma rack-middleware

我用过 Heroku tutorial实现websockets。

它适用于 Thin,但不适用于 Unicorn 和 Puma。

还有一个回显消息实现,它响应客户端的消息。它在每个服务器上都能正常工作,因此 websockets 实现没有问题。

Redis 设置也是正确的(它捕获所有消息,并执行 subscribe 块中的代码)。

现在如何运作:

在服务器启动时,一个空 @clients数组被初始化。然后启动新线程,它正在监听 Redis 并打算将该消息从@clients 数组发送给相应的用户。

在页面加载时,会创建新的 websocket 连接,并将其存储在 @clients 数组中。

如果我们从浏览器收到消息,我们会将其发送回与同一用户连接的所有客户端(该部分在 Thin 和 Puma 上都正常工作)。

如果我们收到来自 Redis 的消息,我们还会查找存储在 @clients 数组中的所有用户连接。
这就是奇怪的事情发生的地方:

  • 如果使用 Thin 运行,它会在 @clients 数组中找到连接并将消息发送给它们。
  • 如果与 Puma/Unicorn 一起运行,@clients 数组总是空的,即使我们按这个顺序尝试(没有页面重新加载或任何东西):
  • 从浏览器发送消息 -> @clients.length为 1,消息已投递
  • 通过Redis发送消息-> @clients.length为 0,消息丢失
  • 从浏览器发送消息 -> @clients.length仍为 1,消息已送达

  • 有人可以澄清我缺少什么吗?

    Puma服务器相关配置:
    workers 1
    threads_count = 1
    threads threads_count, threads_count
    

    相关中间件代码:
    require 'faye/websocket'
    
    class NotificationsBackend
    
      def initialize(app)
        @app     = app
        @clients = []
        Thread.new do
          redis_sub = Redis.new
          redis_sub.subscribe(CHANNEL) do |on|
            on.message do |channel, msg|
              # logging @clients.length from here will always return 0
              # [..] retrieve user
              send_message(user.id, { message: "ECHO: #{event.data}"} )
            end
          end
        end
      end
    
      def call(env)
        if Faye::WebSocket.websocket?(env)
          ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME })
          ws.on :open do |event|
            # [..] retrieve current user
            if user
              # add ws connection to @clients array
            else
              # close ws
            end
          end
    
          ws.on :message do |event|
            # [..] retrieve current user
            Redis.current.publish({user_id: user.id, { message: "ECHO: #{event.data}"}} )
          end
    
          ws.rack_response
        else
          @app.call(env)
        end
      end
      def send_message user_id, message
        # logging @clients.length here will always return correct result
        # cs = all connections which belong to that client
        cs.each { |c| c.send(message.to_json) }
      end
    end
    

    最佳答案

    Unicorn(显然还有 puma)都启动了一个主进程,然后 fork 了一个或多个 worker 。 fork 复制(或至少呈现复制的错觉 - 实际复制通常仅在您写入页面时发生)您的整个过程,但只有调用 fork 的线程。存在于新进程中。

    很明显,您的应用程序在 fork 之前已被初始化 - 通常这样做是为了让工作人员可以快速启动并从写入时复制内存节省中受益。因此,您的 redis 检查线程仅在主进程中运行,而 @clients正在子进程中被修改。

    您可以通过推迟创建 redis 线程或禁用应用程序预加载来解决此问题,但是您应该意识到您的设置将阻止您扩展到单个工作进程之外(使用 puma 和线程友好的 JVM,如 jruby 会减少约束)

    关于ruby-on-rails - 在中间件中运行的线程正在使用旧版本的父实例变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30762774/

    相关文章:

    css - 尝试使用 Bootstrap 和 Rails 调整选择选项标签的宽度

    java - Thread.currentThread().interrupt() 与 Runnable#run() 中的 this.interrupt()

    java - Android - 从线程中返回 boolean 值

    ruby-on-rails - rails 4 : having a proxy model to combine multiple models

    ruby-on-rails - 为什么要求我运行 'rake db:migrate RAILS_ENV=test' ?

    ruby-on-rails - 监视上帝?

    ruby-on-rails - 更新后如何检测属性变化

    ruby-on-rails - Ruby - 数组的未定义方法排序

    vb.net - VB.NET调用方法

    Mysql 2.9.1 错误与 ruby​​ 2.0