eye 0.8.1 → 0.9.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +21 -2
  4. data/.travis.yml +2 -1
  5. data/Gemfile +3 -0
  6. data/LICENSE +1 -1
  7. data/README.md +1 -1
  8. data/bin/loader_eye +1 -1
  9. data/examples/delayed_job.eye +2 -2
  10. data/examples/processes/sample.rb +1 -1
  11. data/examples/puma.eye +1 -1
  12. data/examples/rbenv.eye +1 -1
  13. data/examples/sidekiq.eye +1 -1
  14. data/examples/test.eye +1 -1
  15. data/examples/thin-farm.eye +1 -1
  16. data/examples/unicorn.eye +1 -1
  17. data/eye.gemspec +2 -2
  18. data/lib/eye.rb +2 -3
  19. data/lib/eye/application.rb +3 -6
  20. data/lib/eye/checker.rb +4 -3
  21. data/lib/eye/checker/children_count.rb +3 -3
  22. data/lib/eye/checker/socket.rb +7 -12
  23. data/lib/eye/child_process.rb +5 -7
  24. data/lib/eye/cli.rb +6 -4
  25. data/lib/eye/cli/commands.rb +1 -1
  26. data/lib/eye/cli/render.rb +6 -5
  27. data/lib/eye/client.rb +9 -4
  28. data/lib/eye/controller.rb +2 -2
  29. data/lib/eye/controller/{send_command.rb → apply.rb} +25 -32
  30. data/lib/eye/controller/commands.rb +13 -10
  31. data/lib/eye/controller/load.rb +49 -38
  32. data/lib/eye/controller/status.rb +2 -2
  33. data/lib/eye/dsl.rb +1 -1
  34. data/lib/eye/dsl/application_opts.rb +2 -2
  35. data/lib/eye/dsl/child_process_opts.rb +1 -1
  36. data/lib/eye/dsl/config_opts.rb +1 -1
  37. data/lib/eye/dsl/group_opts.rb +3 -3
  38. data/lib/eye/dsl/main.rb +3 -3
  39. data/lib/eye/dsl/opts.rb +17 -21
  40. data/lib/eye/dsl/process_opts.rb +3 -3
  41. data/lib/eye/dsl/validation.rb +2 -2
  42. data/lib/eye/group.rb +8 -114
  43. data/lib/eye/group/call.rb +73 -0
  44. data/lib/eye/group/chain.rb +19 -17
  45. data/lib/eye/group/data.rb +40 -0
  46. data/lib/eye/loader.rb +1 -1
  47. data/lib/eye/logger.rb +4 -4
  48. data/lib/eye/process.rb +2 -0
  49. data/lib/eye/process/commands.rb +11 -19
  50. data/lib/eye/process/config.rb +2 -1
  51. data/lib/eye/process/controller.rb +5 -12
  52. data/lib/eye/process/data.rb +11 -3
  53. data/lib/eye/process/monitor.rb +5 -5
  54. data/lib/eye/process/notify.rb +1 -1
  55. data/lib/eye/process/scheduler.rb +118 -63
  56. data/lib/eye/process/states.rb +10 -9
  57. data/lib/eye/process/states_history.rb +1 -1
  58. data/lib/eye/process/system.rb +1 -1
  59. data/lib/eye/process/trigger.rb +5 -4
  60. data/lib/eye/process/validate.rb +1 -1
  61. data/lib/eye/server.rb +22 -9
  62. data/lib/eye/trigger.rb +4 -7
  63. data/lib/eye/trigger/check_dependency.rb +1 -1
  64. data/lib/eye/trigger/flapping.rb +8 -10
  65. data/lib/eye/trigger/starting_guard.rb +7 -4
  66. data/lib/eye/trigger/stop_children.rb +1 -1
  67. data/lib/eye/trigger/wait_dependency.rb +2 -2
  68. data/lib/eye/utils.rb +19 -9
  69. metadata +10 -10
  70. data/lib/eye/reason.rb +0 -26
  71. data/lib/eye/utils/celluloid_chain.rb +0 -73
@@ -24,7 +24,7 @@ module Eye::Process::Config
24
24
 
25
25
  auto_update_pidfile_grace: 30.seconds,
26
26
  revert_fuckup_pidfile_grace: 120.seconds
27
- }
27
+ }.freeze
28
28
 
29
29
  def prepare_config(new_config)
30
30
  h = DEFAULTS.merge(new_config)
@@ -46,6 +46,7 @@ module Eye::Process::Config
46
46
  h[:stdall] = Eye::System.normalized_file(h[:stdall], h[:working_dir]) if h[:stdall]
47
47
 
48
48
  h[:environment] = Eye::System.prepare_env(h)
49
+ h[:stop_signals] = [:TERM, 0.5, :KILL] unless h[:stop_command] || h[:stop_signals]
49
50
 
50
51
  h
51
52
  end
@@ -1,8 +1,7 @@
1
1
  module Eye::Process::Controller
2
2
 
3
- def send_command(command, *args)
4
- schedule command, *args, Eye::Reason::User.new(command)
5
- end
3
+ # scheduled actions
4
+ # :update_config, :start, :stop, :restart, :unmonitor, :monitor, :break_chain, :delete, :signal, :user_command
6
5
 
7
6
  def start
8
7
  if load_external_pid_file == :ok
@@ -26,12 +25,10 @@ module Eye::Process::Controller
26
25
  def monitor
27
26
  if self[:auto_start]
28
27
  start
28
+ elsif load_external_pid_file == :ok
29
+ switch :already_running
29
30
  else
30
- if load_external_pid_file == :ok
31
- switch :already_running
32
- else
33
- schedule :unmonitor, Eye::Reason.new(:'not found')
34
- end
31
+ schedule command: :unmonitor, reason: 'not found'
35
32
  end
36
33
  end
37
34
 
@@ -62,8 +59,4 @@ module Eye::Process::Controller
62
59
  end
63
60
  end
64
61
 
65
- def freeze
66
- scheduler_freeze
67
- end
68
-
69
62
  end
@@ -9,7 +9,7 @@ module Eye::Process::Data
9
9
  end
10
10
 
11
11
  def group_name
12
- (self[:group] == '__default__') ? nil : self[:group]
12
+ self[:group] == '__default__' ? nil : self[:group]
13
13
  end
14
14
 
15
15
  def group_name_pure
@@ -46,13 +46,21 @@ module Eye::Process::Data
46
46
 
47
47
  h[:debug] = debug_data if opts[:debug]
48
48
  h[:procline] = Eye::SystemResources.args(self.pid) if opts[:procline]
49
- h[:current_command] = current_scheduled_command if current_scheduled_command
49
+ h[:current_command] = scheduler_current_command if scheduler_current_command
50
50
 
51
51
  h
52
52
  end
53
53
 
54
54
  def debug_data
55
- { queue: scheduler_actions_list, watchers: @watchers.keys }
55
+ { queue: scheduler_actions_list, watchers: @watchers.keys, timers: timers_data }
56
+ end
57
+
58
+ def timers_data
59
+ if actor = Thread.current[:celluloid_actor]
60
+ actor.timers.timers.map(&:interval)
61
+ end
62
+ rescue
63
+ []
56
64
  end
57
65
 
58
66
  def sub_object?(obj)
@@ -39,7 +39,7 @@ private
39
39
  notify :info, 'crashed!'
40
40
  clear_pid_file(true) if control_pid?
41
41
 
42
- switch :crashed, Eye::Reason.new(:crashed)
42
+ switch :crashed, reason: :crashed
43
43
  end
44
44
  end
45
45
 
@@ -83,7 +83,7 @@ private
83
83
  def check_identity
84
84
  if compare_identity == :fail
85
85
  notify :info, 'crashed by identity!'
86
- switch :crashed, Eye::Reason.new(:crashed_by_identity)
86
+ switch :crashed, reason: :crashed_by_identity
87
87
  clear_pid_file if self[:clear_pid]
88
88
  false
89
89
  else
@@ -101,13 +101,13 @@ private
101
101
  warn 'check crashed: process is down'
102
102
 
103
103
  if self[:restore_in]
104
- schedule_in self[:restore_in].to_f, :restore, Eye::Reason.new(:crashed)
104
+ schedule in: self[:restore_in].to_f, command: :restore, reason: :crashed
105
105
  else
106
- schedule :restore, Eye::Reason.new(:crashed)
106
+ schedule command: :restore, reason: :crashed
107
107
  end
108
108
  else
109
109
  warn 'check crashed: process without keep_alive'
110
- schedule :unmonitor, Eye::Reason.new(:crashed)
110
+ schedule command: :unmonitor, reason: :crashed
111
111
  end
112
112
  end
113
113
 
@@ -5,7 +5,7 @@ module Eye::Process::Notify
5
5
  # 2) checker bounded to restart process [:warn]
6
6
  # 3) flapping + switch to unmonitored [:error]
7
7
 
8
- LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }
8
+ LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze
9
9
 
10
10
  def notify(level, msg)
11
11
  # logging it
@@ -1,11 +1,54 @@
1
1
  module Eye::Process::Scheduler
2
2
 
3
- # ex: schedule :update_config, config, "reason: update_config"
4
- def schedule(command, *args, &block)
5
- return unless scheduler.alive?
3
+ # Call params:
4
+ # :command
5
+ # :args
6
+ # :by
7
+ # :reason
8
+ # :block
9
+ # :signal
10
+ # :at
11
+ # :in
12
+
13
+ # :update_config, :start, :stop, :restart, :unmonitor, :monitor, :break_chain, :delete, :signal, :user_command
14
+ def send_call(call)
15
+ user_schedule(call)
16
+ end
17
+
18
+ def user_schedule(call)
19
+ call[:by] ||= :user
20
+ internal_schedule(call)
21
+ end
22
+
23
+ # 2 Forms of schedule:
24
+ # schedule command: 'bla', args: [1, 2, 3]
25
+ # schedule :bla, 1, 2, 3
26
+ def schedule(*args)
27
+ if args[0].is_a?(Hash)
28
+ internal_schedule(args[0])
29
+ else
30
+ internal_schedule(command: args[0], args: args[1..-1])
31
+ end
32
+ end
33
+
34
+ def internal_schedule(call)
35
+ if interval = call[:in]
36
+ debug { "schedule_in #{interval} :#{call[:command]}" }
37
+ after(interval.to_f) do
38
+ debug { "scheduled_in #{interval} :#{call[:command]}" }
39
+ call[:in] = nil
40
+ internal_schedule(call)
41
+ end
42
+ return
43
+ end
44
+
45
+ call[:at] ||= Time.now.to_i
46
+ command = call[:command]
47
+
48
+ @scheduler_freeze = false if call[:freeze] == false
6
49
 
7
- if scheduler_freeze?
8
- warn ":#{command} ignoring to schedule, because scheduler is freeze"
50
+ if @scheduler_freeze
51
+ warn ":#{command} ignoring to schedule, because scheduler is freezed"
9
52
  return
10
53
  end
11
54
 
@@ -14,95 +57,107 @@ module Eye::Process::Scheduler
14
57
  return
15
58
  end
16
59
 
17
- reason = args.pop if args.present? && args[-1].is_a?(Eye::Reason)
18
-
19
- info "schedule :#{command} #{reason ? "(reason: #{reason})" : nil}"
20
-
21
- if reason.class == Eye::Reason
22
- # for auto reasons
23
- # skip already running commands and all in chain
24
- scheduler.add_wo_dups_current(:scheduled_action, command, args: args, reason: reason, block: block)
60
+ if filter_call(call)
61
+ info "schedule :#{command} (#{reason_from_call(call)})"
62
+ scheduler_add(call)
25
63
  else
26
- # for manual, or without reason
27
- # skip only for last in chain
28
- scheduler.add_wo_dups(:scheduled_action, command, args: args, reason: reason, block: block)
64
+ info "not scheduled: #{command} (#{reason_from_call(call)})"
29
65
  end
30
- end
31
66
 
32
- def schedule_in(interval, command, *args, &block)
33
- debug { "schedule_in #{interval} :#{command} #{args}" }
34
- after(interval.to_f) do
35
- debug { "scheduled_in #{interval} :#{command} #{args}" }
36
- schedule(command, *args, &block)
37
- end
67
+ @scheduler_freeze = true if call[:freeze] == true
38
68
  end
39
69
 
40
- def scheduled_action(command, h = {})
41
- reason = h[:reason]
42
- info "=> #{command} #{h[:args].present? ? "#{h[:args] * ','}" : nil} #{reason ? "(reason: #{reason})" : nil}"
70
+ def scheduler_consume(call)
71
+ args = call[:args]
72
+ reason_str = reason_from_call(call)
73
+ info "=> #{call[:command]} #{args.present? ? args.join(',') : nil} (#{reason_str})"
74
+ send(call[:command], *args, &call[:block])
43
75
 
44
- @current_scheduled_command = command
45
- @last_scheduled_command = command
46
- @last_scheduled_reason = reason
47
- @last_scheduled_at = Time.now
76
+ history_cmd = is_a?(Eye::ChildProcess) ? "#{call[:command]}_child" : call[:command]
77
+ scheduler_history.push(history_cmd, reason_str, call[:at])
48
78
 
49
- send(command, *h[:args], &h[:block])
50
- @current_scheduled_command = nil
51
- info "<= #{command}"
79
+ rescue Object => ex
80
+ raise(ex) if ex.class == Celluloid::TaskTerminated
81
+ log_ex(ex)
52
82
 
53
- schedule_history.push(command, reason, @last_scheduled_at.to_i)
83
+ ensure
84
+ # signaling to waiter
85
+ call[:signal].try :signal
54
86
 
55
- if parent = self.try(:parent)
56
- parent.schedule_history.push("#{command}_child", reason, @last_scheduled_at.to_i)
57
- end
87
+ info "<= #{call[:command]}"
58
88
  end
59
89
 
60
- def execute_proc(*_args, &block)
61
- self.instance_exec(&block)
62
- rescue Object => ex
63
- log_ex(ex)
90
+ def filter_call(call)
91
+ # for auto reasons, compare call with current @scheduled_call
92
+ return false if call[:by] != :user && equal_action_call?(@scheduled_call, call)
93
+
94
+ # check any equal call in queue scheduler_calls
95
+ !scheduler_calls.any? { |c| equal_action_call?(c, call) }
64
96
  end
65
97
 
66
- def scheduler_actions_list
67
- scheduler.list.map { |c| c[:args].first rescue nil }.compact
98
+ def equal_action_call?(call1, call2)
99
+ call1 && call2 && (call1[:command] == call2[:command]) && (call1[:args] == call2[:args]) && (call1[:block] == call2[:block])
68
100
  end
69
101
 
70
- def scheduler_clear_pending_list
71
- scheduler.clear_pending_list
102
+ def scheduler_calls
103
+ @scheduler_calls ||= []
72
104
  end
73
105
 
74
- def self.included(base)
75
- base.finalizer :remove_scheduler
76
- base.execute_block_on_receiver :schedule
106
+ def scheduler_history
107
+ @scheduler_history ||= Eye::Process::StatesHistory.new(50)
77
108
  end
78
109
 
79
- attr_accessor :current_scheduled_command
80
- attr_accessor :last_scheduled_command, :last_scheduled_reason, :last_scheduled_at
110
+ def scheduler_add(call)
111
+ scheduler_calls << call
112
+ ensure_scheduler_process
113
+ end
81
114
 
82
- def schedule_history
83
- @schedule_history ||= Eye::Process::StatesHistory.new(50)
115
+ def ensure_scheduler_process
116
+ unless @scheduler_running
117
+ @scheduler_running = true
118
+ async.scheduler_run
119
+ end
84
120
  end
85
121
 
86
- def scheduler_freeze
87
- @scheduler_freeze = true
122
+ def scheduler_run
123
+ while @scheduled_call = scheduler_calls.shift
124
+ @scheduler_running = true
125
+ @last_scheduled_call = @scheduled_call
126
+ scheduler_consume(@scheduled_call)
127
+ end
128
+ @scheduler_running = false
88
129
  end
89
130
 
90
- def scheduler_unfreeze
91
- @scheduler_freeze = nil
131
+ def scheduler_commands_list
132
+ scheduler_calls.map { |c| c[:command] }
92
133
  end
93
134
 
94
- def scheduler_freeze?
95
- @scheduler_freeze
135
+ def scheduler_clear_pending_list
136
+ scheduler_calls.clear
96
137
  end
97
138
 
98
- private
139
+ def scheduler_last_command
140
+ @last_scheduled_call.try :[], :command
141
+ end
142
+
143
+ def scheduler_last_reason
144
+ reason_from_call(@last_scheduled_call)
145
+ end
99
146
 
100
- def remove_scheduler
101
- @scheduler.terminate if @scheduler && @scheduler.alive?
147
+ def scheduler_current_command
148
+ @scheduled_call.try :[], :command
102
149
  end
103
150
 
104
- def scheduler
105
- @scheduler ||= Eye::Utils::CelluloidChain.new(current_actor)
151
+ private
152
+
153
+ def reason_from_call(call)
154
+ return unless call
155
+
156
+ if msg = call[:reason]
157
+ msg.to_s
158
+ elsif call[:by]
159
+ "#{call[:command]} by #{call[:by]}"
160
+ end
106
161
  end
107
162
 
108
163
  end
@@ -1,13 +1,13 @@
1
- require 'state_machine'
2
- require 'state_machine/version'
1
+ require 'state_machines'
2
+ require 'state_machines/version'
3
3
 
4
4
  class Eye::Process
5
5
 
6
- class StateError < Exception; end
6
+ class StateError < RuntimeError; end
7
7
 
8
8
  # do transition
9
- def switch(name, reason = nil)
10
- @state_reason = reason || last_scheduled_reason
9
+ def switch(name, call = {})
10
+ @state_call = @last_scheduled_call ? @last_scheduled_call.merge(call) : call
11
11
  self.send("#{name}!")
12
12
  end
13
13
 
@@ -71,7 +71,7 @@ class Eye::Process
71
71
 
72
72
  def on_crashed
73
73
  self.pid = nil
74
- schedule :check_crash, Eye::Reason.new(:crashed)
74
+ schedule command: :check_crash, reason: :crashed
75
75
  end
76
76
 
77
77
  def on_unmonitored
@@ -79,9 +79,10 @@ class Eye::Process
79
79
  end
80
80
 
81
81
  def log_transition(transition)
82
- if transition.to_name != transition.from_name || @state_reason.is_a?(Eye::Reason::User)
83
- @states_history.push transition.to_name, @state_reason
84
- info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{"(reason: #{@state_reason})" if @state_reason}"
82
+ if transition.to_name != transition.from_name || @state_call[:by] == :user
83
+ reason_str = reason_from_call(@state_call)
84
+ @states_history.push transition.to_name, reason_str
85
+ info "switch :#{transition.event} [:#{transition.from_name} => :#{transition.to_name}] #{reason_str}"
85
86
  end
86
87
  end
87
88
 
@@ -13,7 +13,7 @@ class Eye::Process::StatesHistory < Eye::Utils::Tail
13
13
  tm = [tm, from_time].max if from_time
14
14
  tm = tm.to_f
15
15
  if block
16
- self.each { |s| block.call(s) if s[:at] >= tm }
16
+ self.each { |s| yield(s) if s[:at] >= tm }
17
17
  else
18
18
  self.select { |s| s[:at] >= tm }.map { |c| c[:state] }
19
19
  end
@@ -48,7 +48,7 @@ module Eye::Process::System
48
48
  if (id1 - st1).abs > self[:check_identity_grace]
49
49
  args = Eye::SystemResources.args(pid)
50
50
  msg = "pid_file: '#{Eye::Utils.human_time2(id)}', process: '#{Eye::Utils.human_time2(st)}' (#{args})"
51
- res = (id1 < st1) ? :fail : :touched
51
+ res = id1 < st1 ? :fail : :touched
52
52
  warn "compare_identity: #{res}, #{msg}"
53
53
  res
54
54
  else
@@ -9,7 +9,7 @@ module Eye::Process::Trigger
9
9
  end
10
10
 
11
11
  def check_triggers(transition)
12
- self.triggers.each { |trigger| trigger.notify(transition, state_reason) }
12
+ self.triggers.each { |trigger| trigger.notify(transition, @state_call) }
13
13
  end
14
14
 
15
15
  # conditional start, used in triggers, to start only from unmonitored state, and only if special reason
@@ -19,9 +19,10 @@ module Eye::Process::Trigger
19
19
  return
20
20
  end
21
21
 
22
- previous_reason = state_reason
23
- if last_scheduled_reason && previous_reason && last_scheduled_reason.class != previous_reason.class
24
- warn "skip, last_scheduled_reason(#{last_scheduled_reason.inspect}) != previous_reason(#{previous_reason})"
22
+ state_by = @state_call.try(:[], :by)
23
+ current_by = @scheduled_call.try(:[], :by)
24
+ if state_by && current_by && state_by != current_by
25
+ warn "skip, state_by(#{state_by}) != current_by(#{current_by})"
25
26
  return
26
27
  end
27
28
 
@@ -3,7 +3,7 @@ require 'etc'
3
3
 
4
4
  module Eye::Process::Validate
5
5
 
6
- class Error < Exception; end
6
+ class Error < RuntimeError; end
7
7
 
8
8
  def validate(config, localize = true)
9
9
  if (str = config[:start_command])