Djinn

Djinn is a helper for building simple daemons.

In Arabian mythology a Djinn is a supernatural creature which occupies a parallel world to that of mankind.

Documentation

rdoc.info/projects/craigp/djinn

Installation

gem install djinn

Quickstart Example

Because you might not want to read the wordy version of the documentation, let’s dive right in and start by writing a simple Djinn and saving it in a file called file_djinn.rb

#!/usr/bin/env ruby

require 'rubygems'
require 'djinn'

class FileDjinn

  include Djinn

  djinn do

    configure do
      add :output_file do
        short_switch  "-o"
        long_switch   "--output-file"
        description   "File to output stuff to"
        required      true
      end

      add_flag :create_file do
        short_switch  "-c"
        long_switch   "--create-file"
        description   "Create output file if it doesn't exist"
      end
    end

    start do
      @file = unless File.exists?(config[:output_file])
        unless config[:create_file]
          log "File not found: #{config[:output_file]}"
          nil
        else
          File.open(config[:output_file], 'a')
        end
      else
        File.open(config[:output_file], 'a')
      end
      if @file
        log "Opening output file: #{File.expand_path(@file.path)}"
        loop do
          @file.puts "Writing to the file at #{Time.now}"
          @file.flush
          sleep(5)
        end
      end
    end

    stop do
      log "Djinn is stopping.."
    end

    exit do
      if @file
        log "Closing output file.."
        @file.close
      end
    end
  end

end

FileDjinn.djinnify

Now we can try and start it using:

ruby file_djinn.rb

Oops, no luck, but you should see something like:

Missing argument: output_file

Usage: djinn_file [OPTIONS]
        --no-daemon                  Don't run in the background
        --stop                       Stop the Djinn, if possible
    -o, --output-file OUTPUT_FILE    File to output stuff to
    -c, --create-file                Create output file if it doesn't exist
    -h, --help                       Prints this message

Right, now that we know more about this Djinn, let’s try again..

ruby file_djinn.rb -co test.log --no-daemon

Our shiny new Djinn is running in the foreground! But lets make it run as a daemon:

ruby file_djinn.rb -co test.log

Awesomesauce. Now, I wonder if we can stop it:

ruby file_djinn.rb --stop

In More Detail

Okay, so lets pull that example apart and look at things in a little more detail..

Djinn Definition

Transmogrifying an otherwise workaday class into a Djinn is all about the Djinn definition DSL. So we’ll start with the basics, including the module and starting a definition block.

class MyDjinn

  include Djinn

  djinn do

  end

end

Right, now we need to tell it what to do, and when. There are three events we can hook up to in the lifecycle of a Djinn: start, stop and exit. Doing something when the Djinn starts is the most important, since otherwise .. well, it just won’t do anything. So lets use a simple loop for now:

class MyDjinn

  include Djinn

  djinn do

    start do
      loop do
        log "Doing something.."
        sleep(5)
      end
    end

  end

end

We’ve used another helper method provided to us there, namely log. This will write a timestamped message for us, either to the log if we’re running in the background, or to stdout if we’re running in the foreground.

Just like that we have a working Djinn, well except for one line, at the bottom of the file:

MyDjinn.djinnify

That will take care of parsing any arguments and running the Djinn. We now have working Djinn that will log some entirely useless text to a log file!

Configuration

There are a few options available to us if we want to pass configuration information to our Djinn.

First, we can do it using command line switches, which we can define as part of our Djinn definition block. Taken from the example above:

djinn do

  configure do
    add :output_file do
      short_switch  "-o"
      long_switch   "--output-file"
      description   "File to output stuff to"
      required      true
    end

    add_flag :create_file do
      short_switch  "-c"
      long_switch   "--create-file"
      description   "Create output file if it doesn't exist"
    end
  end   

end

This is pretty straight-forward. The nice thing about doing it like this is that these options will show up in the usage banner if you pass the “–help” command line argument, or screw something up.

By default the daemon will look for a config YAML file in same directory as you executed it from, named the same as the Djinn class, so in this case my_djinn.yml. It will by default create the pid and log files in the same way. You can change this by putting it in the config file or supplying an options hash:

options = {
  'pid_file_path' => 'path/to/pid/file',
  'log_file_path' => 'path/to/log/file'
}

MyDjinn.djinnify(options)

This also illustrates that you can pass config to the djinnify method as well, so now you have two additional ways to set configuration values.

The actions (start, run, etc) are executed in the context of the Djinn itself, so you can get at the config without having to pass it around:

djinn do
  start do
    my_setting = config[:omghax]
  end
end

...

djinn.djinnify(:omghax => "Groovy, baby")

You can also give it a block to work with:

djinn.djinnify do
  puts "This will happen before calling the :start action"
end

If you need to man-handle the internals and stuff, it yields itself:

djinn.djinnify do |djinn|
  djinn.config[:omghax] = "Groovy, baby"
end

Rails Example

The original intention of the gem was to build daemons for my Rails apps, and there’s a simple example Rails app with a daemon in the example directory if you check the code out of git, but here’s the gist of it. Assumes a scenario where you have a Book model that keeps a count of how many times a book has been read..

Create a file in RAILS_ROOT/lib or somewhere similarly convenient:

require 'djinn/rails'
require 'eventmachine'

class BookDjinn

  BOOK_WORKER_INTERVAL = 5

  include Djinn::Rails

  djinn do

    start do
      EM.run do
        log "Workers will run every #{BOOK_WORKER_INTERVAL} secs"
        EM::PeriodicTimer.new(BOOK_WORKER_INTERVAL) do
          log "There are #{Book.count} book(s) in the database"
          log "Updating read counts for all books.."
          Book.all.each &:read!
        end
      end
    end

    exit do
      EM.stop
    end

  end

end

Right, now you need to start it somehow. The easiest way is to create a file, lets call it book_djinn, in RAILS_ROOT/scripts and pop this in it:

#!/usr/bin/env ruby
require 'rubygems'
require File.join(File.dirname(__FILE__), '../lib/book_djinn')
BookDjinn.djinnify_rails

Righto, now start it from RAILS_ROOT:

ruby script/book_djinn

This functions exactly like the non-Rails Djinn did. Try this:

ruby script/book_djinn --help

That should give you a better idea of what’s going on, then try this:

ruby script/book_djinn start -e production

Yay, we have a daemon running in the background! As you can see above, the Rails implementation automatically adds a switch for the environment for you - this will default to development if you don’t supply a value for it.

To stop the Djinn:

ruby script/book_djinn --stop

That gives you more-or-less everything you need to build something basic and monitor it with god or a similar process monitor.

Rails Djinns look for their configuration in a different location - the RAILS_ROOT/config folder. Similarly they write their logs to your RAILS_ROOT/log folder, rather than to the Djinn’s home folder.

TODO

Update this documentation. Make the code cooler.

Copyright © 2010 Craig Paterson. See LICENSE for details.