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¶ ↑
Copyright © 2010 Craig Paterson. See LICENSE for details.