cognizant 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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