Cromwell¶ ↑
Lord Protector of your scripts.
Description¶ ↑
This is a very simple wrapper over Signal#trap
method that allows you to easily protect your scripts from being killed while they are doing something that should not be interrupted (e.g. interacting with some non-transactional service) or is too costly to restart (e.g. long computations).
While inside the protected block, your script will ignore certain signals and continue its work, but if a signal was caught, it will terminate once the protected block is over. By default, only following signals are ignored: INT
(keyboard interrupt ^C
), TERM
(sent by kill
by default), HUP
(sent when shell terminates), and QUIT
(a “dump core” signal, sent with ^\
), but you can specify any list of them (except for KILL
and STOP
, of course).
For a full signal list supported on your operating system, run Signal.list
in your irb
. For more info on signals and their meaning, check your local man signal
.
This gem is based on real-life production code. It is especially useful for protecting various daemon-like scripts in Rails application that are (might be) restarted with every deploy.
Installation procedure¶ ↑
Thanks to Gemcutter, installation is as simple as:
sudo gem install cromwell
If you plan on changing anything, you should run tests and tests require two additional gems: shoulda and mocha. Install them with:
sudo gem install thoughtbot-shoulda mocha
Usage examples¶ ↑
The most important in Cromwell API is the protect
method. It can be called in two ways: with a block and without a block.
Block form¶ ↑
When used with a block, Cromwell executes the code inside the block protecting it from being interrupted with a signal:
puts 'See you in a while...' Cromwell.protect { sleep 10 } puts "You're still here?"
When you run this script (which lives in examples/example1.rb), you won’t be able to interrupt it with ^C
or simple kill
while it’s sleeping for ten seconds:
$ ruby examples/example1.rb See you in a while... ^C^C^C^C [ ten seconds pass... ] $
Because I tried to interrupt the script, it was terminated once the protected block was over. Had I not pressed ^C
, the last line would be executed:
$ ruby examples/example1.rb See you in a while... [ ten seconds pass... ] You're still here? $
The script cannot be killed, too (I run it in background to be able to run other commands in the same shell):
$ ruby examples/example1.rb & [1] 70300 See you in a while... $ kill 70300 [ ten seconds pass... ] $ [1]+ Done ruby examples/example1.rb
If you really want to kill it, use kill -9
:
$ ruby examples/example1.rb & [1] 70328 See you in a while... $ kill -9 70328 $ [1]+ Killed ruby examples/example1.rb
Non-block form¶ ↑
If you want to have more control over what’s protected in your script, you can use protect
without the block. In that case your code will be protected until you call unprotect
method:
puts 'See you in a while...' Cromwell.protect sleep 10 Cromwell.unprotect puts "You're still here?"
The above code lives in examples/example2.rb and behaves in the same way as previous example.
In general it might be good idea to place the call to unprotect
in an ensure
block. Or, if you want your script to just run until it finishes on its own, don’t call unprotect
at all.
Specifying other signals¶ ↑
If you want to protect from other signals than the default list, specify them as parameters to protect
method:
puts "You can't stop me with ^C but you can kill me. My pid is #{$$}." Cromwell.protect("INT") { sleep 10 } puts "You're still here?"
This script is still immune to ^C
:
$ ruby examples/example3.rb You can't stop me with ^C but you can kill me. My pid is 70243. ^C^C^C^C [ ten seconds pass... ] $
But can be killed:
$ ruby examples/example3.rb & [1] 70245 You can't stop me with ^C but you can kill me. My pid is 70245. $ kill 70245 [1]+ Terminated ruby examples/example3.rb $
Inspecting state¶ ↑
You can inspect Cromwell’s state with two methods:
-
Cromwell.protected?
returnstrue
when your code is protected,false
otherwise. -
Cromwell.should_exit?
returnstrue
when a signal was caught and termination will occur after the protected code is over.
Preventing termination¶ ↑
Since version 0.1.2, you can prevent termination of your script even when a signal was caught. To do so, use
Cromwell.should_exit = false
like that:
puts 'You can try to kill me but I will survive!' Cromwell.protect { begin sleep 10 ensure puts "Oh noes! You wanted to kill me! But I'll continue my work!" if Cromwell.should_exit? Cromwell.should_exit = false end } puts "You're still here?"
This script will continue working even after ^C
:
$ ruby examples/example4.rb You can try to kill me but I will survive! ^C^C^C [ ten seconds pass... ] Oh noes! You wanted to kill me! But I'll continue my work! You're still here? $
Removing traps¶ ↑
As of version 0.2, any traps that were previously installed are restored by the unprotect method (which is also called by the protect method if a block was given to it, of course). This means if you had some other code installed to handle signals (profiling, debugging etc.) it should be restored. For example:
Signal.trap("INT") { puts "Original signal handler!" } puts 'See you in a while...' Cromwell.protect { sleep 1 } puts "Try to ^C now to see original signal handler in action!" sleep 10
When run, it goes like this:
$ ruby examples/example5.rb See you in a while... [ 1 second passes... ] Try to ^C now to see original signal handler in action! ^COriginal signal handler! ^COriginal signal handler! ^COriginal signal handler! [ ten seconds pass... ] $
Of course, had I press ^C
right after “See you in a while…”, the script would terminate after 1 second at the end of the protected block.
Logging¶ ↑
If want to see what is going on with Cromwell or have some problems with it, you can use a logger (requires cromwell gem version >= 0.3). If Cromwell is used inside an application that already uses logging, you can use your app’s logger. Or you might prefer to use a separate logger.
Cromwell uses two levels of log messages:
-
Logger::INFO
– when a signal was caught or script is terminated. -
Logger::DEBUG
– all sorts of debugging information: setting up and restoring traps, calling methods and yielding block. Probably not useful for you, unless you have some problems with Cromwell or are messing with the code.
Here’s an example of logger usage:
Cromwell.logger = Logger.new(STDOUT) Cromwell.logger.level = Logger::INFO puts 'See you in a while...' Cromwell.protect { sleep 10 } puts "You're still here?"
When run, this script will log messages on STDOUT
:
$ ruby examples/example6.rb See you in a while... ^CI, [2010-01-14T11:59:29.246851 #19011] INFO -- : Caught signal INT -- ignoring. ^CI, [2010-01-14T11:59:31.558532 #19011] INFO -- : Caught signal INT -- ignoring. ^CI, [2010-01-14T11:59:36.270395 #19011] INFO -- : Caught signal INT -- ignoring. [ ten seconds pass... ] I, [2010-01-14T11:59:37.872514 #19011] INFO -- : Exiting because should_exit is true $
Custom traps¶ ↑
Starting with version 0.4, you can provide your own trap to handle signal. Possible uses of this feature that I can imagine:
-
ask user for password so only admin can kill the script,
-
setup some counter and exit the script after 3 signals,
-
handle some signals differently.
In the following example (examples/example7.rb), SIGINT
is ignored completely, while SIGQUIT
is handled normally (exit after protected block), but with a message:
Cromwell.custom_traps["INT"] = proc { puts "Trying your ^C skills, are you?" } Cromwell.custom_traps["QUIT"] = proc { puts "We'll be leaving soon!" Cromwell.should_exit = true } puts 'See you in a while...' Cromwell.protect { sleep 10 } puts "You're still here?"
Let’s see it in action:
$ ruby examples/example7.rb See you in a while... ^CTrying your ^C skills, are you? [ ten seconds pass... ] You're still here? $
The last message printed means that the script was not terminated after protected block because the signal trap did not set should_exit
to true
. And now, let’s try sending SIGQUIT
:
$ ruby examples/example7.rb See you in a while... ^\We'll be leaving soon! [ ten seconds pass... ] $
This time the script didn’t get to execute the last puts
statement.
Compatibility¶ ↑
Works for me. Tested on Mac OS X 10.4–10.6 and a little bit on Debian Linux. If it works for you too, I’d be glad to know. Cromwell’s reliability depends heavily on your operating system’s signals implementation reliability (which may not be very stable on some systems).
To Do list¶ ↑
-
Empty for now… If you miss some feature, let me know.
Changelog¶ ↑
0.4¶ ↑
-
Added custom traps.
0.3¶ ↑
-
Added logger support.
0.2¶ ↑
-
Remove traps when they are not needed anymore and restore original traps.
0.1.2¶ ↑
-
Allow to prevent termination of your script even when a signal was caught.
-
Ensure that examples use ../lib/cromwell.rb not the installed gem
Note on Patches/Pull Requests¶ ↑
-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
Send me a pull request. Bonus points for topic branches.
Note on terminology¶ ↑
The protection from signals provided by Cromwell and the method names protect
, unprotect
, and protected?
have nothing to do with Ruby’s protected
keyword and the general concept of a protected method in Ruby and other object-oriented languages.
Copyright¶ ↑
Copyright © 2009 Przemyslaw Kowalczyk. See LICENSE for details.