module Procrastinator::Scheduler::DaemonWorking

Daemonized work style

@see WorkProxy

Constants

DEFAULT_PID_DIR

Default directory to store PID files in.

PID_EXT

File extension for process ID files

Public Class Methods

halt!(pid_path) click to toggle source

Stops the procrastinator process denoted by the provided pid file

# File lib/procrastinator/scheduler.rb, line 280
def self.halt!(pid_path)
   pid_path = normalize_pid pid_path

   Process.kill('TERM', pid_path.read.to_i)
end
normalize_pid(pid_path) click to toggle source

Normalizes the given pid path, including conversion to absolute path and defaults.

@param pid_path [Pathname, File, String, nil] path to normalize

# File lib/procrastinator/scheduler.rb, line 272
def self.normalize_pid(pid_path)
   normalized = Pathname.new(pid_path || DEFAULT_PID_DIR)
   normalized /= "#{ PROG_NAME.downcase }#{ PID_EXT }" unless normalized.extname == PID_EXT

   normalized.expand_path
end
running?(pid_path) click to toggle source
# File lib/procrastinator/scheduler.rb, line 286
def self.running?(pid_path)
   pid = normalize_pid(pid_path).read.to_i

   # this raises Errno::ESRCH when no process found, therefore if found we should exit
   Process.getpgid pid

   true
rescue Errno::ENOENT, Errno::ESRCH
   false
end

Public Instance Methods

daemonized!(pid_path = nil) click to toggle source

Consumes the current process and turns it into a background daemon and proceed as threaded. Additional logging is recorded in the directory specified by the Procrastinator.setup configuration.

If pid_path ends with extension ‘.pid’, the basename will be requested as process title (depending on OS support). An extensionless path is assumed to be a directory and a default filename (and proctitle) is used.

@param pid_path [Pathname, File, String, nil] Path to where the process ID file is to be kept.

# File lib/procrastinator/scheduler.rb, line 263
def daemonized!(pid_path = nil)
   spawn_daemon(pid_path)

   threaded
end

Private Instance Methods

ensure_unique(pid_path) click to toggle source
# File lib/procrastinator/scheduler.rb, line 338
def ensure_unique(pid_path)
   return unless pid_path.exist?

   @logger.debug "Checking pid file #{ pid_path }"

   if DaemonWorking.running? pid_path
      hint = 'Either terminate that process or remove the pid file (if coincidental).'
      msg  = "Another process (pid #{ pid_path.read }) already exists for #{ pid_path }. #{ hint }"
      @logger.fatal msg
      raise ProcessExistsError, msg
   else
      @logger.warn "Replacing old pid file of defunct process (pid #{ pid_path.read }) at #{ pid_path }."
   end
end
manage_pid(pid_path) click to toggle source
# File lib/procrastinator/scheduler.rb, line 322
def manage_pid(pid_path)
   ensure_unique(pid_path)

   @logger.debug "Managing pid at path: #{ pid_path }"
   pid_path.dirname.mkpath
   pid_path.write Process.pid.to_s

   at_exit do
      if pid_path.exist?
         @logger.debug "Cleaning up pid file #{ pid_path }"
         pid_path.delete
      end
      @logger.info "Procrastinator (pid #{ Process.pid }) halted."
   end
end
print_debug_context() click to toggle source
rename_process(pid_path) click to toggle source
# File lib/procrastinator/scheduler.rb, line 360
def rename_process(pid_path)
   name = pid_path.basename(PID_EXT).to_s

   if system('pidof', name, out: File::NULL)
      @logger.warn "Another process is already named '#{ name }'. Consider the 'name:' keyword to distinguish."
   end

   @logger.debug "Renaming process to: #{ name }"
   Process.setproctitle name
end
spawn_daemon(pid_path) click to toggle source
# File lib/procrastinator/scheduler.rb, line 303
def spawn_daemon(pid_path)
   pid_path = DaemonWorking.normalize_pid pid_path

   open_log quiet: true
   @logger.info "Starting #{ PROG_NAME } daemon..."

   print_debug_context

   # "You, search from the spastic dentistry department down through disembowelment.
   #  You, cover children's dance recitals through holiday weekend IKEA. Go."
   Process.daemon

   manage_pid pid_path
   rename_process pid_path
rescue StandardError => e
   @logger&.fatal ([e.message] + e.backtrace).join("\n")
   raise e
end