Ruby Procs and Eventmachine callbacks

Posted by dave
on Sunday, January 17

For the last little while, I’ve been messing around to see whether I can whip off a video-encoding server which is easily installable. It’s put together using Sinatra, Thin, and Eventmachine (three of my favourite Ruby libraries lately).

At the point where it all started to work, I ran into a nasty order-of-operations problem. To fix it, I had to do a little research into how Ruby sets up blocks and procs.

The basic system is fairly simple. There’s a web interface written in Sinatra, which runs inside Thin. Off to the side of this running webapp is an Eventmachine process sitting around waiting to encode videos. The webapp interface allows users to easily define encoder profiles, which are sets of tasks for ffmpeg. As an example, we might define an Encoder with three EncodingTask objects attached to it:

  • output to 320×240 flash video
  • output to 720×576 ogg theora
  • make a 320×240 thumbnail jpg.

When this Encoder is applied to a new video, the tasks are run in order on the video. Once there are no more tasks, the video state is changed to “complete”.

The code that accomplishes this is pretty simple, except for the fact that it’s multithreaded:

  def ffmpeg(task, video)
    command_string = "ffmpeg -i #{video.file} #{task.command} #{video.file + task.output_file_suffix}" 
    encoding_operation = proc {
      video.state = "encoding" 
      video.save
      Log.info("Executing: #{task.name}")
      `nice -n 19 #{command_string}`
    }
    completion_callback = proc {|result|
      if task == encoding_tasks.last
        video.state = "complete" 
        video.save
      else
        current_task_index = encoding_tasks.index(task)
        next_task_index = current_task_index + 1
        next_task = encoding_tasks[next_task_index]
        ffmpeg(next_task, video)
      end
    }
    EventMachine.defer(encoding_operation, completion_callback)
  end

The first time through, the code is called with the first EncodingTask and whatever video is being encoded. The method then calls itself again for every EncodingTask until there are no more EncodingTasks left in the queue, at which point the video is marked as complete and everything chills out. The main motivations for this code are:

  • We need to execute a long-running, blocking process (the ffmpeg encoding)
  • We want the Sinatra/Thin application to keep running while we’re encoding, because we want to be able to queue up other videos to encode

The EventMachine.defer method does what we need here. EventMachine is mainly designed to be blindingly fast for network operations by using a single-threaded evented reactor loop, but the single-threaded approach only works in cases where non-blocking processing is being done. For blocking I/O, like the call to ffmpeg here, EventMachine.defer comes to the rescue for us: by default, EventMachine sets up a pool of 20 ruby threads which are available for use by EventMachine.defer in cases where it’s really necessary to use multiple ruby threads.

Don’t confuse EventMachine.defer with EventMachine Deferrables, which are related to the single-threaded part of EventMachine. EM.defer is a method which takes two proc objects as its arguments – the first should contain your long-running operation, the second is a callback which should be executed once the long-running operation is completed. The main problem with my code above is that it didn’t work.

I naively expected that I could set the local variable current_task_index inside my completion_callback proc and use it normally. This was a really dumb thing to do, as I realized once I did a little refresher research into Proc objects and how they work in Ruby.

Procs get their variables from their surrounding scope when they are defined, not when they are called. This led to all kinds of exciting order-of-operations bugs in my code, which was all speedily fixed once I moved the current_task_index = encoding_tasks.index(task) line so that it was the first thing defined in the method.

Capistrano Copying your Production data to your Staging environment

Posted by dave
on Friday, November 06

Here’s a quick bit of capistrano to copy your production data into your staging environment if you’re running a multi-stage setup:

desc "Loads the production database and assets into the staging environment" 
task :load_production_data, :roles => :app do
  require 'yaml'
  # get the database.yml file from the server
  production_yml = "config/database.production#{Time.now.strftime '%Y-%m-%d_%H:%M:%S'}.yml" 
  staging_yml = "config/database.staging#{Time.now.strftime '%Y-%m-%d_%H:%M:%S'}.yml" 

  begin
    get "/path/to/your/production/current/config/database.yml", production_yml
    get "#{current_path}/config/database.yml", staging_yml

    # load the values
    p_config = YAML::load_file(production_yml)
    username = p_config['production']['username']
    password = p_config['production']['password']
    database = p_config['production']['database']
    s_config = YAML::load_file(staging_yml)
    s_username = s_config['staging']['username']
    s_password = s_config['staging']['password']
    s_database = s_config['staging']['database']
  rescue
    raise
  ensure
    `rm -f #{production_yml};rm -f #{staging_yml}`
  end

  # copy the SQL data
  filename = "/tmp/dump.#{Time.now.strftime '%Y-%m-%d_%H:%M:%S'}.sql.gz" 
  run "mysqldump -u #{username} --password='#{password}' #{database} | gzip > #{filename}" 
  run "gunzip -c #{filename} | mysql -u #{s_username} --password='#{s_password}' #{s_database} && rm -f gunzip #{filename}" 

  # Copy media files under system  
  run "cp -Rf /path/to/your/production/current/public/system/* #{current_path}/public/system/" 
end

Seems to work alright for us – remember that you may need to re-apply database migrations using cap deploy:migrate in case your staging database is ahead of your production database.

BreadMachine: a 3-D Secure capable XPay gem for Ruby

Posted by dave
on Wednesday, October 07

We’ve currently got a requirement to integrate credit card payment processing in a way where

  • we can deal with some fairly strange, long-running business logic (payments that can take a month before they’re settled) and
  • we don’t really want to store full credit card info for security and liability reasons.

As both Dan and Matt have had good experiences in the past when working with SecureTrading, and they can handle our strange payment flows without any trouble, we’ve decided to go ahead and write a gem to integrate our app with SecureTrading’s XPay gateway.

Our code could only charitably be called “pre-alpha” in its current form, but we have successfully run some transactions through the test gateway. 3-D Secure verification, beloved by application developers everywhere, works in our tests – our app shoots card info at BreadMachine, which does the 3-D Secure enrollment check. Based on what BreadMachine tells it, our app then either does a standard auth check for funds, or redirects to the acquiring bank’s 3D-Secure verification page. The user then punches in their info, and get redirected back to our app, which either does a 3-D Secure auth request for funds or tells the user to get lost depending on the results of the 3-D Secure verification check.

The whole process is a huge hassle from a development point of view, but it’s required for certain cards which see widespread use in the UK (such as Maestro), and may be required by many more acquiring banks in future despite the many criticisms of the system. As far as we can tell, we’re the first ones to attempt to deal with 3-D Secure in a rubygem format. ActiveMerchant don’t do it, and given the much more ambitious nature of their multi-gateway code, that’s probably a smart choice (there is a fork available which adds some 3-D Secure integration, though). However, if you’re using SecureTrading and need to do 3-D Secure (or regular) auth, you can

gem install breadmachine

or fork the code on Github.

The bulk of the code was written by Matt. Currently there are no plans to turn this into a multi-gateway gem, a la ActiveMerchant, but I guess it really depends on how forky people want to get with the code.

Gedit On Rails

Posted by dave
on Thursday, October 01

As part of a general quest to slim down my programming environment (driven largely by the purchase of a Samsung NC-10 netbook and a love of programming on trains), I’ve switched to gedit as my main editor. It’s been hugely enjoyable to code in a lightweight environment – and the gedit-mate git repo in combination with apt-get install gedit-plugins makes it easy to set up a decent Ruby environment.

The only small problem I’ve had with the gedit-mate installer was that syntax highlighting wasn’t working for me out of the box. To fix it, I’ve forked the github repo and pushed a commit that installs the ruby_on_rails.lang file so that everything works properly on Ubuntu 9.04.