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.(@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:
= {
'pid_file_path' => 'path/to/pid/file',
'log_file_path' => 'path/to/log/file'
}
MyDjinn.djinnify()
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
Copyright © 2010 Craig Paterson. See LICENSE for details.