在Middleware中运行的线程正在使用父级实例变量的旧版本 [英] Thread running in Middleware is using old version of parent's instance variable

查看:79
本文介绍了在Middleware中运行的线程正在使用父级实例变量的旧版本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经使用 Heroku教程来实现websockets.

I've used Heroku tutorial to implement websockets.

它与Thin兼容,但不适用于Unicorn和Puma.

It works properly with Thin, but does not work with Unicorn and Puma.

还实现了回显消息,该消息响应客户的消息.它可以在每台服务器上正常工作,因此websockets的实现没有任何问题.

Also there's an echo message implemented, which responds to client's message. It works properly on each server, so there are no problems with websockets implementation.

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

Redis setup is also correct (it catches all messages, and executes the code inside subscribe block).

现在如何工作:

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

On server start, an empty @clients array is initialized. Then new Thread is started, which is listening to Redis and which is intended to send that message to corresponding user from @clients array.

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

On page load, new websocket connection is created, it is stored in @clients array.

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

If we receive the message from browser, we send it back to all clients connected with the same user (that part is working properly on both Thin and Puma).

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

If we receive the message from Redis, we also look up for all user's connections stored in @clients array. This is where weird thing happens:

  • 如果与Thin一起运行,它将在@clients数组中找到连接并将消息发送给它们.

  • If running with Thin, it finds connections in @clients array and sends the message to them.

如果与Puma/Unicorn一起运行,则@clients数组始终为空,即使我们以该顺序尝试(没有页面重新加载或任何操作):

If running with Puma/Unicorn, @clients array is always empty, even if we try it in that order (without page reload or anything):

  1. 从浏览器发送消息-> @clients.length为1,消息已传递
  2. 通过Redis发送消息-> @clients.length为0,消息丢失
  3. 从浏览器发送消息-> @clients.length仍为1,消息已传递
  1. Send message from browser -> @clients.length is 1, message is delivered
  2. Send message via Redis -> @clients.length is 0, message is lost
  3. Send message from browser -> @clients.length is still 1, message is delivered

有人可以告诉我我想念什么吗?

Could someone please clarify me what am I missing?

Puma服务器的相关配置:

Related config of Puma server:

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

推荐答案

独角兽(显然是美洲狮)都启动了一个主进程,然后派遣了一个或多个工人.分叉复制(或至少表现出复制的错觉-实际复制通常仅在您写入页面时发生)整个过程,但新过程中仅存在名为fork的线程.

Unicorn (and apparently puma) both start up a master process and then fork one or more workers. fork copies (or at least presents the illusion of copying - an actual copy usually only happens as you write to pages) your entire process but only the thread that called fork exists in the new process.

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

Clearly your app is being initialised before being forked - this is normally done so that workers can start quickly and benefit from copy on write memory savings. As a consequence your redis checking thread is only running in the master process whereas @clients is being modified in the child process.

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

You can probably work around this by either deferring the creation of your redis thread or disabling app preloading, however you should be aware that your setup will prevent you from scaling beyond a single worker process (which with puma and a thread friendly JVM like jruby would be less of a constraint)

这篇关于在Middleware中运行的线程正在使用父级实例变量的旧版本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆