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!
I found solution of this problem, em-synchrony should work inline out of the box by requiring this library:
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:
Don`t forget to take this gem from github
Hope it helps someone.