technology from back to front
on
16/11/11

King Kong! Misadventures in Ruby meta-programming

Sometimes after a particularly fraught bug stomping session you make a frivolous offhand remark to a colleague, for example “I will write a macro that converts lisp definitions in prefix form so that arithmetic looks like how it was taught to you in school” or “I won’t let my unit test really be an integration test ever again”. Today after wrestling with Ruby I mentioned a mild dislike for monkey patching as often used in Ruby and the idea for King Kong was born.

If you don’t like monkey patching in Ruby, then King Kong is the solution. He is a very large primate who likes to have his own way. If you define a class and then monkey patch it King Kong will eat it, it will be gone, no longer available for your use. Fortunately King Kong can’t use source code control systems (yet!) so you can correct your error and ponder his subtle ways.

We shall start by writing a spec:

<pre>
require "rspec"
require "king-kong"

class X
    def x()
        "x"
    end
end

describe "King Kong" do

    it "does not allow monkey patching" do
        Object.const_defined?("X").should == true

        class X
            def x()
                "not x"
            end
        end

        Object.const_defined?("X").should == false
    end
end
</pre>

Now we define King Kong!

<pre>
class Class
    @@method_history = {}

    def method_added(method_name)
        @@method_history[self] ||= {}
        if @@method_history[self][method_name] then
            puts "King kong is the top primate\nHe has eaten your class!"
            Object.send(:remove_const, self.name)
        else
                @@method_history[self][method_name] = true
        end
    end
end
</pre>

Run the spec, spec passes, job done! This code is licensed as is and probably shouldn’t be run in production, or even your QA environment but you are welcome to run it on your development machine for minimal comedic effect.

King Kong is a fine example of Ruby meta-programming and an excellent addition to your arsenal of offensive programming tools taking a large amount of inspiration from tools like Guantanamo. Marvel at how easy it is to completely annihilate a class from existence in Ruby, if that was Java you would have to give a class loader or something a walloping with a GreasySpannerFactory to achieve the same thing.

Remember sometimes monkey patching is a solution and some times it is the cause of the problem!

If you want to take a more serious approach to this problem you might want to look here.

by
tim
on
16/11/11
on
30/04/08

STOMP adapter updated for RabbitMQ 1.3.0

I’ve updated our STOMP adapter for RabbitMQ to fix a bug reported by Carl Bourne. In the process, I updated the code to work with the latest snapshots of RabbitMQ, including the currently-released version, v1.3.0.

You can get the code by checking it out from our repository with

hg clone http://hg.rabbitmq.com/rabbitmq-stomp/
hg update rabbitmq_v1_3_0_branch

UPDATE: use the default branch these days, unless you’re still running 1.3.0!

or you can instead download a snapshot of the current state of the adapter[1], currently at revision 90dd1726fe0b.

(Update: I forgot to mention that the mercurial repository has two branches in it: default, which tracks our internal RabbitMQ server repository, and rabbitmq_v1_3_0_branch, which should stay compatible with the 1.3.0 server release. Thanks to Aman Gupta, who pointed out the problem in a comment below!)

Here’s a summary of how to build and run a STOMP-enabled RabbitMQ broker – for more details, see the original post on the topic:

  1. First, retrieve the RabbitMQ server 1.3.0 source code, and unpack it:
curl&nbsp;http://www.rabbitmq.com/releases/source/rabbitmq-1.3.0.tar.gz | tar -zxvf -
  1. Next, grab the latest STOMP adapter (here we download a copy of the rabbitmq_v1_3_0_branch rather than the main trunk):
curl&nbsp;http://hg.rabbitmq.com/rabbitmq-stomp/archive/rabbitmq\_v1\_3\_0\_branch.tar.gz | tar -zxvf -
  1. Compile the server itself:
make -C rabbitmq-1.3.0/erlang/rabbit
  1. Finally, compile the adapter, and start the server with extra options that cause the adapter to start too:
make -C rabbitmq-stomp-rabbitmq\_v1\_3\_0\_branch run

If this is successful, you should end up with “starting STOMP-listeners …done” and “broker running” in your terminal. At this point you can try out the service – for instance, you can run Carl’s test cases if you have ruby and rubygems handy:

sudo apt-get install ruby
sudo apt-get install rubygems
sudo gem install stomp
ruby rabbitmq-stomp-rabbitmq_v1_3_0_branch/priv/tests-ruby/cb-receiver.rb

and in another window

ruby rabbitmq-stomp-rabbitmq_v1_3_0_branch/priv/tests-ruby/cb-sender.rb

It will transfer 10,000 short messages, and end up displaying

...
Test Message number 9998
Test Message number 9999
All Done!

in the receiver-side terminal.

If you’re interested in the gory details of the bug-fix itself, you can see the relevant patch here. The problem was that the code that handled abrupt socket closure wasn’t handshaking with enough of the internals of the server to ensure that the last few work items were being processed successfully. Trapping socket closure in the STOMP adapter code, and politely handshaking, turned out to be all that was required. An alternative workaround would be to use STOMP’s DISCONNECT method before closing the socket on the client side.


Footnote 1: Note that despite the misleading URL, the snapshot download really is of the STOMP adapter, and not of the broker itself! I’m making use of hgwebdir’s archive-download feature here.

by
tonyg
on
30/04/08
on
02/02/08

How to run RabbitMQ’s experimental STOMP adapter

The code for the experimental STOMP adapter for RabbitMQ is distributed separately from the main server, at the moment. This post walks through the steps needed to try the adapter out, running it against a recent snapshot release of RabbitMQ. The perl Net::Stomp STOMP client is used to demonstrate the adapter in action, subscribing to a queue and sending a couple of messages to it.

You will need:

  • Erlang R11B-5 installed, with
erl

and

erlc

on your path * GNU Make * A recent snapshot of RabbitMQ. For this example, I used this one. * A recent snapshot of the RabbitMQ STOMP adapter. An up-to-the-minute snapshot is available here (generated on-the-fly from a mercurial repository).

and, optionally, Perl to run the Net::Stomp example (or you could write your own examples using any other STOMP client, of course!); my Mac laptop comes with perl 5.8.8.

The first step is to unpack and compile the RabbitMQ server.

$ tar -zxvf rabbitmq-200801150657.tar.gz
$ cd rabbitmq-200801150657/erlang/rabbit/
$ make
$ cd ../../..

Once that’s done, fetch and extract the latest RabbitMQ STOMP adapter.

$ curl http://hg.rabbitmq.com/rabbitmq-stomp/archive/default.tar.bz2 \
    | tar -jxvf -

Each time a commit is made to the STOMP adapter’s mercurial repository, the hexadecimal revision identifier changes, so the directory created by the unpacking of the snapshot you retrieve will be different. At the time of writing, the STOMP adapter’s directory was

rabbitmq-stomp-7b00398dd81f

.

To compile the STOMP adapter, you need to point

make

at the location of the RabbitMQ server codebase you’ve just compiled:

$ cd rabbitmq-stomp-7b00398dd81f/
$ make RABBIT_SOURCE_ROOT=../rabbitmq-200801150657

If you’ve managed to get through all those steps successfully, it’s time to try running the server. Issue the following “make run” command from the

rabbitmq-stomp-7b00398dd81f

directory:

$ make RABBIT_SOURCE_ROOT=../rabbitmq-200801150657 run
make -C ../rabbitmq-200801150657/erlang/rabbit run \
                RABBIT_ARGS='-pa '"$(pwd)/ebin"' -rabbit \
                        stomp_listeners [{\"0.0.0.0\",61613}] \
                        extra_startup_steps [{\"STOMP-listeners\",rabbit_stomp,kickstart,[]}]'
NODE_IP_ADDRESS= NODE_PORT= NODE_ONLY=true LOG_BASE=/tmp  RABBIT_ARGS="-pa /Users/tonyg/dev/AMQ/scratch/rabbitmq-stomp-7b00398dd81f/ebin -rabbit                stomp_listeners [{\"0.0.0.0\",61613}]           extra_startup_steps [{\"STOMP-listeners\",rabbit_stomp,kickstart,[]}] -s rabbit" MNESIA_DIR=/tmp/rabbitmq-rabbit-mnesia ./scripts/rabbitmq-server
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:30] [kernel-poll:true]

Eshell V5.5.5  (abort with ^G)
(rabbit@walk)1> RabbitMQ 200801150657 (AMQP 8-0)
Copyright (C) 2007 LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
Licensed under the MPL.  See http://www.rabbitmq.com/

Logging to "/tmp/rabbit.log"
SASL logging to "/tmp/rabbit-sasl.log"

starting database             ...done
starting core processes       ...done
starting recovery             ...done
starting persister            ...done
starting builtin applications ...done
starting TCP listeners        ...done
starting STOMP-listeners      ...done

broker running

Now, let’s try it out. I’ll use the Perl Net::Stomp module, available on CPAN.

$ sudo cpan -i Net::Stomp

The examples I’m going to try out are those from the Net::Stomp documentation – run

perldoc Net::Stomp

to read the originals. Save the following code into

rabbit\_stomp\_send.pl

:

# send a message to the queue 'foo'
use Net::Stomp;
my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'});
$stomp->connect({login=>'guest', passcode=>'guest'});
$stomp->send({destination=>'foo', body=>($ARGV[0] or 'test message')});
$stomp->disconnect;

and the following code into

rabbitmq\_stomp\_recv.pl

:

# subscribe to messages from the queue 'foo'
use Net::Stomp;
my $stomp = Net::Stomp->new({hostname=>'localhost', port=>'61613'});
$stomp->connect({login=>'guest', passcode=>'guest'});
$stomp->subscribe({'destination'=>'foo', 'ack'=>'client'});
while (1) {
    my $frame = $stomp->receive_frame;
    print $frame->body . "\n";
    $stomp->ack({frame=>$frame});
    last if $frame->body eq 'QUIT';
}
$stomp->disconnect;

Run the receiver before the sender to make sure the queue exists at the moment the send takes place. In one terminal window, start the receiver:

$ perl ./rabbitmq_stomp_recv.pl

In another terminal window, run the sender:

$ perl ./rabbitmq_stomp_send.pl
$ perl ./rabbitmq_stomp_send.pl "hello world"
$ perl ./rabbitmq_stomp_send.pl QUIT

The receiver’s window should contain the received messages:

$ perl ./rabbitmq_stomp_recv.pl 
test message
hello
QUIT
$
by
tonyg
on
02/02/08
2000-13 LShift Ltd, 1st Floor Office, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK +44 (0)20 7729 7060