cromwell 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +60 -12
- data/VERSION +1 -1
- data/cromwell.gemspec +5 -3
- data/examples/example7.rb +19 -0
- data/lib/cromwell.rb +71 -76
- data/test/test_cromwell.rb +20 -6
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -6,9 +6,9 @@ Lord Protector of your scripts.
|
|
6
6
|
|
7
7
|
This is a very simple wrapper over <code>Signal#trap</code> 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).
|
8
8
|
|
9
|
-
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:
|
9
|
+
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 <code>^C</code>), +TERM+ (sent by +kill+ by defautl), +HUP+ (sent when shell terminates), and +QUIT+ (a "dump core" signal, sent with <code>^\\</code>), but you can specify any list of them (except for +KILL+ and +STOP+, of course).
|
10
10
|
|
11
|
-
For a full signal list supported on your operating system, run <code>Signal.list</code> in your
|
11
|
+
For a full signal list supported on your operating system, run <code>Signal.list</code> in your +irb+. For more info on signals and their meaning, check your local <code>man signal</code>.
|
12
12
|
|
13
13
|
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.
|
14
14
|
|
@@ -24,7 +24,7 @@ If you plan on changing anything, you should run tests and tests require two add
|
|
24
24
|
|
25
25
|
== Usage examples
|
26
26
|
|
27
|
-
The most important in Cromwell API is the
|
27
|
+
The most important in Cromwell API is the +protect+ method. It can be called in two ways: with a block and without a block.
|
28
28
|
|
29
29
|
=== Block form
|
30
30
|
|
@@ -36,7 +36,7 @@ When used with a block, Cromwell executes the code inside the block protecting i
|
|
36
36
|
}
|
37
37
|
puts "You're still here?"
|
38
38
|
|
39
|
-
When you run this script (which lives in {examples/example1.rb}[http://github.com/szeryf/cromwell/blob/master/examples/example1.rb]), you won't be able to interrupt it with <code>^C</code> or simple
|
39
|
+
When you run this script (which lives in {examples/example1.rb}[http://github.com/szeryf/cromwell/blob/master/examples/example1.rb]), you won't be able to interrupt it with <code>^C</code> or simple +kill+ while it's sleeping for ten seconds:
|
40
40
|
|
41
41
|
$ ruby examples/example1.rb
|
42
42
|
See you in a while...
|
@@ -73,7 +73,7 @@ If you really want to kill it, use <code>kill -9</code>:
|
|
73
73
|
|
74
74
|
=== Non-block form
|
75
75
|
|
76
|
-
If you want to have more control over what's protected in your script, you can use
|
76
|
+
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:
|
77
77
|
|
78
78
|
puts 'See you in a while...'
|
79
79
|
Cromwell.protect
|
@@ -83,11 +83,11 @@ If you want to have more control over what's protected in your script, you can u
|
|
83
83
|
|
84
84
|
The above code lives in {examples/example2.rb}[http://github.com/szeryf/cromwell/blob/master/examples/example2.rb] and behaves in the same way as previous example.
|
85
85
|
|
86
|
-
In general it might be good idea to place the call to
|
86
|
+
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.
|
87
87
|
|
88
88
|
=== Specifying other signals
|
89
89
|
|
90
|
-
If you want to protect from other signals than the default list, specify them as parameters to
|
90
|
+
If you want to protect from other signals than the default list, specify them as parameters to +protect+ method:
|
91
91
|
|
92
92
|
puts "You can't stop me with ^C but you can kill me. My pid is #{$$}."
|
93
93
|
Cromwell.protect("INT") {
|
@@ -116,8 +116,8 @@ But can be killed:
|
|
116
116
|
|
117
117
|
You can inspect Cromwell's state with two methods:
|
118
118
|
|
119
|
-
* <code>Cromwell.protected?</code> returns
|
120
|
-
* <code>Cromwell.should_exit?</code> returns
|
119
|
+
* <code>Cromwell.protected?</code> returns +true+ when your code is protected, +false+ otherwise.
|
120
|
+
* <code>Cromwell.should_exit?</code> returns +true+ when a signal was caught and termination will ocur after the protected code is over.
|
121
121
|
|
122
122
|
=== Preventing termination
|
123
123
|
|
@@ -195,7 +195,7 @@ Here's an example of logger usage:
|
|
195
195
|
}
|
196
196
|
puts "You're still here?"
|
197
197
|
|
198
|
-
When run, this script will log messages on
|
198
|
+
When run, this script will log messages on +STDOUT+:
|
199
199
|
|
200
200
|
$ ruby examples/example6.rb
|
201
201
|
See you in a while...
|
@@ -206,16 +206,64 @@ When run, this script will log messages on <code>STDOUT</code>:
|
|
206
206
|
I, [2010-01-14T11:59:37.872514 #19011] INFO -- : Exiting because should_exit is true
|
207
207
|
$
|
208
208
|
|
209
|
+
=== Custom traps
|
210
|
+
|
211
|
+
Starting with version 0.4, you can provide your own trap to handle signal. Possible uses of this feature that I can imagine:
|
212
|
+
|
213
|
+
* ask user for password so only admin can kill the script,
|
214
|
+
* setup some counter and exit the script after 3 signals,
|
215
|
+
* handle some signals differently.
|
216
|
+
|
217
|
+
In the following example ({examples/example7.rb}[http://github.com/szeryf/cromwell/blob/master/examples/example7.rb]), +SIGINT+ is ignored completely, while +SIGQUIT+ is handled normally (exit after protected block), but with a message:
|
218
|
+
|
219
|
+
Cromwell.custom_traps["INT"] = proc {
|
220
|
+
puts "Trying your ^C skills, are you?"
|
221
|
+
}
|
222
|
+
|
223
|
+
Cromwell.custom_traps["QUIT"] = proc {
|
224
|
+
puts "We'll be leaving soon!"
|
225
|
+
Cromwell.should_exit = true
|
226
|
+
}
|
227
|
+
|
228
|
+
puts 'See you in a while...'
|
229
|
+
Cromwell.protect {
|
230
|
+
sleep 10
|
231
|
+
}
|
232
|
+
puts "You're still here?"
|
233
|
+
|
234
|
+
Let's see it in action:
|
235
|
+
|
236
|
+
$ ruby examples/example7.rb
|
237
|
+
See you in a while...
|
238
|
+
^CTrying your ^C skills, are you?
|
239
|
+
[ ten seconds pass... ]
|
240
|
+
You're still here?
|
241
|
+
$
|
242
|
+
|
243
|
+
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+:
|
244
|
+
|
245
|
+
$ ruby examples/example7.rb
|
246
|
+
See you in a while...
|
247
|
+
^\We'll be leaving soon!
|
248
|
+
[ ten seconds pass... ]
|
249
|
+
$
|
250
|
+
|
251
|
+
This time the script didn't get to execute the last +puts+ statement.
|
252
|
+
|
209
253
|
== Compatibility
|
210
254
|
|
211
255
|
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).
|
212
256
|
|
213
257
|
== To Do list
|
214
258
|
|
215
|
-
*
|
259
|
+
* Empty for now... If you miss some feature, let me know.
|
216
260
|
|
217
261
|
== Changelog
|
218
262
|
|
263
|
+
=== 0.4
|
264
|
+
|
265
|
+
* Added custom traps.
|
266
|
+
|
219
267
|
=== 0.3
|
220
268
|
|
221
269
|
* Added logger support.
|
@@ -241,7 +289,7 @@ Works for me. Tested on Mac OS X 10.4--10.6 and a little bit on Debian Linux. If
|
|
241
289
|
|
242
290
|
== Note on terminology
|
243
291
|
|
244
|
-
The protection from signals provided by Cromwell and the method names
|
292
|
+
The protection from signals provided by Cromwell and the method names +protect+, +unprotect+, and <code>protected?</code> have <b>nothing</b> to do with Ruby's +protected+ keyword and the general concept of a <i>protected</i> method in Ruby and other object-oriented languages.
|
245
293
|
|
246
294
|
== Copyright
|
247
295
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/cromwell.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{cromwell}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Przemyslaw Kowalczyk"]
|
12
|
-
s.date = %q{2010-01-
|
12
|
+
s.date = %q{2010-01-31}
|
13
13
|
s.description = %q{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). }
|
14
14
|
s.email = %q{szeryf@negativeiq.pl}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -30,6 +30,7 @@ Gem::Specification.new do |s|
|
|
30
30
|
"examples/example4.rb",
|
31
31
|
"examples/example5.rb",
|
32
32
|
"examples/example6.rb",
|
33
|
+
"examples/example7.rb",
|
33
34
|
"lib/cromwell.rb",
|
34
35
|
"test/helper.rb",
|
35
36
|
"test/test_cromwell.rb"
|
@@ -47,7 +48,8 @@ Gem::Specification.new do |s|
|
|
47
48
|
"examples/example3.rb",
|
48
49
|
"examples/example4.rb",
|
49
50
|
"examples/example5.rb",
|
50
|
-
"examples/example6.rb"
|
51
|
+
"examples/example6.rb",
|
52
|
+
"examples/example7.rb"
|
51
53
|
]
|
52
54
|
|
53
55
|
if s.respond_to? :specification_version then
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# ensure that we use ../lib/cromwell.rb, not the installed gem
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
require 'cromwell'
|
5
|
+
|
6
|
+
Cromwell.custom_traps["INT"] = proc {
|
7
|
+
puts "Trying your ^C skills, are you?"
|
8
|
+
}
|
9
|
+
|
10
|
+
Cromwell.custom_traps["QUIT"] = proc {
|
11
|
+
puts "We'll be leaving soon!"
|
12
|
+
Cromwell.should_exit = true
|
13
|
+
}
|
14
|
+
|
15
|
+
puts 'See you in a while...'
|
16
|
+
Cromwell.protect {
|
17
|
+
sleep 10
|
18
|
+
}
|
19
|
+
puts "You're still here?"
|
data/lib/cromwell.rb
CHANGED
@@ -1,12 +1,44 @@
|
|
1
1
|
class Cromwell
|
2
2
|
DEFAULT_SIGNAL_LIST = %w[INT TERM HUP QUIT].freeze
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
@logger = nil
|
5
|
+
@should_exit = false
|
6
|
+
@protected = false
|
7
|
+
@old_traps = {}
|
8
|
+
@custom_traps = {}
|
8
9
|
|
9
10
|
class << self
|
11
|
+
# Something to log Cromwell's messages. This can be a +Logger+ or any other class that
|
12
|
+
# accepts +debug+ and +info+ messages. Default value is +nil+.
|
13
|
+
attr_accessor :logger
|
14
|
+
|
15
|
+
# True when the script will be terminated after protected block, i.e. when a signal
|
16
|
+
# was caught that was protected from. You can set it to false to prevent script from
|
17
|
+
# termination even if a signal was caught.
|
18
|
+
attr_accessor :should_exit
|
19
|
+
alias_method :should_exit?, :should_exit
|
20
|
+
|
21
|
+
# True if the protection is currently active.
|
22
|
+
attr_reader :protected
|
23
|
+
alias_method :protected?, :protected
|
24
|
+
|
25
|
+
# Use this +Hash+ to provide custom traps for signals while protection is active.
|
26
|
+
# The key should be a signal name (in same form as you give it to +protect+ method)
|
27
|
+
# and the value should be a +Proc+ object that will be called when appropriate
|
28
|
+
# signal is caught.
|
29
|
+
#
|
30
|
+
# Note that your block will replace default Cromwell's handler completely,
|
31
|
+
# so if you actually wanted to get the same behavior, you should use:
|
32
|
+
#
|
33
|
+
# Cromwell.should_exit = true
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
#
|
37
|
+
# Cromwell.custom_traps["INT"] = proc {
|
38
|
+
# puts "Trying your ^C skills, are you?"
|
39
|
+
# }
|
40
|
+
attr_accessor :custom_traps
|
41
|
+
|
10
42
|
# call-seq:
|
11
43
|
# Cromwell.protect(*signals) { ... some code ... }
|
12
44
|
# Cromwell.protect(*signals)
|
@@ -18,20 +50,12 @@ class Cromwell
|
|
18
50
|
# is called. Signals can be given in all the forms that <code>Signal#trap</code> recognizes.
|
19
51
|
# Without parameters, the code is protected from the signals in DEFAULT_SIGNAL_LIST.
|
20
52
|
# More info and examples in README.rdoc.
|
21
|
-
def protect *signals
|
53
|
+
def protect *signals, &block
|
22
54
|
debug "Protect called with [#{signals * ', '}]"
|
23
55
|
set_up_traps(signals.empty? ? DEFAULT_SIGNAL_LIST : signals.flatten)
|
24
|
-
|
25
|
-
|
26
|
-
if block_given?
|
27
|
-
begin
|
28
|
-
debug "About to yield to block."
|
29
|
-
yield
|
30
|
-
debug "After yielding to block."
|
31
|
-
ensure
|
32
|
-
unprotect
|
33
|
-
end
|
34
|
-
end
|
56
|
+
@should_exit = false
|
57
|
+
@protected = true
|
58
|
+
protect_block(&block) if block_given?
|
35
59
|
end
|
36
60
|
|
37
61
|
# call-seq:
|
@@ -41,56 +65,14 @@ class Cromwell
|
|
41
65
|
# if signal was caught earlier (so any <code>at_exit</code> code will be executed).
|
42
66
|
# The protect method calls this automatically when executed with a block.
|
43
67
|
def unprotect
|
44
|
-
debug "Unprotect called"
|
45
|
-
|
46
|
-
|
47
|
-
if @@should_exit
|
68
|
+
debug "Unprotect called, should_exit? = #{@should_exit}"
|
69
|
+
@protected = false
|
70
|
+
if @should_exit
|
48
71
|
info "Exiting because should_exit is true"
|
49
72
|
exit
|
73
|
+
else
|
74
|
+
restore_old_traps
|
50
75
|
end
|
51
|
-
restore_old_traps
|
52
|
-
end
|
53
|
-
|
54
|
-
# call-seq:
|
55
|
-
# Cromwell.should_exit?
|
56
|
-
#
|
57
|
-
# True when the script will be terminated after protected block, i.e. when a signal
|
58
|
-
# was caught that was protected from.
|
59
|
-
def should_exit?
|
60
|
-
@@should_exit
|
61
|
-
end
|
62
|
-
|
63
|
-
# call-seq:
|
64
|
-
# Cromwell.should_exit = boolean
|
65
|
-
#
|
66
|
-
# Set to false to prevent script from termination even if a signal was caught. You can also set
|
67
|
-
# this to true to have your script terminated after protected block should you wish so.
|
68
|
-
def should_exit= boolean
|
69
|
-
@@should_exit = boolean
|
70
|
-
end
|
71
|
-
|
72
|
-
# call-seq:
|
73
|
-
# Cromwell.protected?
|
74
|
-
#
|
75
|
-
# True if the protection is currently active.
|
76
|
-
def protected?
|
77
|
-
@@protected
|
78
|
-
end
|
79
|
-
|
80
|
-
# call-seq:
|
81
|
-
# Cromwell.logger
|
82
|
-
#
|
83
|
-
# Returns logger. There is no default logger, so if you haven't set this before, it will be nil.
|
84
|
-
def logger
|
85
|
-
@@logger
|
86
|
-
end
|
87
|
-
|
88
|
-
# call-seq:
|
89
|
-
# Cromwell.logger = some_logger
|
90
|
-
#
|
91
|
-
# Set a logger for Cromwell.
|
92
|
-
def logger= logger
|
93
|
-
@@logger = logger
|
94
76
|
end
|
95
77
|
|
96
78
|
private
|
@@ -103,37 +85,50 @@ class Cromwell
|
|
103
85
|
|
104
86
|
def set_up_trap signal
|
105
87
|
debug "Setting trap for #{signal}"
|
106
|
-
trap
|
107
|
-
if
|
108
|
-
|
109
|
-
@@should_exit = true
|
110
|
-
"IGNORE"
|
88
|
+
trap(signal) {
|
89
|
+
if @custom_traps.has_key? signal
|
90
|
+
@custom_traps[signal].call
|
111
91
|
else
|
112
|
-
|
113
|
-
exit
|
92
|
+
handle signal
|
114
93
|
end
|
115
|
-
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
def handle signal
|
98
|
+
info "Caught signal #{signal} -- ignoring."
|
99
|
+
@should_exit = true
|
100
|
+
"IGNORE"
|
116
101
|
end
|
117
102
|
|
118
103
|
def stash old_trap, signal
|
119
104
|
debug "Stashing old trap #{old_trap} for #{signal}"
|
120
|
-
|
105
|
+
@old_traps[signal] = old_trap
|
121
106
|
end
|
122
107
|
|
123
108
|
def restore_old_traps
|
124
|
-
|
109
|
+
@old_traps.each do |signal, old_trap|
|
125
110
|
debug "Restoring old trap #{old_trap} for #{signal}"
|
126
111
|
trap signal, old_trap
|
127
112
|
end
|
128
|
-
|
113
|
+
@old_traps = {}
|
129
114
|
end
|
130
115
|
|
131
116
|
def debug msg
|
132
|
-
|
117
|
+
@logger.debug msg if @logger
|
133
118
|
end
|
134
119
|
|
135
120
|
def info msg
|
136
|
-
|
121
|
+
@logger.info msg if @logger
|
122
|
+
end
|
123
|
+
|
124
|
+
def protect_block &block
|
125
|
+
begin
|
126
|
+
debug "About to yield to block."
|
127
|
+
yield
|
128
|
+
debug "After yielding to block."
|
129
|
+
ensure
|
130
|
+
unprotect
|
131
|
+
end
|
137
132
|
end
|
138
133
|
end
|
139
134
|
end
|
data/test/test_cromwell.rb
CHANGED
@@ -94,6 +94,24 @@ class TestCromwell < Test::Unit::TestCase
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
+
context "custom traps" do
|
98
|
+
should "be used if provided" do
|
99
|
+
im_in_ur_blok_touchin_ur_vars = false
|
100
|
+
Cromwell.custom_traps["HUP"] = proc {
|
101
|
+
im_in_ur_blok_touchin_ur_vars = true
|
102
|
+
}
|
103
|
+
Cromwell.protect {
|
104
|
+
Process.kill("HUP", $$)
|
105
|
+
}
|
106
|
+
assert im_in_ur_blok_touchin_ur_vars
|
107
|
+
end
|
108
|
+
|
109
|
+
teardown do
|
110
|
+
Cromwell.custom_traps = {}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
|
97
115
|
context "method protect" do
|
98
116
|
should "set up trap with given signals" do
|
99
117
|
Cromwell.expects(:set_up_traps).with(["HUP", "TERM"])
|
@@ -227,13 +245,8 @@ class TestCromwell < Test::Unit::TestCase
|
|
227
245
|
Cromwell.logger = nil
|
228
246
|
end
|
229
247
|
|
230
|
-
should "log about being called" do
|
231
|
-
@log.expects(:debug).with("Unprotect called")
|
232
|
-
Cromwell.unprotect
|
233
|
-
end
|
234
|
-
|
235
248
|
should "log about should_exit value" do
|
236
|
-
@log.expects(:debug).with("should_exit? = false")
|
249
|
+
@log.expects(:debug).with("Unprotect called, should_exit? = false")
|
237
250
|
Cromwell.unprotect
|
238
251
|
end
|
239
252
|
end # with a logger
|
@@ -247,4 +260,5 @@ class TestCromwell < Test::Unit::TestCase
|
|
247
260
|
end
|
248
261
|
end
|
249
262
|
|
263
|
+
|
250
264
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cromwell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Przemyslaw Kowalczyk
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-31 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- examples/example4.rb
|
56
56
|
- examples/example5.rb
|
57
57
|
- examples/example6.rb
|
58
|
+
- examples/example7.rb
|
58
59
|
- lib/cromwell.rb
|
59
60
|
- test/helper.rb
|
60
61
|
- test/test_cromwell.rb
|
@@ -95,3 +96,4 @@ test_files:
|
|
95
96
|
- examples/example4.rb
|
96
97
|
- examples/example5.rb
|
97
98
|
- examples/example6.rb
|
99
|
+
- examples/example7.rb
|