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,23 @@
1
+ require 'cognizant/util/dsl_proxy_methods_handler'
2
+
3
+ module Cognizant
4
+ class Application
5
+ class DSLProxy
6
+ include Cognizant::Util::DSLProxyMethodsHandler
7
+
8
+ def initialize(application, &dsl_block)
9
+ super
10
+ @application = application
11
+ instance_eval(&dsl_block)
12
+ end
13
+
14
+ def monitor(process_name = nil, attributes = {}, &block)
15
+ @application.monitor(process_name, attributes, &block)
16
+ end
17
+
18
+ def process(process_name = nil, attributes = {}, &block)
19
+ @application.process(process_name, attributes, &block)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ require 'uri'
2
+ require 'yaml'
3
+ require 'socket'
4
+
5
+ module Cognizant
6
+ class Client
7
+ # Keep this in this file so client can be loaded entirely
8
+ # standalone by user code.
9
+ module Transport
10
+ def self.send_message(socket, message)
11
+ line = serialize_message(message)
12
+ socket.write(line)
13
+ end
14
+
15
+ def self.receive_message(socket)
16
+ line = socket.readline
17
+ deserialize_message(line)
18
+ end
19
+
20
+ def self.serialize_message(message)
21
+ serialized = YAML.dump(message)
22
+ escaped = URI.escape(serialized, "%\n")
23
+ escaped + "\n"
24
+ end
25
+
26
+ def self.deserialize_message(line)
27
+ serialized = URI.unescape(line)
28
+ YAML.load(serialized)
29
+ end
30
+ end
31
+
32
+ @@responseless_commands = ["shutdown"]
33
+
34
+ def self.for_port(hostname = "127.0.0.1", port)
35
+ socket = TCPSocket.open(hostname, port)
36
+ self.new(socket)
37
+ end
38
+
39
+ def self.for_path(path_to_socket)
40
+ socket = UNIXSocket.open(path_to_socket)
41
+ self.new(socket)
42
+ end
43
+
44
+ def initialize(socket)
45
+ @socket = socket
46
+ end
47
+
48
+ def command(command_hash)
49
+ Transport.send_message(@socket, command_hash)
50
+ Transport.receive_message(@socket) if expect_response?(command_hash)
51
+ end
52
+
53
+ def expect_response?(command_hash)
54
+ !@@responseless_commands.include?(command_hash['command'])
55
+ end
56
+
57
+ def close
58
+ @socket.close
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,164 @@
1
+ require "json"
2
+ require "cognizant/client"
3
+ require "cognizant/system"
4
+
5
+ module Cognizant
6
+ module Commands
7
+ @@commands = {}
8
+
9
+ def self.command(name, description = nil, &block)
10
+ @@commands[name] = { :description => description, :block => block }
11
+ end
12
+
13
+ def self.process_command(connection, command)
14
+ response = generate_response(connection, command)
15
+ if !response.nil?
16
+ send_message(connection, response)
17
+ else
18
+ Log[self].debug("Got back nil response, so not responding to command.")
19
+ end
20
+ # connection.close_connection_after_writing
21
+ end
22
+
23
+ def self.send_message(connection, response)
24
+ if response.kind_of?(String)
25
+ response = { 'message' => response }
26
+ end
27
+ serialized_message = Cognizant::Client::Transport.serialize_message(response)
28
+ connection.send_data(serialized_message)
29
+ end
30
+
31
+ def self.generate_response(connection, command)
32
+ begin
33
+ request = Cognizant::Client::Transport.deserialize_message(command)
34
+ rescue ArgumentError => e
35
+ return { 'message' => "Could not parse command: #{e}" }
36
+ end
37
+
38
+ unless command_name = request['command']
39
+ return { 'message' => 'No "command" parameter provided; not sure what you want me to do.' }
40
+ end
41
+
42
+ run_command(connection, request, command, command_name)
43
+ end
44
+
45
+ def self.run_command(connection, request, command, command_name)
46
+ if command_spec = @@commands[command_name]
47
+ # Log[self].debug("Received command: #{command.inspect}")
48
+ begin
49
+ return command_spec[:block].call(connection, request)
50
+ rescue StandardError => e
51
+ msg = "Error while processing command #{command_name.inspect}: #{e} (#{e.class})\n #{e.backtrace.join("\n ")}"
52
+ Log[self].error(msg)
53
+ return msg
54
+ end
55
+ else
56
+ Log[self].debug("Received unrecognized command: #{command.inspect}")
57
+ return unrecognized_command(connection, request)
58
+ end
59
+ end
60
+
61
+ def self.command_descriptions
62
+ command_specs = @@commands.select do |_, spec|
63
+ spec[:description]
64
+ end.sort_by {|name, _| name}
65
+
66
+ command_specs.map do |name, spec|
67
+ "#{name}: #{spec[:description]}"
68
+ end.join("\n")
69
+ end
70
+
71
+ def self.unrecognized_command(connection, request)
72
+ <<EOF
73
+ Unrecognized command: #{request['command'].inspect}
74
+
75
+ #{command_descriptions}
76
+ EOF
77
+ end
78
+
79
+ # Used by cognizant shell.
80
+ command '_ehlo' do |conn, request|
81
+ if request["app"].to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["app"].to_sym)
82
+ use = request["app"]
83
+ else
84
+ use = Cognizant::Controller.daemon.applications.keys.first
85
+ end
86
+
87
+ message = <<EOF
88
+ Welcome #{request['user']}! You are speaking to the Cognizant Monitoring Daemon.
89
+ EOF
90
+
91
+ { "message" => message, "use" => use }
92
+ end
93
+
94
+ # Used by cognizant shell.
95
+ command '_autocomplete_keywords' do |conn, request|
96
+ keywords = []
97
+ keywords += @@commands.keys.reject { |c| c =~ /^\_.+/ }
98
+ keywords += Cognizant::Controller.daemon.applications.keys
99
+ keywords += Cognizant::Controller.daemon.applications[request["app"].to_sym].processes.keys if request.has_key?("app") and request["app"].to_s.size > 0
100
+ keywords
101
+ end
102
+
103
+ # command('reload', 'Reload Cognizant') do |connection, _|
104
+ # # TODO: make reload actually work (command socket reopening is
105
+ # # an issue). Would also be nice if user got a confirmation that
106
+ # # the reload completed, though that's not strictly necessary.
107
+ #
108
+ # # In the normal case, this will do a write
109
+ # # synchronously. Otherwise, the bytes will be stuck into the
110
+ # # buffer and lost upon reload.
111
+ # send_message(connection, 'Reloading, as commanded')
112
+ # Cognizant.reload
113
+ #
114
+ # # Reload should not return
115
+ # raise "Not reachable"
116
+ # end
117
+
118
+ def self.processes_for_name_or_group(app, args)
119
+ processes = []
120
+ if app.to_s.size > 0
121
+ Cognizant::Controller.daemon.applications[app.to_sym].processes.values.each do |process|
122
+ processes << process if args.include?(process.name) or args.include?(process.group)
123
+ end
124
+ end
125
+ processes
126
+ end
127
+
128
+ def self.send_process_or_group_status(app, args = [])
129
+ output_processes = []
130
+ if args.size > 0
131
+ output_processes = processes_for_name_or_group(app, args)
132
+ return %Q{No such process or group: #{args.join(', ')}} if output_processes.size == 0
133
+ else
134
+ output_processes = Cognizant::Controller.daemon.applications[app.to_sym].processes.values
135
+ end
136
+
137
+ format_process_or_group_status(output_processes)
138
+ end
139
+
140
+ def self.format_process_or_group_status(output_processes)
141
+ output = []
142
+ output_processes.each do |process|
143
+ pid = process.cached_pid
144
+ output << {
145
+ "Process" => process.name,
146
+ "PID" => pid,
147
+ "Group" => process.group,
148
+ "State" => process.state,
149
+ "Since" => process.last_transition_time,
150
+ "% CPU" => Cognizant::System.cpu_usage(pid).to_f,
151
+ "Memory" => Cognizant::System.memory_usage(pid).to_f # in KBs.
152
+ }
153
+ end
154
+ output
155
+ end
156
+ end
157
+ end
158
+
159
+ require "cognizant/commands/actions"
160
+ require "cognizant/commands/help"
161
+ require "cognizant/commands/load"
162
+ require "cognizant/commands/shutdown"
163
+ require "cognizant/commands/status"
164
+ require "cognizant/commands/use"
@@ -0,0 +1,30 @@
1
+ module Cognizant
2
+ module Commands
3
+ [
4
+ ["monitor", "Monitor the specified process or group"],
5
+ ["unmonitor", "Unmonitor the specified process or group"],
6
+ ["start", "Start the specified process or group"],
7
+ ["stop", "Stop the specified process or group"],
8
+ ["restart", "Restart the specified process or group"]
9
+ ].each do |(name, description)|
10
+ command(name, description) do |connection, request|
11
+ args = request["args"]
12
+ unless args.size > 0
13
+ "Missing process name"
14
+ else
15
+ output_processes = []
16
+ output_processes = processes_for_name_or_group(request["app"], args)
17
+ if output_processes.size == 0
18
+ %Q{No such process or group: #{args.join(', ')}}
19
+ else
20
+ output_processes.each do |process|
21
+ process.handle_user_command(name)
22
+ end
23
+ # send_process_or_group_status(request["app"], args)
24
+ "OK"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module Cognizant
2
+ module Commands
3
+ command('help', 'Print out available commands') do
4
+ "You are speaking to the Cognizant command socket. You can run the following commands:
5
+
6
+ #{command_descriptions}
7
+ "
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("load", "Loads the process information from specified Ruby file") do |connection, request|
4
+ request["args"].each do |file|
5
+ Cognizant::Controller.daemon.load_file(file)
6
+ end
7
+ "OK"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("shutdown", "Stop the monitoring daemon without affecting managed processes") do |connection, _|
4
+ Cognizant::Controller.daemon.shutdown!
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("status", "Display status of managed process(es) or group(s)") do |connection, request|
4
+ if request.has_key?("app") and request["app"].to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["app"].to_sym)
5
+ send_process_or_group_status(request["app"], request["args"])
6
+ else
7
+ %Q{No such application: "#{request['app']}"}
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Cognizant
2
+ module Commands
3
+ command("use", "Switch the current application for use with process maintenance commands") do |connection, request|
4
+ puts "Cognizant::Controller.daemon.applications.keys: #{Cognizant::Controller.daemon.applications.keys}"
5
+ if request["args"].size > 0 and request["args"].first.to_s.size > 0 and Cognizant::Controller.daemon.applications.has_key?(request["args"].first.to_sym)
6
+ app = request["args"].first
7
+ message = "OK"
8
+ else
9
+ app = request["app"]
10
+ message = %Q{No such application: "#{request['args'].first}"}
11
+ end
12
+ { "use" => app, "message" => message }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require "cognizant/daemon"
2
+
3
+ module Cognizant
4
+ module Controller
5
+ class << self
6
+ attr_accessor :daemon
7
+ end
8
+
9
+ def self.daemon
10
+ @daemon ||= Cognizant::Daemon.new
11
+ end
12
+
13
+ def self.start_daemon(options = {})
14
+ self.daemon.start(options)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,279 @@
1
+ require "eventmachine"
2
+
3
+ require "cognizant"
4
+ require "cognizant/log"
5
+ require "cognizant/process"
6
+ require "cognizant/application"
7
+ require "cognizant/system"
8
+ require "cognizant/util/transform_hash_keys"
9
+
10
+ module Cognizant
11
+ class Daemon
12
+ # Whether or not to the daemon in the background.
13
+ # @return [true, false] Defaults to true
14
+ attr_accessor :daemonize
15
+
16
+ attr_accessor :sockfile
17
+
18
+ # The pid lock file for the daemon.
19
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.pid
20
+ # @return [String] Defaults to /var/run/cognizant/cognizantd.pid
21
+ attr_accessor :pidfile
22
+
23
+ # The file to log the daemon's operations information into.
24
+ # e.g. /Users/Gurpartap/.cognizant/cognizantd.log
25
+ # @return [String] Defaults to /var/log/cognizant/cognizantd.log
26
+ attr_accessor :logfile
27
+
28
+ # Whether or not to log to syslog daemon instead of file.
29
+ # @return [true, false] Defaults to true
30
+ attr_accessor :syslog
31
+
32
+ # The level of information to log. This does not affect the log
33
+ # level of managed processes.
34
+ # @note The possible values must be one of the following:
35
+ # DEBUG, INFO, WARN, ERROR and FATAL or 0, 1, 2, 3, 4 (respectively).
36
+ # @return [Logger::Severity] Defaults to Logger::INFO
37
+ attr_accessor :loglevel
38
+
39
+ # Hash of applications being managed.
40
+ # @private
41
+ # @return [Hash]
42
+ attr_accessor :applications
43
+
44
+ # @private
45
+ attr_accessor :socket
46
+
47
+ def start(options = {})
48
+ self.reset!
49
+
50
+ load_files = []
51
+ rb_files = []
52
+ yml_files = []
53
+ apps = []
54
+
55
+ load_files = [*options.delete(:load)] if options and options.has_key?(:load)
56
+ load_files.each do |include|
57
+ Dir[File.expand_path(include)].each do |file|
58
+ Log[self].info "Including config from #{file}."
59
+ if file.end_with?(".yml")
60
+ yml_files << file
61
+ else
62
+ rb_files << file
63
+ end
64
+ end
65
+ end
66
+ apps << options.delete(:applications) if options and options.has_key?(:applications)
67
+
68
+ yml_files.each do |file|
69
+ file_opts = YAML.load_file(file)
70
+ if file_opts
71
+ file_opts.deep_symbolize_keys!
72
+ apps << file_opts.delete(:applications) if file_opts.has_key?(:applications)
73
+ end
74
+ end
75
+
76
+ # Attributes.
77
+ options.each do |key, value|
78
+ self.send("#{key}=", options.delete(key)) if self.respond_to?("#{key}=")
79
+ end
80
+
81
+ self.sockfile = File.expand_path(self.sockfile)
82
+ self.pidfile = File.expand_path(self.pidfile)
83
+ self.logfile = File.expand_path(self.logfile)
84
+
85
+ Log[self].info "Booting up Cognizant daemon..."
86
+ setup_directories
87
+ setup_logging
88
+ stop_previous_daemon
89
+ stop_previous_socket
90
+ trap_signals
91
+ daemonize_process
92
+ write_pid
93
+
94
+ EventMachine.run do
95
+ # Applications.
96
+ Log[self].info "Cognizant daemon running successfully."
97
+
98
+ apps.each do |app|
99
+ app.each do |key, value|
100
+ self.create_application(key, value)
101
+ end
102
+ end
103
+
104
+ [*rb_files].each do |rb_file|
105
+ load_file(rb_file)
106
+ end
107
+
108
+ EventMachine.start_unix_domain_server(self.sockfile, Cognizant::Interface)
109
+
110
+ EventMachine.add_periodic_timer(1) do
111
+ Cognizant::System.reset_data!
112
+ self.applications.values.each(&:tick)
113
+ end
114
+ end
115
+ end
116
+
117
+ def load_file(file)
118
+ if file.end_with?(".yml")
119
+ yml_config = YAML.load_file(file)
120
+ if yml_config
121
+ yml_config.deep_symbolize_keys!
122
+ yml_config_apps = yml_config.delete(:applications) if yml_config.has_key?(:applications)
123
+ yml_config_apps.each { |key, value| self.create_application(key, value) }
124
+ end
125
+ else
126
+ rb_file = File.expand_path(file)
127
+ Kernel.load(rb_file) if File.exists?(rb_file)
128
+ end
129
+ end
130
+
131
+ def reset!
132
+ self.daemonize = true
133
+ self.sockfile = "/var/run/cognizant/cognizantd.sock"
134
+ self.pidfile = "/var/run/cognizant/cognizantd.pid"
135
+ self.syslog = false
136
+ self.logfile = "/var/log/cognizant/cognizantd.log"
137
+ self.loglevel = Logging::LEVELS["INFO"]
138
+ self.applications.values.each(&:reset!) if self.applications.is_a?(Hash)
139
+ self.applications = {}
140
+ end
141
+
142
+ def create_application(name, options = {}, &block)
143
+ app = Cognizant::Application.new(name, options, &block)
144
+ self.applications[app.name.to_sym] = app
145
+ app
146
+ end
147
+
148
+ def setup_logging
149
+ Cognizant::Log.logger.root.level = self.loglevel
150
+
151
+ unless self.daemonize
152
+ stdout_appender = Cognizant::Log.stdout
153
+ Cognizant::Log.logger.root.add_appenders(stdout_appender)
154
+ end
155
+
156
+ if self.syslog
157
+ # TODO: Choose a non-default facility? (default: LOG_USR).
158
+ syslog_appender = Cognizant::Log.syslog("cognizantd")
159
+ Cognizant::Log.logger.root.add_appenders(syslog_appender)
160
+ elsif self.logfile
161
+ logfile_appender = Cognizant::Log.file(self.logfile)
162
+ Cognizant::Log.logger.root.add_appenders(logfile_appender)
163
+ end
164
+ end
165
+
166
+ def setup_directories
167
+ # Create the require directories.
168
+ System.mkdir(File.dirname(self.sockfile), File.dirname(self.pidfile), File.dirname(self.logfile))
169
+ end
170
+
171
+ # Stops the socket server and the tick loop, and performs cleanup.
172
+ def shutdown!
173
+ Log[self].info "Shutting down Cognizant daemon..."
174
+ EventMachine.next_tick do
175
+ EventMachine.add_shutdown_hook do
176
+ self.applications.values.each(&:shutdown!)
177
+ unlink_pid
178
+ # TODO: Close logger?
179
+ end
180
+ EventMachine.stop
181
+ end
182
+ end
183
+
184
+ def trap_signals
185
+ terminator = Proc.new do
186
+ Log[self].info "Received signal to shutdown."
187
+ shutdown!
188
+ end
189
+
190
+ Signal.trap('TERM', &terminator)
191
+ Signal.trap('INT', &terminator)
192
+ Signal.trap('QUIT', &terminator)
193
+ end
194
+
195
+ def stop_previous_daemon
196
+ if self.pidfile and File.exists?(self.pidfile)
197
+ if previous_daemon_pid = File.read(self.pidfile).to_i
198
+ # Only attempt to stop automatically if the daemon will run in background.
199
+ if self.daemonize and Cognizant::System.pid_running?(previous_daemon_pid)
200
+ # Ensure that the process stops within 5 seconds or force kill.
201
+ signals = ["TERM", "KILL"]
202
+ timeout = 2
203
+ catch :stopped do
204
+ signals.each do |stop_signal|
205
+ # Send the stop signal and wait for it to stop.
206
+ Cognizant::System.signal(stop_signal, previous_daemon_pid)
207
+
208
+ # Poll to see if it has stopped yet. Minimum 2 so that we check at least once again.
209
+ ([timeout / signals.size, 2].max).times do
210
+ throw :stopped unless Cognizant::System.pid_running?(previous_daemon_pid)
211
+ sleep 1
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+
218
+ # Alert the user to manually stop the previous daemon, if it is [still] alive.
219
+ if Cognizant::System.pid_running?(previous_daemon_pid)
220
+ raise "There is already a daemon running with pid #{previous_daemon_pid}."
221
+ else
222
+ unlink_pid # This will be overwritten anyways.
223
+ end
224
+ end
225
+ end
226
+
227
+ def stop_previous_socket
228
+ # Socket isn't actually owned by anyone.
229
+ begin
230
+ sock = UNIXSocket.new(self.sockfile)
231
+ rescue Errno::ECONNREFUSED
232
+ # This happens with non-socket files and when the listening
233
+ # end of a socket has exited.
234
+ rescue Errno::ENOENT
235
+ # Socket doesn't exist.
236
+ return
237
+ else
238
+ # Rats, it's still active.
239
+ sock.close
240
+ raise Errno::EADDRINUSE.new("Another process or application is likely already listening on the socket at #{self.sockfile}.")
241
+ end
242
+
243
+ # Socket should still exist, so don't need to handle error.
244
+ stat = File.stat(self.sockfile)
245
+ unless stat.socket?
246
+ raise Errno::EADDRINUSE.new("Non-socket file present at socket file path #{self.sockfile}. Either remove that file and restart Cognizant, or change the socket file path.")
247
+ end
248
+
249
+ Log[self].info("Blowing away old socket file at #{self.sockfile}. This likely indicates a previous Cognizant application which did not shutdown gracefully.")
250
+
251
+ # Whee, blow it away.
252
+ unlink_sockfile
253
+ end
254
+
255
+ # Daemonize the current process and save it pid in a file.
256
+ def daemonize_process
257
+ if self.daemonize
258
+ Log[self].info "Daemonizing into the background..."
259
+ ::Process.daemon
260
+ end
261
+ end
262
+
263
+ def write_pid
264
+ pid = ::Process.pid
265
+ if self.pidfile
266
+ Log[self].info "Writing the daemon pid (#{pid}) to the pidfile..."
267
+ File.open(self.pidfile, "w") { |f| f.write(pid) }
268
+ end
269
+ end
270
+
271
+ def unlink_pid
272
+ Cognizant::System.unlink_file(self.pidfile) if self.pidfile
273
+ end
274
+
275
+ def unlink_sockfile
276
+ Cognizant::System.unlink_file(self.sockfile) if self.sockfile
277
+ end
278
+ end
279
+ end