1

I am having an issue with the following and that might be straightforward to someone with more knowledge or experience. Having spent the whole weekend trying to figure it out without success...extra help and insight would be very appreciated, thank you.

I have a class variable, @@clients that keeps track of opened websocket connections. When I access that variable from within the on.message block from the redis_sub.subscribe block (in a new Thread) the array is empty. I made a test class variable @@test that is incremented everytime a new websocket connection happens and log that variable, same, the log shows @@test to be 0, its initial value.

class Shrimp
    # Heroku has a 50 seconds idle connection time limit. 
    KEEPALIVE_TIME = 15 # in seconds
    @@clients = []
    @@test = 0

    class << self
        def load_session(client)
            if !client.env["rack.session"].loaded?
                client.env["rack.session"][:init] = true
            end
        end

        def get_client(user_id)
            # check if user is one of the ws clients  
            @@clients.each do |client|
                # lazily load session
                load_session(client)

                # get user_id from the session
                if(client.env["rack.session"]["warden.user.user.key"][0][0] == user_id)
                    return client
                end
            end
            nil
        end
    end

    def initialize(app)
        @app = app

        redis_uri = URI.parse(ENV["REDISCLOUD_URL"])
        # work on a separte thread not to block current thread

        Thread.new do
            redis_sub = Redis.new(host: redis_uri.host, port: redis_uri.port, password: redis_uri.password)
            redis_sub.subscribe(ENV["REDIS_CHANNEL"]) do |on| # thread blocking operation
                p [:redis_suscribe]
                p [:test, @@test]

                on.message do |channel, msg|
                    data = JSON.parse(msg)

                    p [:redis_receive_message, data]
                    p [:test, @@test]

                    client = Shrimp.get_client(data["user_id"])
                    client.send(data["thumbnail_urls"].to_json) if client
                end
            end
        end
    end

    def call(env)
        env['rack.shrimp'] = self
        if Faye::WebSocket.websocket?(env)
            puts websocket_string

            # Send every KEEPALIVE_TIME sec a ping for keeping the connection open.
            ws = Faye::WebSocket.new(env, nil, { ping: KEEPALIVE_TIME })

            ws.on :open do |event|
                puts '***** WS OPEN *****'
                @@clients << ws
                @@test = @@test + 1
                p [:test, @@test]
            end

            ws.on :message do |event|
                puts '***** WS INCOMING MESSAGE *****'
                p [:message, event.data]
                p [:test, @@test]
                @@clients.each { |client| client.send(event.data.to_json) }
            end

            ws.on :close do |event|
                puts '***** WS CLOSE *****'
                p [:close, ws.object_id, event.code, event.reason]
                @@clients.delete(ws)
                p [:test, @@test]
                ws = nil
            end

            ws.on :error do |event|
                puts '***** WS ERROR *****'
                p [:close, ws.object_id, event.code, event.reason]
            end 

            # Return async Rack response
            ws.rack_response
        else
            @app.call(env)
        end
    end
end

terminal output: enter image description here

we can see the @@testclass variable being 1 after a first connection opens, still 1 when the server gets a message from that one client, 0 when logged from within the on.message block and 1 again when that websocket connection is closed.

I am obviously missing something but cannot figure it out despite all the research and readings.

Andrea.cabral
  • 338
  • 3
  • 16

0 Answers0