cramp framework sync 'render' correct way using em-synchrony

180 views Asked by At

To describe my problem I attach simple Cramp http://cramp.in/ class. I add some modification but its mainly work like https://github.com/lifo/cramp-pub-sub-chat-demo/blob/master/app/actions/chat_action.rb

class ChatAction < Cramp::Websocket

  use_fiber_pool

  on_start :create_redis
  on_finish :handle_leave, :destroy_redis
  on_data :received_data
  
  def create_redis
    @redis = EM::Hiredis.connect('redis://127.0.0.1:6379/0')    
  end
  
  def destroy_redis
    @redis.pubsub.close_connection
    @redis.close_connection
  end
  
  def received_data(data)
    msg = parse_json(data)
    case msg[:action]
    when 'join'
      handle_join(msg)
    when 'message'
      handle_message(msg)
    else
      # skip
    end
  end
  
  def handle_join(msg)
    @user = msg[:user]
    subscribe
    publish(:action => 'control', :user => @user, :message => 'joined the chat room')
  end
  
  def handle_leave
    publish :action => 'control', :user => @user, :message => 'left the chat room'
  end
  
  def handle_message(msg)
    publish(msg.merge(:user => @user))
    # added only for inline sync tests
    render_json(:action => 'message', :user => @user, :message => "this info should appear after published message")
  end
  
  private

  def subscribe
    @redis.pubsub.subscribe('chat') do |message|
      render(message)
    end
  end
  
  def publish(message)
    @redis.publish('chat', encode_json(message))
  end
  
  def encode_json(obj)
    Yajl::Encoder.encode(obj)
  end
  
  def parse_json(str)
    Yajl::Parser.parse(str, :symbolize_keys => true)
  end

  def render_json(hash)
    render encode_json(hash)
  end
end

More about what i try to do is in handle_message method.

I try send messages to client in correct order. First publish message to all subscribers, second render some internal info only for current connected client.

For above code client receives:

{"action":"message","user":"user1","message":"this info should appear after published message"}
{"action":"message","message":"simple message","user":"user1"}

Its not synchronized, because of em-hiredis defferable responses, probably. So I try to synchronized it this way:

def handle_message(msg)
  EM::Synchrony.sync publish(msg.merge(:user => @user))
  EM::Synchrony.next_tick do # if I comment this block messages order is still incorrect
     render_json(:action => 'message', :user => @user, :message => "this info should appear after published message")
  end
end

Now, client handle messages with correct order.

{"action":"message","message":"simple message","user":"user1"}
{"action":"message","user":"user1","message":"this info should appear after published message"}

My questions are:

  • When I comment EM::Synchrony.next_tick block, messages order is still incorrect. What meaning have EM::Synchrony.next_tick block in this example?
  • Is this good way to handle inline sync with Cramp or EventMachine ?
  • Is there a better, clearer way to handle it ?

Thank you!

1

There are 1 answers

0
mateuszdw On BEST ANSWER

I found solution of this problem, em-synchrony should work inline out of the box by requiring this library:

require 'em-synchrony/em-hiredis'

class ChatAction < Cramp::Websocket

Using EM::Synchrony.next_tick block is bad idea, with big help of em-synchrony community I add em-hiredis 0.2.1 compatibility patch on github

So now handle_message method looks like this:

def handle_message(msg)
   publish(msg.merge(:user => @user))
   render_json(:action => 'message', :user => @user, :message => "this info should appear after published message")
end

Don`t forget to take this gem from github

gem 'em-synchrony', :git=> 'git://github.com/igrigorik/em-synchrony.git'

Hope it helps someone.