cognizant 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.travis.yml +17 -0
  4. data/Gemfile +4 -1
  5. data/{LICENSE → License.md} +4 -2
  6. data/Rakefile +5 -0
  7. data/Readme.md +95 -0
  8. data/bin/cognizant +76 -122
  9. data/bin/cognizantd +28 -61
  10. data/cognizant.gemspec +8 -4
  11. data/examples/apps/redis-server.cz +42 -0
  12. data/examples/apps/redis-server.yml +29 -0
  13. data/examples/apps/redis-server_dsl.cz +54 -0
  14. data/examples/apps/resque.cz +17 -0
  15. data/examples/apps/thin.cz +32 -0
  16. data/examples/apps/thin.yml +48 -0
  17. data/examples/cognizantd.yml +18 -47
  18. data/features/child_process.feature +62 -0
  19. data/features/commands.feature +65 -0
  20. data/features/cpu_usage.feature +45 -0
  21. data/features/daemon.feature +12 -0
  22. data/features/flapping.feature +39 -0
  23. data/features/memory_usage.feature +45 -0
  24. data/features/shell.feature +30 -0
  25. data/features/step_definitions/common_steps.rb +14 -0
  26. data/features/step_definitions/daemon_steps.rb +25 -0
  27. data/features/step_definitions/shell_steps.rb +96 -0
  28. data/features/support/env.rb +54 -0
  29. data/lib/cognizant.rb +1 -5
  30. data/lib/cognizant/application.rb +122 -0
  31. data/lib/cognizant/application/dsl_proxy.rb +23 -0
  32. data/lib/cognizant/client.rb +61 -0
  33. data/lib/cognizant/commands.rb +164 -0
  34. data/lib/cognizant/commands/actions.rb +30 -0
  35. data/lib/cognizant/commands/help.rb +10 -0
  36. data/lib/cognizant/commands/load.rb +10 -0
  37. data/lib/cognizant/commands/shutdown.rb +7 -0
  38. data/lib/cognizant/commands/status.rb +11 -0
  39. data/lib/cognizant/commands/use.rb +15 -0
  40. data/lib/cognizant/controller.rb +17 -0
  41. data/lib/cognizant/daemon.rb +279 -0
  42. data/lib/cognizant/interface.rb +17 -0
  43. data/lib/cognizant/log.rb +25 -0
  44. data/lib/cognizant/process.rb +138 -94
  45. data/lib/cognizant/process/actions.rb +30 -41
  46. data/lib/cognizant/process/actions/restart.rb +73 -17
  47. data/lib/cognizant/process/actions/start.rb +35 -12
  48. data/lib/cognizant/process/actions/stop.rb +38 -17
  49. data/lib/cognizant/process/attributes.rb +41 -10
  50. data/lib/cognizant/process/children.rb +36 -0
  51. data/lib/cognizant/process/{condition_check.rb → condition_delegate.rb} +11 -13
  52. data/lib/cognizant/process/conditions.rb +7 -4
  53. data/lib/cognizant/process/conditions/cpu_usage.rb +5 -6
  54. data/lib/cognizant/process/conditions/memory_usage.rb +2 -6
  55. data/lib/cognizant/process/dsl_proxy.rb +23 -0
  56. data/lib/cognizant/process/execution.rb +16 -9
  57. data/lib/cognizant/process/pid.rb +16 -6
  58. data/lib/cognizant/process/status.rb +14 -2
  59. data/lib/cognizant/process/trigger_delegate.rb +57 -0
  60. data/lib/cognizant/process/triggers.rb +19 -0
  61. data/lib/cognizant/process/triggers/flapping.rb +68 -0
  62. data/lib/cognizant/process/triggers/transition.rb +22 -0
  63. data/lib/cognizant/process/triggers/trigger.rb +15 -0
  64. data/lib/cognizant/shell.rb +142 -0
  65. data/lib/cognizant/system.rb +16 -0
  66. data/lib/cognizant/system/ps.rb +1 -1
  67. data/lib/cognizant/system/signal.rb +2 -2
  68. data/lib/cognizant/util/dsl_proxy_methods_handler.rb +25 -0
  69. data/lib/cognizant/util/fixnum_percent.rb +5 -0
  70. data/lib/cognizant/util/transform_hash_keys.rb +33 -0
  71. data/lib/cognizant/validations.rb +142 -142
  72. data/lib/cognizant/version.rb +1 -1
  73. metadata +131 -71
  74. data/README.md +0 -221
  75. data/examples/redis-server.rb +0 -28
  76. data/examples/resque.rb +0 -10
  77. data/images/logo-small.png +0 -0
  78. data/images/logo.png +0 -0
  79. data/images/logo.pxm +0 -0
  80. data/lib/cognizant/logging.rb +0 -33
  81. data/lib/cognizant/process/conditions/flapping.rb +0 -57
  82. data/lib/cognizant/process/conditions/trigger_condition.rb +0 -52
  83. data/lib/cognizant/server.rb +0 -14
  84. data/lib/cognizant/server/commands.rb +0 -80
  85. data/lib/cognizant/server/daemon.rb +0 -277
  86. data/lib/cognizant/server/interface.rb +0 -86
  87. data/lib/cognizant/util/symbolize_hash_keys.rb +0 -19
@@ -0,0 +1,17 @@
1
+ require "eventmachine"
2
+
3
+ require "cognizant/commands"
4
+
5
+ module Cognizant
6
+ class Interface < EventMachine::Connection
7
+ def post_init
8
+ end
9
+
10
+ def receive_data(args)
11
+ Cognizant::Commands.process_command(self, args)
12
+ end
13
+
14
+ def unbind
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require "logging"
2
+
3
+ module Cognizant
4
+ module Log
5
+ def self.logger
6
+ Logging.logger
7
+ end
8
+
9
+ def self.[](name)
10
+ self.logger[name]
11
+ end
12
+
13
+ def self.stdout
14
+ Logging.appenders.stdout
15
+ end
16
+
17
+ def self.syslog(*args)
18
+ Logging.appenders.syslog(*args)
19
+ end
20
+
21
+ def self.file(*args)
22
+ Logging.appenders.file(*args)
23
+ end
24
+ end
25
+ end
@@ -3,13 +3,18 @@ require "thread"
3
3
 
4
4
  require "state_machine"
5
5
 
6
+ require "cognizant/process/dsl_proxy"
6
7
  require "cognizant/process/pid"
7
8
  require "cognizant/process/status"
8
9
  require "cognizant/process/execution"
9
10
  require "cognizant/process/attributes"
10
11
  require "cognizant/process/actions"
11
- require "cognizant/process/condition_check"
12
- require "cognizant/util/symbolize_hash_keys"
12
+ require "cognizant/process/conditions"
13
+ require "cognizant/process/condition_delegate"
14
+ require "cognizant/process/triggers"
15
+ require "cognizant/process/trigger_delegate"
16
+ require "cognizant/process/children"
17
+ require "cognizant/util/transform_hash_keys"
13
18
 
14
19
  module Cognizant
15
20
  class Process
@@ -18,6 +23,7 @@ module Cognizant
18
23
  include Cognizant::Process::Execution
19
24
  include Cognizant::Process::Attributes
20
25
  include Cognizant::Process::Actions
26
+ include Cognizant::Process::Children
21
27
 
22
28
  state_machine :initial => :unmonitored do
23
29
  # These are the idle states, i.e. only an event (either external or internal) will trigger a transition.
@@ -65,40 +71,69 @@ module Cognizant
65
71
  transition any => :unmonitored
66
72
  end
67
73
 
68
- after_transition any => :starting, :do => :start_process
69
- before_transition :running => :stopping, :do => lambda { |p| p.autostart = false }
70
- after_transition any => :stopping, :do => :stop_process
71
- before_transition any => :restarting, :do => lambda { |p| p.autostart = true }
72
- after_transition any => :restarting, :do => :restart_process
74
+ before_transition any => :starting, :do => lambda { |p| p.autostart = true }
75
+ after_transition any => :starting, :do => :start_process
76
+
77
+ before_transition any => :stopping, :do => lambda { |p| p.autostart = false }
78
+ after_transition :running => :stopping, :do => :stop_process
79
+
80
+ before_transition any => :restarting, :do => lambda { |p| p.autostart = true }
81
+ after_transition any => :restarting, :do => :restart_process
82
+
83
+ before_transition any => :unmonitored, :do => lambda { |p| p.autostart = false }
73
84
 
74
85
  before_transition any => any, :do => :notify_triggers
75
86
  after_transition any => any, :do => :record_transition
76
87
  end
77
88
 
78
- def initialize(process_name = nil, options = {})
89
+ def initialize(process_name = nil, attributes = {}, &block)
90
+ reset!
91
+
92
+ @name = process_name.to_s if process_name
93
+
94
+ set_attributes(attributes)
95
+
96
+ handle_initialize_block(&block) if block
97
+
98
+ raise "Process name is missing. Aborting." unless self.name
99
+ Log[self].info "Loading process #{self.name}..."
100
+
101
+ # Let state_machine initialize as well.
102
+ initialize_state_machines
103
+ end
104
+
105
+ def handle_initialize_block(&block)
106
+ if block.arity == 0
107
+ attributes = Cognizant::Process::DSLProxy.new(self, &block).attributes
108
+ set_attributes(attributes)
109
+ else
110
+ instance_exec(self, &block)
111
+ end
112
+ end
113
+
114
+ def reset!
115
+ reset_attributes!
116
+
117
+ @application = nil
79
118
  @ticks_to_skip = 0
80
- @checks = []
119
+ @conditions = []
81
120
  @triggers = []
121
+ @children = []
82
122
  @action_mutex = Monitor.new
83
-
84
- self.name = process_name.to_s if process_name
85
- self.autostart = true # Default.
86
-
87
- if options.has_key?(:checks) and options[:checks].kind_of?(Hash)
88
- options[:checks].each do |condition_name, args|
89
- self.check(condition_name, args)
90
- end
91
- end
92
- options.delete(:checks)
123
+ @monitor_children = false
124
+ end
93
125
 
94
- options.each do |attribute_name, value|
95
- self.send("#{attribute_name}=", value) if self.respond_to?("#{attribute_name}=")
126
+ def check(check_name, options, &block)
127
+ if klass = Cognizant::Process::Conditions[check_name]
128
+ @conditions << ConditionDelegate.new(check_name, options.deep_symbolize_keys!, &block)
129
+ elsif klass = Cognizant::Process::Triggers[check_name]
130
+ @triggers << TriggerDelegate.new(check_name, self, options.deep_symbolize_keys!, &block)
96
131
  end
132
+ end
97
133
 
98
- yield(self) if block_given?
99
-
100
- # Let state_machine initialize as well.
101
- super
134
+ def monitor_children(child_process_attributes = {}, &child_process_block)
135
+ @monitor_children = true
136
+ @child_process_attributes, @child_process_block = child_process_attributes, child_process_block
102
137
  end
103
138
 
104
139
  def tick
@@ -108,18 +143,28 @@ module Cognizant
108
143
  # Invoke the state_machine event.
109
144
  super
110
145
 
111
- self.run_checks if self.running?
146
+ if self.running? # State method.
147
+ run_conditions
148
+
149
+ if @monitor_children
150
+ refresh_children!
151
+ @children.each(&:tick)
152
+ end
153
+ end
112
154
  end
113
155
 
114
- def record_transition(transition)
115
- unless transition.loopback?
116
- @transitioned = true
117
- @last_transition_time = Time.now.to_i
156
+ def skip_ticks_for(skips)
157
+ # Accept negative skips with the result being >= 0.
158
+ # +1 so that we don't have to >= and ensure 0 in #skip_tick?.
159
+ @ticks_to_skip = [@ticks_to_skip + (skips.to_i + 1), 0].max
160
+ end
118
161
 
119
- # When a process changes state, we should clear the memory of all the checks.
120
- @checks.each { |check| check.clear_history! }
121
- puts "#{name} changing from #{transition.from_name} => #{transition.to_name}"
122
- end
162
+ def pidfile
163
+ @pidfile || File.join(@application.pids_dir, @name + '.pid')
164
+ end
165
+
166
+ def logfile
167
+ @logfile || File.join(@application.logs_dir, @name + '.log')
123
168
  end
124
169
 
125
170
  def last_transition_time
@@ -127,11 +172,9 @@ module Cognizant
127
172
  end
128
173
 
129
174
  def handle_user_command(command)
130
- if command == :unmonitor
131
- # When the user issues an unmonitor command, reset any
132
- # triggers so that scheduled events gets cleared.
133
- @triggers.each { |trigger| trigger.reset! }
134
- end
175
+ # When the user issues a command, reset any
176
+ # triggers so that scheduled events gets cleared.
177
+ @triggers.each { |trigger| trigger.reset! }
135
178
  dispatch!(command, "user initiated")
136
179
  end
137
180
 
@@ -145,72 +188,42 @@ module Cognizant
145
188
  end
146
189
  end
147
190
 
148
- def check(condition_name, options, &block)
149
- klass = Cognizant::Process::Conditions[condition_name]
150
- case klass.superclass.name.split("::").last
151
- when "TriggerCondition"
152
- @triggers << klass.new(self, options.deep_symbolize_keys!)
153
- when "PollCondition"
154
- @checks << ConditionCheck.new(condition_name, options.deep_symbolize_keys!, &block)
155
- end
156
- end
157
-
158
- def notify_triggers(transition)
159
- @triggers.each { |trigger| trigger.notify(transition) }
160
- end
161
-
162
- def run_checks
163
- now = Time.now.to_i
191
+ private
164
192
 
165
- threads = @checks.collect do |check|
166
- [check, Thread.new { Thread.current[:actions] = check.run(read_pid, now) }]
167
- end
193
+ def record_transition(transition)
194
+ unless transition.loopback?
195
+ @transitioned = true
196
+ @last_transition_time = Time.now.to_i
168
197
 
169
- @transitioned = false
198
+ # When a process changes state, we should clear the memory of all the conditions.
199
+ @conditions.each { |condition| condition.clear_history! }
200
+ Log[self].debug "Changing state of #{name} from #{transition.from_name} => #{transition.to_name}"
170
201
 
171
- threads.inject([]) do |actions, (check, thread)|
172
- thread.join
173
- if thread[:actions].size > 0
174
- puts "#{check.condition_name} dispatched: #{thread[:actions].join(',')}"
175
- thread[:actions].each do |action|
176
- actions << [action, check.to_s]
177
- end
202
+ # And we should re-populate its child list.
203
+ if @monitor_children
204
+ @children.clear
178
205
  end
179
- actions
180
- end.each do |(action, reason)|
181
- break if @transitioned
182
- self.dispatch!(action, reason)
206
+
207
+ # Update the pid from pidfile, since the state of process changed, if the process is managing it's own pidfile.
208
+ read_pid if @pidfile
183
209
  end
184
210
  end
185
211
 
186
- def process_running?
187
- @process_running = begin
188
- # Do not assume change when we're giving time to an execution by skipping ticks.
189
- if @ticks_to_skip > 0
190
- @process_running
191
- elsif self.ping_command and run(self.ping_command).succeeded?
192
- true
193
- elsif pid_running?
194
- true
195
- else
196
- false
212
+ def set_attributes(attributes)
213
+ if attributes.has_key?(:checks) and attributes[:checks].kind_of?(Hash)
214
+ attributes[:checks].each do |check_name, args, &block|
215
+ check(check_name, args, &block)
197
216
  end
198
217
  end
199
- end
200
-
201
- def pidfile
202
- @pidfile = @pidfile || File.join(Cognizant::Server.daemon.pids_dir, self.name + '.pid')
203
- end
218
+ attributes.delete(:checks)
204
219
 
205
- def logfile
206
- @logfile = @logfile || File.join(Cognizant::Server.daemon.logs_dir, self.name + '.log')
207
- end
208
-
209
- private
220
+ if attributes.has_key?(:monitor_children) and attributes[:monitor_children].kind_of?(Hash)
221
+ monitor_children(attributes[:monitor_children])
222
+ end
210
223
 
211
- def skip_ticks_for(skips)
212
- # Accept negative skips with the result being >= 0.
213
- @ticks_to_skip = [@ticks_to_skip + (skips.to_i + 1), 0].max # +1 so that we don't have to >= and ensure 0 in "skip_tick?".
224
+ attributes.each do |attribute_name, value|
225
+ self.send("#{attribute_name}=", value) if self.respond_to?("#{attribute_name}=")
226
+ end
214
227
  end
215
228
 
216
229
  def skip_tick?
@@ -225,5 +238,36 @@ module Cognizant
225
238
  end
226
239
  execute(command, options.merge(action_overrides))
227
240
  end
241
+
242
+ def run_conditions
243
+ now = Time.now.to_i
244
+
245
+ threads = @conditions.collect do |condition|
246
+ [condition, Thread.new { Thread.current[:actions] = condition.run(cached_pid, now) }]
247
+ end
248
+
249
+ @transitioned = false
250
+
251
+ collect_conditions_actions(threads).each do |(action, reason)|
252
+ break if @transitioned
253
+ dispatch!(action, reason)
254
+ end
255
+ end
256
+
257
+ def collect_conditions_actions(threads)
258
+ threads.inject([]) do |actions, (condition, thread)|
259
+ thread.join
260
+ thread[:actions].each do |action|
261
+ action_name = action.respond_to?(:call) ? "call to custom block" : action
262
+ Log[self].debug "Dispatching #{action_name} to #{name} for #{condition.to_s.strip}."
263
+ actions << [action, condition.to_s]
264
+ end
265
+ actions
266
+ end
267
+ end
268
+
269
+ def notify_triggers(transition)
270
+ @triggers.each { |trigger| trigger.notify(transition) }
271
+ end
228
272
  end
229
273
  end
@@ -12,63 +12,52 @@ module Cognizant
12
12
 
13
13
  private
14
14
 
15
- def execute_action(result_handler, options)
16
- before_command = options[:before]
17
- command = options[:command]
18
- after_command = options[:after]
19
- signals = options[:signals]
20
- timeout = options[:timeout] || 10
21
-
15
+ def handle_action(result_handler, options)
22
16
  # TODO: Works well but can some refactoring make it more declarative?
23
17
  @action_thread = Thread.new do
24
18
  result = false
25
- queue = Queue.new
26
- thread = Thread.new do
27
- if before_command and not success = run(before_command).succeeded?
28
- queue.push(success)
29
- Thread.exit
30
- end
19
+ queue, thread = execute_action(options)
31
20
 
32
- if (command and success = run(command, options) and success.succeeded?)
33
- run(after_command) if after_command
34
- queue.push(success)
35
- Thread.exit
36
- end
37
-
38
- # If the caller has attempted to set signals, then it can handle it's result.
39
- if success = send_signals(signals: signals, timeout: timeout)
40
- run(after_command) if after_command
41
- queue.push(success)
42
- Thread.exit
43
- end
44
-
45
- queue.push(false)
46
- Thread.exit
47
- end
48
-
49
- timeout_left = timeout + 1
50
- while (timeout_left -= 1) > 0 do
21
+ time_left = options[:timeout]
22
+ while time_left >= 0 do
51
23
  # If there is something in the queue, we have the required result.
52
- if result = queue.pop
53
- # Rollback the pending skips, since we finished before timeout.
54
- skip_ticks_for(-timeout_left)
24
+ unless queue.empty?
25
+ result = queue.pop
55
26
  break
56
27
  end
57
28
  sleep 1
29
+ time_left -= 1
58
30
  end
59
31
 
60
32
  # Kill the nested thread.
61
33
  thread.kill
62
34
 
63
35
  # Action callback.
64
- result_handler.call(result) if result_handler.respond_to?(:call)
65
-
66
- # Kill self.
67
- Thread.kill
36
+ self.send(result_handler, result, time_left) if result_handler.present? and self.respond_to?(result_handler)
68
37
  end
38
+ end
39
+
40
+ def execute_action(options)
41
+ before_command = options[:before]
42
+ command = options[:command]
43
+ after_command = options[:after]
44
+ signals = options[:signals]
45
+ timeout = options[:timeout]
69
46
 
70
- # We skip so that we're not reinformed about the required transition by the tick.
71
- skip_ticks_for(timeout)
47
+ queue = Queue.new
48
+ thread = Thread.new do
49
+ # If before_command succeeds, we move to the next command.
50
+ (before_command and not success = run(before_command).succeeded?) or
51
+ # If the command is available and it succeeds, we stop here.
52
+ (command and success = run(command, options) and success.succeeded?) or
53
+ # As a last try, check for signals. If the action has set signals, then it can handle its result.
54
+ (success = send_signals(signals: signals, timeout: timeout))
55
+
56
+ run(after_command) if success and after_command
57
+ queue.push(success)
58
+ Thread.exit
59
+ end
60
+ return queue, thread
72
61
  end
73
62
 
74
63
  def send_signals(options = {})
@@ -14,45 +14,101 @@ module Cognizant
14
14
  # The command to restart the process with. This command can optionally
15
15
  # be similar in behavior to the stop command, since the process will
16
16
  # anyways be automatically started again, if autostart is set to true.
17
+ # Also see restart_expect variable.
17
18
  # @return [String] Defaults to nil
18
19
  attr_accessor :restart_command
19
20
 
20
21
  # The signals to pass to the process one by one attempting to restart
21
22
  # it. Each signal is passed within the timeout period over equally
22
- # distributed intervals (min. 2 seconds). Override with signals without
23
+ # divided intervals (min. 2 seconds). Override with signals without
23
24
  # "KILL" to never force kill the process.
25
+ # Also see restart_expect variable.
24
26
  # e.g. ["TERM", "INT"]
25
- # @return [Array] Defaults to ["TERM", "INT", "KILL"]
27
+ # @return [Array] Defaults to ["QUIT", "TERM", "INT"]
26
28
  attr_accessor :restart_signals
27
29
 
30
+ # Whether or not the process is expected to stop itself after the
31
+ # restart action is executed. If, upon restart action, the process will
32
+ # stop but not start again by itself, it should be set to true. If the
33
+ # process will start again within the timeout period, it should be set
34
+ # to false. For convenience, it defaults to false, if restart_command
35
+ # or restart_signals are set, as the restart action is then expected to
36
+ # start itself after a stop.
37
+ # @return [true, false] Defaults to !restart_command.present? ||
38
+ # !restart_signals.present?
39
+ attr_accessor :restart_expect_stopped
40
+
28
41
  # The grace time period in seconds for the process to stop within
29
42
  # (for restart). Covers the time period for the restart command or
30
43
  # signals. After the timeout is over, the process is checked for
31
44
  # running status and if not stopped, it re-enters the auto start
32
- # lifecycle based on conditions.
33
- # @return [String] Defaults to 10
45
+ # lifecycle based on conditions. Timeout of request has the same effect
46
+ # as :stopped setting :restart_expect.
47
+ # @return [String] Defaults to 30
34
48
  attr_accessor :restart_timeout
35
49
 
36
50
  # The command to run after the process is restarted.
37
51
  # @return [String] Defaults to nil
38
52
  attr_accessor :restart_after_command
39
53
 
54
+ def restart_expect_stopped!
55
+ self.restart_expect_stopped = true
56
+ end
57
+
58
+ def restart_expect_stopped
59
+ unless !!@restart_expect_stopped == @restart_expect_stopped
60
+ @restart_expect_stopped = !(self.restart_command.present? or self.restart_signals.present?)
61
+ end
62
+ @restart_expect_stopped
63
+ end
64
+
65
+ def reset_attributes!
66
+ self.restart_env = {}
67
+ self.restart_before_command = nil
68
+ self.restart_command = nil
69
+ self.restart_signals = nil
70
+ self.restart_expect_stopped = nil
71
+ self.restart_timeout = 30
72
+ self.restart_after_command = nil
73
+ super
74
+ end
75
+
40
76
  def restart_process
41
- result_handler = Proc.new do |result|
42
- # If it is a boolean and value is true OR if it's an execution result and it succeeded.
43
- if (!!result == result and result) or (result.respond_to?(:succeeded?) and result.succeeded?)
44
- unlink_pid unless pid_running?
77
+ # We skip so that we're not reinformed about the required transition by the tick.
78
+ skip_ticks_for(self.restart_timeout)
79
+
80
+ options = {
81
+ env: self.env.merge(self.restart_env),
82
+ before: self.restart_before_command,
83
+ command: self.restart_command,
84
+ signals: self.restart_signals || ["QUIT", "TERM", "INT"],
85
+ after: self.restart_after_command,
86
+ timeout: self.restart_timeout
87
+ }
88
+ handle_action('_restart_result_handler', options)
89
+ end
90
+
91
+ # @private
92
+ def _restart_result_handler(result, time_left = 0)
93
+ # If it is a boolean and value is true OR if it's an execution result and it succeeded.
94
+ if (!!result == result and result) or (result.respond_to?(:succeeded?) and result.succeeded?)
95
+ unlink_pid if not pid_running? and self.daemonize
96
+
97
+ # We are not resetting @process_pid here to give process a second of grace period.
98
+
99
+ unless self.restart_expect_stopped
100
+ while (time_left >= 0 and not process_running?) do
101
+ sleep 1
102
+ time_left -= 1
103
+ @process_pid = nil
104
+ end
45
105
  end
106
+ else
107
+ @process_pid = nil
46
108
  end
47
- execute_action(
48
- result_handler,
49
- env: (self.env || {}).merge(self.restart_env || {}),
50
- before: self.restart_before_command,
51
- command: self.restart_command,
52
- signals: self.restart_signals,
53
- after: self.restart_after_command,
54
- timeout: self.restart_timeout
55
- )
109
+
110
+ # Rollback the pending skips.
111
+ skip_ticks_for(-time_left) if time_left > 0
56
112
  end
57
113
  end
58
114
  end