Class: Cognizant::Daemon

Inherits:
Object
  • Object
show all
Defined in:
lib/cognizant/daemon.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#applicationsHash

Hash of applications being managed.

Returns:


42
43
44
# File 'lib/cognizant/daemon.rb', line 42

def applications
  @applications
end

#daemonizetrue, false

Whether or not to the daemon in the background.

Returns:

  • (true, false)

    Defaults to true


14
15
16
# File 'lib/cognizant/daemon.rb', line 14

def daemonize
  @daemonize
end

#logfileString

The file to log the daemon's operations information into. e.g. /Users/Gurpartap/.cognizant/cognizantd.log

Returns:

  • (String)

    Defaults to /var/log/cognizant/cognizantd.log


26
27
28
# File 'lib/cognizant/daemon.rb', line 26

def logfile
  @logfile
end

#loglevelLogger::Severity

Note:

The possible values must be one of the following: DEBUG, INFO, WARN, ERROR and FATAL or 0, 1, 2, 3, 4 (respectively).

The level of information to log. This does not affect the log level of managed processes.

Returns:

  • (Logger::Severity)

    Defaults to Logger::INFO


37
38
39
# File 'lib/cognizant/daemon.rb', line 37

def loglevel
  @loglevel
end

#pidfileString

The pid lock file for the daemon. e.g. /Users/Gurpartap/.cognizant/cognizantd.pid

Returns:

  • (String)

    Defaults to /var/run/cognizant/cognizantd.pid


21
22
23
# File 'lib/cognizant/daemon.rb', line 21

def pidfile
  @pidfile
end

#socketObject


45
46
47
# File 'lib/cognizant/daemon.rb', line 45

def socket
  @socket
end

#sockfileObject

Returns the value of attribute sockfile


16
17
18
# File 'lib/cognizant/daemon.rb', line 16

def sockfile
  @sockfile
end

#syslogtrue, false

Whether or not to log to syslog daemon instead of file.

Returns:

  • (true, false)

    Defaults to true


30
31
32
# File 'lib/cognizant/daemon.rb', line 30

def syslog
  @syslog
end

Instance Method Details

#create_application(name, options = {}, &block) ⇒ Object


142
143
144
145
146
# File 'lib/cognizant/daemon.rb', line 142

def create_application(name, options = {}, &block)
  app = Cognizant::Application.new(name, options, &block)
  self.applications[app.name.to_sym] = app
  app
end

#daemonize_processObject

Daemonize the current process and save it pid in a file.


256
257
258
259
260
261
# File 'lib/cognizant/daemon.rb', line 256

def daemonize_process
  if self.daemonize
    Log[self].info "Daemonizing into the background..."
    ::Process.daemon
  end
end

#load_file(file) ⇒ Object


117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/cognizant/daemon.rb', line 117

def load_file(file)
  if file.end_with?(".yml")
    yml_config = YAML.load_file(file)
    if yml_config
      yml_config.deep_symbolize_keys!
      yml_config_apps = yml_config.delete(:applications) if yml_config.has_key?(:applications)
      yml_config_apps.each { |key, value| self.create_application(key, value) }
    end
  else
    rb_file = File.expand_path(file)
    Kernel.load(rb_file) if File.exists?(rb_file)
  end
end

#reset!Object


131
132
133
134
135
136
137
138
139
140
# File 'lib/cognizant/daemon.rb', line 131

def reset!
  self.daemonize    = true
  self.sockfile     = "/var/run/cognizant/cognizantd.sock"
  self.pidfile      = "/var/run/cognizant/cognizantd.pid"
  self.syslog       = false
  self.logfile      = "/var/log/cognizant/cognizantd.log"
  self.loglevel     = Logging::LEVELS["INFO"]
  self.applications.values.each(&:reset!) if self.applications.is_a?(Hash)
  self.applications = {}
end

#setup_directoriesObject


166
167
168
169
# File 'lib/cognizant/daemon.rb', line 166

def setup_directories
  # Create the require directories.
  System.mkdir(File.dirname(self.sockfile), File.dirname(self.pidfile), File.dirname(self.logfile))
end

#setup_loggingObject


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/cognizant/daemon.rb', line 148

def setup_logging
  Cognizant::Log.logger.root.level = self.loglevel

  unless self.daemonize
    stdout_appender = Cognizant::Log.stdout
    Cognizant::Log.logger.root.add_appenders(stdout_appender)
  end

  if self.syslog
    # TODO: Choose a non-default facility? (default: LOG_USR).
    syslog_appender = Cognizant::Log.syslog("cognizantd")
    Cognizant::Log.logger.root.add_appenders(syslog_appender)
  elsif self.logfile
    logfile_appender = Cognizant::Log.file(self.logfile)
    Cognizant::Log.logger.root.add_appenders(logfile_appender)
  end
end

#shutdown!Object

Stops the socket server and the tick loop, and performs cleanup.


172
173
174
175
176
177
178
179
180
181
182
# File 'lib/cognizant/daemon.rb', line 172

def shutdown!
  Log[self].info "Shutting down Cognizant daemon..."
  EventMachine.next_tick do
    EventMachine.add_shutdown_hook do
      self.applications.values.each(&:shutdown!)
      unlink_pid
      # TODO: Close logger?
    end
    EventMachine.stop
  end
end

#start(options = {}) ⇒ Object


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/cognizant/daemon.rb', line 47

def start(options = {})
  self.reset!

  load_files = []
  rb_files   = []
  yml_files  = []
  apps       = []

  load_files = [*options.delete(:load)] if options and options.has_key?(:load)
  load_files.each do |include|
    Dir[File.expand_path(include)].each do |file|
      Log[self].info "Including config from #{file}."
      if file.end_with?(".yml")
        yml_files << file
      else
        rb_files << file
      end
    end
  end
  apps << options.delete(:applications) if options and options.has_key?(:applications)

  yml_files.each do |file|
    file_opts = YAML.load_file(file)
    if file_opts
      file_opts.deep_symbolize_keys!
      apps << file_opts.delete(:applications) if file_opts.has_key?(:applications)
    end
  end

  # Attributes.
  options.each do |key, value|
    self.send("#{key}=", options.delete(key)) if self.respond_to?("#{key}=")
  end

  self.sockfile = File.expand_path(self.sockfile)
  self.pidfile  = File.expand_path(self.pidfile)
  self.logfile  = File.expand_path(self.logfile)

  Log[self].info "Booting up Cognizant daemon..."
  setup_directories
  setup_logging
  stop_previous_daemon
  stop_previous_socket
  trap_signals
  daemonize_process
  write_pid

  EventMachine.run do
    # Applications.
    Log[self].info "Cognizant daemon running successfully."

    apps.each do |app|
      app.each do |key, value|
        self.create_application(key, value)
      end
    end

    [*rb_files].each do |rb_file|
      load_file(rb_file)
    end

    EventMachine.start_unix_domain_server(self.sockfile, Cognizant::Interface)

    EventMachine.add_periodic_timer(1) do
      Cognizant::System.reset_data!
      self.applications.values.each(&:tick)
    end
  end
end

#stop_previous_daemonObject


195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/cognizant/daemon.rb', line 195

def stop_previous_daemon
  if self.pidfile and File.exists?(self.pidfile)
    if previous_daemon_pid = File.read(self.pidfile).to_i
      # Only attempt to stop automatically if the daemon will run in background.
      if self.daemonize and Cognizant::System.pid_running?(previous_daemon_pid)
        # Ensure that the process stops within 5 seconds or force kill.
        signals = ["TERM", "KILL"]
        timeout = 2
        catch :stopped do
          signals.each do |stop_signal|
            # Send the stop signal and wait for it to stop.
            Cognizant::System.signal(stop_signal, previous_daemon_pid)

            # Poll to see if it has stopped yet. Minimum 2 so that we check at least once again.
            ([timeout / signals.size, 2].max).times do
              throw :stopped unless Cognizant::System.pid_running?(previous_daemon_pid)
              sleep 1
            end
          end
        end
      end
    end

    # Alert the user to manually stop the previous daemon, if it is [still] alive.
    if Cognizant::System.pid_running?(previous_daemon_pid)
      raise "There is already a daemon running with pid #{previous_daemon_pid}."
    else
      unlink_pid # This will be overwritten anyways.
    end
  end
end

#stop_previous_socketObject


227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/cognizant/daemon.rb', line 227

def stop_previous_socket
  # Socket isn't actually owned by anyone.
  begin
    sock = UNIXSocket.new(self.sockfile)
  rescue Errno::ECONNREFUSED
    # This happens with non-socket files and when the listening
    # end of a socket has exited.
  rescue Errno::ENOENT
    # Socket doesn't exist.
    return
  else
    # Rats, it's still active.
    sock.close
    raise Errno::EADDRINUSE.new("Another process or application is likely already listening on the socket at #{self.sockfile}.")
  end

  # Socket should still exist, so don't need to handle error.
  stat = File.stat(self.sockfile)
  unless stat.socket?
    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.")
  end

  Log[self].info("Blowing away old socket file at #{self.sockfile}. This likely indicates a previous Cognizant application which did not shutdown gracefully.")

  # Whee, blow it away.
  unlink_sockfile
end

#trap_signalsObject


184
185
186
187
188
189
190
191
192
193
# File 'lib/cognizant/daemon.rb', line 184

def trap_signals
  terminator = Proc.new do
    Log[self].info "Received signal to shutdown."
    shutdown!
  end

  Signal.trap('TERM', &terminator)
  Signal.trap('INT',  &terminator)
  Signal.trap('QUIT', &terminator)
end

271
272
273
# File 'lib/cognizant/daemon.rb', line 271

def unlink_pid
  Cognizant::System.unlink_file(self.pidfile) if self.pidfile
end

275
276
277
# File 'lib/cognizant/daemon.rb', line 275

def unlink_sockfile
  Cognizant::System.unlink_file(self.sockfile) if self.sockfile
end

#write_pidObject


263
264
265
266
267
268
269
# File 'lib/cognizant/daemon.rb', line 263

def write_pid
  pid = ::Process.pid
  if self.pidfile
    Log[self].info "Writing the daemon pid (#{pid}) to the pidfile..."
    File.open(self.pidfile, "w") { |f| f.write(pid) }
  end
end