concurrency models in jruby using jetlang…
Note: This is in response to a post I saw the other morning on #dev-test-ops called “Jruby + Jetlang => Awesome Message Passing Concurrency“, written by Sai Venkatarishan.
As Sai’s post states, jetlang (or if you’re in C#, retlang) makes for awesome message passing concurrency. I have become familiar with jetlang/retlang in my past few years of working at DRW Trading; many times writing code very similar to that within the examples provided by Sai but in C#. However, over the past few months my team has started using jruby more frequently so naturally we used jetlang as stated in Sai’s post. Shortly after adopting jruby as our JVM based language we started using jetlang via a gem written by Gareth Reeves, called jretlang, which provides the benefits of jetlang in jruby with less of the ceremony. To illustrate this, I’ve rewritten Sai’s first example using jretlang as opposed to jetlang. You’ll see that essentially all it does is clean up some of the setup of the file.
require "rubygems"
require "jretlang"
class Arnie
def initialize
@channel = JRL::Channel.new
@consumer = JRL::Fiber.new
end
def start
@consumer.start
@channel.subscribe_on_fiber( @consumer ) do |message|
case message
when "The End"
puts "I will be back..."
@consumer.dispose
@consumer.join
when "Terminate"
puts "Hastala vista baby!!!"
else
puts "You are terminated******"
end
end
end
def ^(message)
@channel.publish(message)
end
end
terminator = Arnie.new
terminator.start
terminator ^ "Terminate"
terminator ^ "Buy me ice cream"
terminator ^ "The End"
terminator ^ "Terminate" #Will not puts anything, as you've stopped the fiber
Now upon seeing this, it does not really seem to need the channel for the message passing. Typically we’ll use channels when the object publishing a message and the object(s) receiving the message are distinct and don’t know about each other. Notice I used “(s)” on object, this is because the publisher does not need to worry itself with he details of how many objects, if any, are subscribed on its channel. This is commonly referred to as a process calculi approach of concurrency. The Actor-model approach, which Sai speaks of, is more about an object having a reference to another object, their actor, which will act on the delivery of a message to its inbox. Jetlang/Retlang’s fibers support this concept without having the overhead of a channel. Fibers contain an execute method that can simply execute a lambda on that fiber. Using this concept, we can clean this example up even more and move to a truer actor-model based approach.
require "rubygems"
require "jretlang"
class Arnie
def initialize
@consumer = JRL::Fiber.new
end
def start
@consumer.start
end
def ^(message)
@consumer.execute do
case message
when "The End"
puts "I will be back..."
@consumer.dispose
@consumer.join
when "Terminate"
puts "Hastala vista baby!!!"
else
puts "You are terminated******"
end
end
end
end
terminator = Arnie.new
terminator.start
terminator ^ "Terminate"
terminator ^ "Buy me ice cream"
terminator ^ "The End"
terminator ^ "Terminate" #Will not puts, fiber has been disposed
Having used jetlang/retlang as my main source of concurrency for a while, I’ve seen myself writing very similar code to that above every time I introduce a new thread (fiber). But when I look at the class above I think, what really is the main job of the class? I find the answer in the body of the fiber’s execute. It is the only thing that differs from Arnie to any other class that has similar functionality, say JohnConner. After putting up with implementing this duplication for far too long, my co-worker Pat Farley and I decided that we were tired of wasting our “valuable” time and effort typing most of this over and over again. This lead us to create mailbox (see Pat’s blog post), a gem to help remove most of the structural redundancies involved in jruby concurrency models using jetlang. So, using mailbox our example code becomes:
require "rubygems"
require "mailbox"
class Arnie
include Mailbox
mailslot
def ^(message)
case message
when "The End"
puts "I will be back..."
# cannot dispose from here
when "Terminate"
puts "Hastala vista baby!!!"
else
puts "You are terminated******"
end
end
end
terminator = Arnie.new
terminator ^ "Terminate"
terminator ^ "Buy me ice cream"
terminator ^ "The End"
terminator ^ "Terminate" # THIS STILL PRINTS
This separates the message handling logic from how the object achieves actor-model based concurrency. In addition to actor-model based concurrency, mailbox supports the use of channels, but this example is better shown without channels.
All the above code is available on github as a gist. I’ll try later this week to do the ping-pong example using mailbox and put it up either as a Gist or just an example in Mailbox; please look in the comments for another link to the ping-pong example.
@Joel – Read the examples in your post and cool examples with Jetrlang. I used Jetlang as pretty raw without much of abstraction over the library.. Thank you..