Gotchas
- MRI 1.9.3-p362 has known segfaults - Consider 2.x.x or other patchlevels
- JRuby 1.7.3 has broken Fibers - Consider using 1.7.2, 1.7.4 or TaskThread
- JRuby 1.7.4 has a cross-Fiber resume bug - Consider 1.7.5
- UNIX signal handling is broken - Workaround
- Celluloid pools have several bugs
Celluloid is fundamentally a messaging system which uses thread-safe proxies to manage all inter-object communication in the system. While the goal of these proxies is to make it simple for you to write concurrent programs by applying the uniform access principle to thread-safe inter-object messaging, you can't simply forget they're there.
The thread-safety guarantees Celluloid provides around synchronizing access to instance variables only work so long as all access to actors go through the proxy objects. If the real objects that Celluloid is wrapping in an actor manage to leak out of the system, all hell will break loose. See also Thread Safety.
Here are a few rules you can follow to keep this from happening:
in cases where you want to pass an actor around to other actors or threads,
use Celluloid::Actor.current
, or if you're within an actor itself, you can
just call Actor.current
as you've included the Celluloid
module.
This is a problem that Celluloid shares with Akka (see the warning about 'this' part way down the page) and Go.
If you really need to get a hold of self
in order to add instance-specific behavior, e.g for
metaprogramming purposes or adding stubs during tests, call MyActor#wrapped_object
to
obtain the actual object an actor is wrapping. This isn't thread safe in
general but is perfectly fine to do if you've just created an actor, hold
the only reference to it, and want to do some metaprogramming before using
it as part of a test.
This means you must think about data in one of two different ways: either you "fire and forget" the data, leaving it for other actors to do with what they will, or you must treat it as immutable if you have any plans of sharing it with other actors. If you're paranoid (and when you're dealing with concurrency, there's nothing wrong with being paranoid), you can freeze objects so you can detect subsequent mutations (or rather, turn attempts at mutation into errors). If you want to use freeze, be aware that freeze is shallow and will not freeze all objects referenced by the object you're freezing.
If you make a call to another actor with a mutex held, you're doing it wrong.
While it's perfectly fine and strongly encouraged to call out to thread safe libraries from Celluloid actors, if you're using libraries that acquire mutexes and then execute callbacks (e.g. they take a block while they're holding a mutex) the guarantees that Celluloid provides will become weak and you may encounter deadlocks.
Celluloid employs Fibers as an intrinsic part of how it implements actors. While it's possible for certain uses of Fibers to cooperatively work alongside how Celluloid behaves, in most cases you'll be writing a check you can't afford. So please ask yourself: why are you using Fibers, and why can't it be solved by a block? If you've got a really good reason and you're feeling lucky, knock yourself out.
Please fork prior to creating actors. If you don't, Ruby semantics are both undefined, broken, and can lead to crashes. Specifically it will lead to all actor threads being dead after forking.
Tools such as Rails Spring and DelayedJob employ this pattern to speed-up load time, thus defer creating actors util after preloading has finished.
The semantics of blocks in Celluloid can be confusing. Please see the section on blocks for more information.
Each method call running concurrently on an actor creates a new Task with associated Fiber. E.g.:
class Foo
def initialize(bar)
@bar = bar
end
def work
@bar.work # Actor that blocks for 5 seconds.
end
end
f = Foo.new(some_bar)
5.times { f.async.work } # Now you have 5 tasks.
Celluloid uses this to avoid deadlocks and to run concurrent tasks more effectively (you can disable it with Celluloid.exclusive(&block)
). For example celluloid-io uses this concept to manage multiple connected clients in a non-blocking evented fashion.
However due to how Fibers are implemented in JRuby, each Fiber actually spawns new kernel-level thread. Which means:
You have separate Thread.current
for each Task.
This is a bit of strange JRuby errata and most likely should be considered a JRuby bug.
This poses funny problems for libs which use it to store thread-local data. In example: ActiveRecord
stores connection id in there. That means each Task will check out new connection from connection pool, never checking it in if you don't handle it properly.
By default, a Celluloid::IO
socket will fall back to Kernel#select
unless the hosting actor has included Celluloid::IO
. If you find that you have an IO operation blocking that you don't expect to, make sure that the calling actor is including Celluloid::IO
or else it's completely normal that your otherwise-normally-blocking IO operation will block. Just having a handle on a Celluloid::IO
socket isn't sufficient for the nio4r non-blocking operations to operate.
You can tell that this is happening if the Task reports itself as :running
instead of :iowait
. A nio4r-based IO task should appear as :iowait
when it's awaiting I/O. If you see the task :running
, then it's most likely you forgot to include Celluloid::IO
RSpec caches things created in :let
blocks and the subject
variable it automatically provides. This can cause some serious confusion as it helps state to leak between tests. It also means that something can be cached with a reference to an Actor which gets terminated, leading to a wide variety of errors.
The best ways to avoid these issues are:
- Always create/retrieve the Actor under test at the start of the test; don't rely on
subject
or even an actor reference created in aBefore
block. - Before each test, call
Celluloid.shutdown; Celluloid.boot
- Look at the notes elsewhere on the wiki about using
#wrapped_object
to get a reference to actors and not their proxy objects.
Always feel free to:
- Visit the
#celluloid
channel on freenode. - Post a bug report or feature request.
- Ask questions on our mailing list.