Matt Connolly's Blog

my brain dumps here…

Category Archives: Ruby

A year with ZeroMQ + Ruby

It was a year ago since my first commit to rbczmq, a Ruby bindings gem for ZeroMQ, which wraps the higher level CZMQ library.

I don’t recall exactly how I first heard about ZeroMQ, but needless to say, I got interested and started reading up on it. At the time, I discovered there were three gems for using ZeroMQ in ruby:

  • zmq – the original official zmq library. This is old. It tracks ZeroMQ version 2 (currently version 4) and has not been updated for nearly 3 years.
  • ffi-rzmq – a binding using FFI, which is compatible with Ruby, Rubinius and JRuby.
  • rbczmq – a native extension (no JRuby support) binding using the czmq library.

I didn’t spend a lot of time with the zmq gem since it was so old. The ffi-rzmq gem worked well, but didn’t feel very ruby like in its interface. For example, when receiving a message with a C method you would pass a buffer as a parameter to receive the contents of the message and the returned value would be an error code. This is quite un-ruby-like: I would expect receive to return the received data or raise an exception for the error code in keeping with built in ruby socket/file i/o calls.

So I started to explore rbczmq. Initially, I wasn’t so interested in the CZMQ wrapping part, I just wanted something that was more ruby-like to use. And it was. And it was faster. And the CZMQ part actually helps too.

In ZeroMQ each message part, or “frame” is considered a message. So when you read multi-frame messages in ZeroMQ you need to check the “more” flag, and read the next part. CZMQ wraps this as a single message with a number of “frames”. rbczmq neatly exposes these as Ruby classes: ZMQ::Message and ZMQ::Frame. You can still send and receive raw frames (as strings), but the class is a nice wrapper.

And to boot, it turns out that it was way faster than the ffi gem. I seem to have lost track of the comparison I did, but I recall it was convincing.

What’s changed?

During this year, rbczmq has received a number of updates and new features, major ones including:

  • Upgrade to ZeroMQ 4
  • Upgrade to CZMQ 2
  • Support for SmartOS platform
  • Fixes to memory management

Major things still to do:

  • Add support for new authentication interface.
  • Ship binary gems (like libv8) to save compilation time on deploy / install.

Hard bits

The hardest bit of work I contributed to this project was fixing bugs in the memory management. In particular, CZMQ has specific rules about ownership of memory. Ruby is a garbage collected environment, which also has its own set of rules about ownership of memory. The two do not match.

Most calls to ZeroMQ are done outside of the Ruby “GVL” (global lock) which allows the ruby VM to continue processing ruby code in other threads while one is doing a synchronous/blocking read on a socket, for example. When you combine this with Ruby threads, things can get hairy. The solution was two-fold:

  1. Use an ownership flag. When ownership was known to be transferred to ZeroMQ, mark the ruby object as no longer owned by Ruby. This meant that Ruby garbage collection callbacks would know if they were ultimately responsible for freeing memory used by an object. There was also some tricky interplay between contexts and sockets, since a socket is owned by a context, and destroying a context also destroys the sockets, so a socket is only owned by ruby if it has not been closed and the context has not been destroy.
  2. A socket closing mutex: Socket closing and context closing are asynchronous. If a socket is still open when a context is destroyed, then all sockets belonging to that socket will be closed. This happens outside the Ruby GVL, which means that a race condition exists where the Ruby garbage collector may collect the socket while it is still closing. ZeroMQ socket close is not threadsafe, so a mutex was the only solution to make this safe.

Using a mutex for socket close may result in a performance hit for an application which opens and closes sockets rapidly, but from what I understand, that is a bad thing to do anyway.

Looking forward

I have a few projects in the wild now using the rbczmq gem, and am very happy with its stability and performance. I haven’t used all of the APIs in Anger (such as Loops or Beacons), but I’m sure the time will come. I look forward to another year of contributions to this project to keep it up to date with what’s happening in the ZeroMQ and CZMQ projects.

I’d love to hear from other people using this gem, so give me a shout!

Ruby Tuples (and file scanning)

I enjoyed Andrew Pontious’s recent episode of Edge Cases podcast talking about tuples. I’m doing a lot of Ruby these days, so I thought I’d add my two cents worth about using tuples in Ruby.

It’s true that there is no separate tuple class, but Ruby arrays can do everything that tuples in Python can do.

To assign two variables, you can do:

a, b = 1, 2

Which is equivalent to:

a, b = [1, 2]

Which is equivalent to:

a = 1
b = 2

Elements not present are treated as nil, so a, b = 1 assigns the value 1 into a and nil into b.

Functions can return arrays like so:

def f(x)
  [1, 2]

def g(x)
  return 1, 2

The Ruby way to iterate a list of items is with the each method that takes a block:

[1,2,3].each { |x| puts x }

Calls the block 3 times with x having the values 1, 2 and 3 from the list. If these items are themselves arrays, then the items in those sub-arrays can be expanded out into the block variables, like so:

[[1,2], [3,4], [5,6]].each { |a, b| puts "a = #{a}, b = #{b}" }
# outputs:
# a = 1, b = 2
# a = 3, b = 4
# a = 5, b = 6

Hashes can also be enumerated this way, where each key value pair is represented as an array with 2 items:

{a: 1, b: 2, c: 3}.each { |key, value| puts "#{key} => #{value}"}
# outputs:
# a => 1
# b => 2
# c => 3

Python’s list comprehension is really great. Where in python you might write the following to select only items from a list given some condition determined by the function g(x), and return the value f(x) for those values:

results = [f(x) for x in source_list if g(x)]

Ruby achieves the same with select and map methods, which can be composed in either order according to your needs. The Ruby equivalent would be:

results = { |x| g(x) }.map { |x| f(x) }

Python’s list comprehension can only do these two things, in that order. By making the select step and the map steps separate in Ruby, they can be composed in any order. To reverse the map and select order in Ruby:

results = { |x| f(x) }.select { |x| g(x) }

This is not so easy in python:

results = [y for y in [f(x) for x in source_list] if g(y)]

Ruby also contains many more useful operations that can be done on any enumerable sequence (for example readlines from a file), just take a look at the Enumerable module docs:

So I’ve got a bit off the tuple track, so I’ll finish with yet another tangent relating to the podcast episode: Deep searching a file hierarchy for files matching an extension. Try this out for concise:


To return an array of all the .json files anywhere under the current directory. Ruby is full of little treasures like this.

I used to do quite a bit of scripting in Python until I learnt Ruby. I’ve never looked back.

Network latency in SmartOS virtual machines

Today I decided to explore network latency in SmartOS virtual machines. Using the rbczmq ruby gem for ZeroMQ, I made two very simple scripts: a server that replies “hello” and a benchmark script that times how long it takes to send and receive 5000 messages after establishing the connection.

The server code is:

require 'rbczmq'
ctx =
sock = ctx.socket ZMQ::REP
loop do
  sock.send "reply"

The benchmark code is:

require 'rbczmq'
require 'benchmark'

ctx =
sock = ctx.socket ZMQ::REQ

# establish the connection
sock.send "hello"

# run 5000 cycles of send request, receive reply.
puts Benchmark.measure {
  5000.times {
    sock.send "hello"

The test machines are:

* Mac laptop – server & benchmarking
* SmartOS1 (SmartOS virtual machine/zone) server & benchmarking
* SmartOS2 (SmartOS virtual machine/zone) benchmarking
* Linux1 (Ubuntu Linux in KVM virtual machine) server & benchmarking
* Linux2 (Ubuntu Linux in KVM virtual machine) benchmarking

The results are:

Source      Dest        Connection      Time          Req-Rep/Sec
------      ----        ----------      ----          --------
Mac         Linux1      1Gig Ethernet   5.038577      992.3
Mac         SmartOS1    1Gig Ethernet   4.972102      1005.6
Linux2      Linux1      Virtual         1.696516      2947.2
SmartOS2    Linux1      Virtual         1.153557      4334.4
Linux2      SmartOS1    Virtual         0.952066      5251.8
Linux1      Linux1      localhost       0.836955      5974.0
Mac         Mac         localhost       0.781815      6395.4
SmartOS2    SmartOS1    Virtual         0.470290      10631.7
SmartOS1    SmartOS1    localhost       0.374373      13355.7

localhost tests use

SmartOS has an impressive network stack. Request-reply times from one SmartOS machine to another are over 3 times faster than when using Linux under KVM (on the same host). This mightn’t make much of a difference to web requests coming from slow mobile device connections, but if your web server is making many requests to internal services (database, cache, etc) this could make a noticeable difference.

ZeroMQ logging for ruby apps

I’ve been thinking for a while about using ZeroMQ for logging. This is especially useful with trends towards micro-services and scaling apps to multiple cloud server instances.

So I put thoughts into action and added a logger class to the rbczmq gem that logs to a ZeroMQ socket from an object that looks just like a normal ruby logger:

There’s not much to it, because, well, there’s not much to it. Here’s a simple app that writes log messages:

Log Writer:

require 'rbczmq'
require_relative './logger'
require 'benchmark'
ctx =
socket = ctx.socket(ZMQ::PUSH)
logger =
puts Benchmark.measure {
 10000.times do |x|
 logger.debug "Hello world, #{x}"

With benchmark results such as:

  0.400000   0.220000   0.620000 (  0.418493)

Log Reader:

And reading is even easier:

require 'rbczmq'
ctx =
socket = ctx.socket(ZMQ::PULL)
loop do
 msg = socket.recv
 puts msg

Voila. Multiple apps can connect to the same log reader. Log messages will be “fair queued” between the sources. In a test run on my 2010 MacBook Pro, I can send about 13000 log messages a second. I needed to run three of the log writers above in parallel before I maxed out the 4 cores and it slowed down. Each process used about 12 MB RAM. Lightweight and fast.

Log Broadcasting:

If we then need to broadcast these log messages for multiple readers, we could easily do this:

require 'rbczmq'
ctx =
socket = ctx.socket(ZMQ::PULL)
publish = ctx.socket(ZMQ::PUB)
loop do
 msg = socket.recv

Then we have many log sources connected to many log readers. And the log readers can also subscribe to a filtered stream of messages, so one reader could do something special with error messages, for example.

Building Ruby 2 in SmartOS

Installing Ruby 2 in SmartOS!

Ruby 2 has been out for a while, so let’s get it going in SmartOS!!

I’m working with the SmartMachine (base64 1.9.0).

Part 1: Requirements.

The SmartMachine has a whole bunch of useful packages already installed. Here are the additional packages needed to build Ruby 2.0.0 in SmartOS:

# pkgin install build-essential gcc47 gcc47-libs libyaml 

Part 2: Configure hacks

There are a few issues with Ruby 2.0.0, but fortunately all of them have a command line workaround.

1. Ruby bug #5384

Actually an upstream bug in (Open)Solaris/Illumos/OpenIndiana/SmartOS. Workaround is to add `ac_cv_func_dl_iterate_phdr=no` to the configure line.

See also:

2. Ruby bug #8071

Non-portable code in ruby’s configure scipt. Easy workaround, prepend the configure command with a shell that can handle the current state of the configure script, ie: `bash`. (A fix has already been submitted, ans should be in the next ruby patch.)

3. Ruby bug #8268

“-ggdb3″ C flags issue (logged with Ruby, but not necessarily actually a bug in ruby. Please help if you can on this one!)

Workaround 1: Add `debugflags=”-ggdb”` to the configure line. Caveat: It appears this will add gdb debug info to built ruby binaries, which may not be desired.

Workaround 2: Add `CFLAGS=”-R -fPIC”`. This introduces a make error. Missing function ‘signbit’. Boo.

Workaround 3: Add `CFLAGS=”-R -fPIC” rb_cv_have_signbit=no`. Works!

So with these three taken care of, the following command line will configure ruby-2.0.0-p0 to compile on SmartOS (in 64 bit):

$ bash ./configure --prefix=$PREFIX --with-opt-dir=/opt/local --enable-shared ac_cv_func_dl_iterate_phdr=no CFLAGS="-R -fPIC" rb_cv_have_signbit=no
$ make && make install

And there we have it. Ruby 2.0.0-p0 building in SmartOS. Next challenge, making use of those built in DTrace probes…

Building ruby 2.0.0 for Mac

After downloading ruby source code, use this:

CC=/usr/bin/clang ./configure ...

This also works with RVM

CC=/usr/bin/clang rvm install ruby-2.0.0

Passenger apache module for OpenIndiana

I did a bit of hunting and made some patches to the ‘passenger’ gem so that it’s apache module would compile for OpenIndiana. Changes are in my github fork:

And I just noticed that one of the fixes was in a patch in Joyent’s SmartOS instructions for using passenger.

I tested this also on a VM guest installation of Solaris 11 Express, and it worked too. I’d be interested to hear if it works for others on OpenIndiana, Solaris or SmartOS.

So with updates to rvm, latest version of ruby and with this patched version of passenger, I’m finally good to go to deploy rails apps on OpenIndiana. Woot!

TTCP, in Ruby

I’ve used the TTCP tcp test program from time to time, and am at present looking at some networking in Ruby. So why not have a look at porting that to ruby? So I did.

This has been built as a gem, which an executable ‘ttcp’ that will install in your gem’s bin folder. You can get the gem from here:

Or type: `gem install ttcp` at your terminal.

Source code is released under MIT license, and available on github:

So far, I’ve tested it out on Mac and OpenIndiana in ruby 1.8.7, 1.9.3 and JRuby 1.6.5. I can’t seem to run the tests in JRuby, but it appears to work anyway.