问题
Following are my Celluloid codes.
client1.rb One of the 2 clients. (I named it as client 1)
client2.rb 2nd of the 2 clients. (named as client 2 )
Note:
the only the difference between the above 2 clients is the text that is passed to the server. i.e ('client-1'
and 'client-2'
respectively)
On testing this 2 clients (by running them side by side) against following 2 servers (one at time). I found very strange results.
server1.rb (a basic example taken from the README.md of the celluloid-zmq)
Using this as the example server for the 2 above clients resulted in parallel executions of tasks.
OUTPUT
ruby server1.rb
Received at 04:59:39 PM and message is client-1
Going to sleep now
Received at 04:59:52 PM and message is client-2
Note:
the client2.rb message was processed when client1.rb request was on sleep.(mark of parallelism)
server2.rb
Using this as the example server for the 2 above clients did not resulted in parallel executions of tasks.
OUTPUT
ruby server2.rb
Received at 04:55:52 PM and message is client-1
Going to sleep now
Received at 04:56:52 PM and message is client-2
Note:
the client-2 was ask to wait 60 seconds since client-1 was sleeping(60 seconds sleep)
I ran the above test multiple times all resulted in same behaviour.
Can anyone explain me from the results of the above tests that.
Question: Why is celluloid made to wait for 60 seconds before it can process the other request i.e as noticed in server2.rb case.?
Ruby version
ruby -v
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
回答1:
Using your gists, I verified this issue can be reproduced in MRI 2.2.1
as well as jRuby 1.7.21
and Rubinius 2.5.8
... The difference between server1.rb
and server2.rb
is the use of the DisplayMessage
and message
class method in the latter.
Use of sleep
in DisplayMessage
is out of Celluloid
scope.
When sleep
is used in server1.rb
it is using Celluloid.sleep
in actuality, but when used in server2.rb
it is using Kernel.sleep
... which locks up the mailbox for Server
until 60 seconds have passed. This prevents future method calls on that actor to be processed until the mailbox is processing messages ( method calls on the actor ) again.
There are three ways to resolve this:
Use a defer {} or
future {}
block.Explicitly invoke Celluloid.sleep rather than
sleep
( if not explicitly invoked asCelluloid.sleep
, usingsleep
will end up callingKernel.sleep
sinceDisplayMessage
does notinclude Celluloid
likeServer
does )Bring the contents of
DisplayMessage.message
intohandle_message
as inserver1.rb
; or at least intoServer
, which is inCelluloid
scope, and will use the correct sleep.
The defer {}
approach:
def handle_message(message)
defer {
DisplayMessage.message(message)
}
end
The Celluloid.sleep
approach:
class DisplayMessage
def self.message(message)
#de ...
Celluloid.sleep 60
end
end
Not truly a scope issue; it's about asynchrony.
To reiterate, the deeper issue is not the scope of sleep
... that's why defer
and future
are my best recommendation. But to post something here that came out in my comments:
Using defer
or future
pushes a task that would cause an actor to become tied up into another thread. If you use future
, you can get the return value once the task is done, if you use defer
you can fire & forget.
But better yet, create another actor for tasks that tend to get tied up, and even pool that other actor... if defer
or future
don't work for you.
I'd be more than happy to answer follow-up questions brought up by this question; we have a very active mailing list, and IRC channel. Your generous bounties are commendable, but plenty of us would help purely to help you.
回答2:
Managed to reproduce and fix the issue.
Deleting my previous answer.
Apparently, the problem lies in sleep
.
Confirmed by adding logs "actor/kernel sleeping"
to the local copy of Celluloids.rb's sleep().
In server1.rb
,
the call to
sleep
is withinserver
- a class that includes Celluloid.Thus Celluloid's implementation of
sleep
overrides the nativesleep
.
class Server
include Celluloid::ZMQ
...
def run
loop { async.handle_message @socket.read }
end
def handle_message(message)
...
sleep 60
end
end
Note the log actor sleeping
from server1.rb
. Log added to Celluloids.rb's sleep()
This suspends only the current "actor" in Celluloid i.e. only the current "Celluloid thread" handling the client1 sleeps.
In server2.rb
,
the call to
sleep
is within a different classDisplayMessage
that does NOT include Celluloid.Thus it is the native
sleep
itself.
class DisplayMessage
def self.message(message)
...
sleep 60
end
end
Note the ABSENCE of any actor sleeping
log from server2.rb
.
This suspends the current ruby task i.e. the ruby server sleeps (not just a single Celluloid actor).
The Fix?
In server2.rb, the appropriate
sleep
must be explicitly specified.
class DisplayMessage
def self.message(message)
puts "Received at #{Time.now.strftime('%I:%M:%S %p')} and message is #{message}"
## Intentionally added sleep to test whether Celluloid block the main process for 60 seconds or not.
if message == 'client-1'
puts 'Going to sleep now'.red
# "sleep 60" will invoke the native sleep.
# Use Celluloid.sleep to support concurrent execution
Celluloid.sleep 60
end
end
end
来源:https://stackoverflow.com/questions/35061826/understanding-celluloid-concurrency