cromwell 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: <code>INT</code> (keyboard interrupt <code>^C</code>), <code>TERM</code> (sent by <code>kill</code> by defautl), <code>HUP</code> (sent when shell terminates), and <code>QUIT</code> (a "dump core" signal, sent with <code>^\\</code>), but you can specify any list of them (except for <code>KILL</code> and <code>STOP</code>, of course).
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 <code>irb</code>. For more info on signals and their meaning, check your local <code>man signal</code>.
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 <code>protect</code> method. It can be called in two ways: with a block and without a block.
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 <code>kill</code> while it's sleeping for ten seconds:
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 <code>protect</code> without the block. In that case your code will be protected until you call <code>unprotect</code> method:
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 <code>unprotect</code> in an <code>ensure</code> block. Or, if you want your script to just run until it finishes on its own, don't call <code>unprotect</code> at all.
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 <code>protect</code> method:
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 <code>true</code> when your code is protected, <code>false</code> otherwise.
120
- * <code>Cromwell.should_exit?</code> returns <code>true</code> when a signal was caught and termination will ocur after the protected code is over.
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 <code>STDOUT</code>:
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
- * Allow to customize behavior after catching a signal. Right now, the script is terminated after the protected block is done (even if the signal would not normally cause script termination).
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 <code>protect</code>, <code>unprotect</code>, and <code>protected?</code> have <b>nothing</b> to do with Ruby's <code>protected</code> keyword and the general concept of a <i>protected</i> method in Ruby and other object-oriented languages.
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.3.0
1
+ 0.4.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{cromwell}
8
- s.version = "0.3.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-14}
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?"
@@ -1,12 +1,44 @@
1
1
  class Cromwell
2
2
  DEFAULT_SIGNAL_LIST = %w[INT TERM HUP QUIT].freeze
3
3
 
4
- @@logger = nil
5
- @@should_exit = false
6
- @@protected = false
7
- @@old_traps = {}
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
- @@should_exit = false
25
- @@protected = true
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
- @@protected = false
46
- debug "should_exit? = #{@@should_exit}"
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 signal do
107
- if @@protected
108
- info "Caught signal #{signal} -- ignoring."
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
- info "Caught signal #{signal} -- exiting."
113
- exit
92
+ handle signal
114
93
  end
115
- end
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
- @@old_traps[signal] = old_trap
105
+ @old_traps[signal] = old_trap
121
106
  end
122
107
 
123
108
  def restore_old_traps
124
- @@old_traps.each do |signal, old_trap|
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
- @@old_traps = {}
113
+ @old_traps = {}
129
114
  end
130
115
 
131
116
  def debug msg
132
- @@logger.debug msg if @@logger
117
+ @logger.debug msg if @logger
133
118
  end
134
119
 
135
120
  def info msg
136
- @@logger.info msg if @@logger
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
@@ -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.3.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-14 00:00:00 +01:00
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