Verbose Logging

software development with some really amazing hair

T + G I F R

Make Your Own Celluloid

· · Posted in Programming
Tagged with

Oh look, a post on ruby concurrency. LOLZ THREADS.

Anyway, let's get past the lolz about ruby and threads and just run this on JRuby okay?

Celluloid

Celluloid is a ruby library to make concurrency easy. It gives you a nice object oriented interface to the concurrent patterns it provides. We're going to look at two specific interfaces: Actors and Futures.

Before we begin, let me say I love Celluloid. I'm just doing this to see how much basic functionality I can get with very little code. You probably shouldn't use any of the code I post here in production, but it's a good learning experience. Just use Celluloid if you need concurrency.

Our Interface

We want to be able to mix in a module and get two methods: async and future.

The async method returns something that sends messages to the instance asynchronously, so calling object.async.long_running_method will return immediately and long_running_method will execute in another thread.

The future method returns something that sends messages it receives to the instance and immediately returns a Future. This Future has one method, value, which will return the return value of the message send when it's ready, possibly blocking until it is ready.

We'll call our gem lol_concurrency

The Async Interface

Let's do the async method first. We'll put everything in the LolConcurrency::Actor module, which you include into your class. We do a fancy dance to memoize the @async variable in a threadsafe manner (I think that should be threadsafe...), and then all the real work is done in the LolConcurrency::Actor::Async class. It forwards respond_to? to the underlying instance, and then method_missing saves everything and sends it to the mailbox (using the Queue class, which is threadsafe). A loop in another thread pops from the mailbox and executes the methods.

Boom.

The Future Interface

The future method is very similar. We do the same dance to cache the Factory as I've called it, respond_to? still gets forwarded, but there's no mailbox. When a message gets sent and hits method_missing, we make a SizedQueue able to hold a single value. The actual work gets done in a new thread and the return value shovelled into the queue. We then return a new Future with the queue. The Future then does the dance to cache the value it pops from the queue when you want the value. The initial call to value might be slow (maybe you're making an HTTP request), but will be instantaneous afterwards since the value is cached.

All Done

That's pretty much it. Wrapping up threads in some safe interface like this can really save you a bunch of headaches. Oh sure, you can still shoot yourself in the foot, doing mutations to things that aren't thread safe. These two modules just give you a nice way to deal with concurrent things without spending too many brain cycles on it.

Looking at this tested code after the fact, it's probably fine to use, even in production, as long as it's for super simple things. I'll still probably stick with Celluloid anyway. It gives you a ton of other great features to handle actor lifecycles (lol exceptions) and is generally better thought out and designed than this little gem I hacked up in a few hours.

That's it for now, happy threading!