Having a C implementation of Davenport’s algorithm, the next step was to make it usable from Ruby. Recall that socelect.org is served-up using Ruby on Rails.
This is the part of the project that I’m finding the most difficult to write about. It was the part that gave me the most trouble. Not so much in the coding, which was a comfortable technical challenge, but with the integration, which gave me the worst nightmare. More about that later.
Ruby C Extension
The Ruby C extension creates, in C, a module that can be included and some functions that can be called in Ruby code.
The most helpful documentation I found about how to do this is
extension.rdoc in Ruby itself about creating extension
We’re essentially programming Ruby with C, using C functions to manipulate C data structures set-up by Ruby. It doesn’t make sense to do anything too complicated or navigate Ruby data structures too much. Those make more sense in Ruby to begin with. Keep it as simple as possible.
There are multiple aspects to a Ruby C extension, which I placed in one file, davenport_ruby.c:
- C functions that implement the methods using
VALUEstructs that represent data parameters passed to them by Ruby, and returning one
- C functions and data structures that inform the Ruby runtime how to initialize and manage memory for the extension.
- A C function that defines the Ruby module, class, and methods of the class.
Ruby will invoke this function when it encounters the statement,
To keep things simple, I defined one class,
PreferenceGraph that provides
three methods. The methods are:
initialize(called by the class constructor), which allocates a graph with a given number of alternatives (nodes).
add_preference, which accepts an array of integer rank numbers, checks that the length equals the number of alternatives given the initializer, initializes a C array from the Ruby array, and invokes the
preference_graph_add_preferencefunction from the Davenport library.
davenport, which invokes the Davenport algorithm on the preference graph and returns a Ruby array of rank numbers.
Parts of the code in the latter two functions translates between Ruby arrays and C arrays. The other parts are straight C usage of the Davenport library.
I wanted an exception to throw if the array given to
the right size, or doesn’t contain only integers (Ruby Numeric type
At first I thought, “Gee, now I have to define a Ruby
in C.” Pretty soon it occurred to me to simply define it in Ruby, within the
gem, and use it.
Strangely, in order to access the exception class within the C code, you
call a method called
rb_define_class_under. That method has smarts to
return the existing class if it already exists.
davenport_ruby C extension allocates a data structure that will
represent an instance of the
by defining a function,
pg_allocate provided in a call to
rb_define_alloc_func. The function
returns data storage reserved with
malloc, initialized, and wrapped up using
That macro accepts a structure,
rb_davenport_type that contains
information for the Ruby garbage collector. The information includes
a reference to a method
pg_report_size that returns
the amount of data the instance has allocated and a reference to
pg_deallocate defined to free it.
The functions implementing the methods of the class access the instance data
for the class using a macro,
Packaging as a Gem
Building and packaging the extension is accomplished through
extconf.rb Ruby program containing calls to methods made available by
mkmf. The most helpful documents are the
at RubyGems.org and the
mkmf library at Ruby-lang.org.
The gem specification,
davenport.gemspec contains a line,
s.extensions << 'ext/davenport_ruby/extconf.rb'
that tells RubyGems to run that file and execute the resulting
extconf.rb program uses
mkmf methods that check for the
Davenport library and generate the configuration to link it, a C language
header, and a make file specific to the platform.
To enable compilation, the gem requires
rake-compiler as a dependency,
and includes a call to
within a file called,
Rakefile within the project.
There’s a “smoke test” Ruby program for verifying that the gem will install properly and function. You can find it as wbreeze/dvt.
An Integration Nightmare
I don’t even want to write about this. It took me three weeks to get over it. When it came to deploying the web site with the Davenport Ruby gem integrated, the site would not operate with the gem. It worked on my development box. It wouldn’t work on the server, which has a different operating system.
Attempting to run the program on the server yielded:
libdavenport.so.0: cannot open shared object file: No such file or\ directory - /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0\ /gems/davenport-1.0.2.pre/lib/davenport_ruby/davenport_ruby.so\ (LoadError)
The worst part is that there are three different systems where the the problem might lie:
- The coding and packaging of the Ruby C extension gem
- The coding, compilation, and installation of the C library
- The coding and usage within the Ruby program
Each of these (least of all the last, mostly the first two) have any number of details that might be wrong. If you followed the earlier sections of this post, you have an idea of the details involved.
These kinds of problems always give me the jeebies because they aren’t coding problems. They’re devops problems, configuration problems. Many of the same problem solving skills apply; but, I always feel like I’m dealing with a dark, obscure, illogical labyrinth. As with anything, it’s something that can be learned. There are many approaches that can be employed, that shed light into the dark corners and reveal what is going on. Because I don’t like it, I resist it. Overcoming that is maybe more than half the battle.
To make matters more difficult, it wasn’t something that cropped-up on my development OS. It was something that only manifested itself on a remote server running a different OS, or on the virtual server I ended-up installing in order to test.
As the illustration by Francisco Goya implies, we conquer these monsters through reason.
In the end, the message itself needed close examination. All of the other aspects of setting-up the Gem and the C library were correct, although I felt uneasy about them. Doing some good searches using the error message revealed the answer. Searching the error message is always a good strategy. I was too wrapped-up in uncertainty about the whole setup.
The ugly details are in a
that I opened seeking help.
It required use of some Linux, shared library diagnostic and configuration
ldconfig, that were new to me. Posting the query,
following-through the suggestions I received, and reading about linking and
shared libraries were key to breaking-through. The act of describing the
problem in writing is helpful in and of itself.
In short, it was an installation problem with the Davenport C library.
There is a known shortcoming in the
and linking setup, such that it doesn’t detect the need, on some systems,
to run the
ldconfig command after installing the library.
That shortcoming was listed as an implementation
in the GNU documentation for
libtool. It’s an opportunity for someone
to make an improvement, probably, to
autoconf. I solved it by running
the command after installation and documenting the
I developed the davenport-ruby gem during the third week of May. (The installation headache came much later, at the end of July.) Find the source on GitHub as wbreeze/davenport-ruby. The next step was to use it. I was ready to start working with the socelect Ruby on Rails code base.