Module: MiGA::Common::WithDaemon

Included in:
Daemon, Lair
Defined in:
lib/miga/common/with_daemon.rb

Overview

Helper module with specific functions to handle objects that have daemons. The class including it must extend MiGA::Common::WithDaemonClass and define:

  • #daemon_home Path to the daemon’s home

  • #daemon_name Name of the daemon

  • #daemon_loop One loop of the daemon to be repeatedly called

  • #daemon_first_loop To be executed before the first call to #daemon_loop

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#declare_alive_pidObject (readonly)

Process ID of the forked process declaring the daemon alive



13
14
15
# File 'lib/miga/common/with_daemon.rb', line 13

def declare_alive_pid
  @declare_alive_pid
end

#loop_iObject (readonly)

Loop counter



16
17
18
# File 'lib/miga/common/with_daemon.rb', line 16

def loop_i
  @loop_i
end

Instance Method Details

#active?Boolean

Is the daemon active?

Returns:

  • (Boolean)


46
47
48
49
50
# File 'lib/miga/common/with_daemon.rb', line 46

def active?
  return false unless File.exist? alive_file

  (last_alive || Time.new(0)) > Time.now - 60
end

#alive_fileObject



30
31
32
# File 'lib/miga/common/with_daemon.rb', line 30

def alive_file
  self.class.alive_file(daemon_home)
end

#daemon(task, opts = [], wait = true) ⇒ Object

Launches the task with options opts (as command-line arguments) and returns the process ID as an Integer. If wait it waits for the process to complete, immediately returns otherwise. Supported tasks: start, run, stop, status.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/miga/common/with_daemon.rb', line 127

def daemon(task, opts = [], wait = true)
  MiGA::MiGA.DEBUG "#{self.class}#daemon #{task} #{opts}"
  task = task.to_sym
  raise "Unsupported task: #{task}" unless respond_to? task
  return send(task, opts, wait) unless %i[start run].include? task

  # start & run:
  options = default_options
  opts.unshift(task.to_s)
  options[:ARGV] = opts
  # This additional degree of separation below was introduced so the Daemons
  # package doesn't kill the parent process in workflows.
  pid = fork { launch_daemon_proc(options) }
  Process.wait(pid) if wait
  pid
end

#declare_aliveObject

Tell the world that you’re alive.



54
55
56
57
58
59
60
61
# File 'lib/miga/common/with_daemon.rb', line 54

def declare_alive
  if active?
    raise "Trying to declare alive an active daemon, if you think this is a" \
      " mistake please remove #{alive_file} or try again in 1 minute"
  end
  @declare_alive_pid = fork { declare_alive_loop }
  sleep(1) # <- to wait for the process check
end

#declare_alive_loop(pid = Process.ppid) ⇒ Object

Loop checking if the process with PID pid is still alive. By default, the parent process. Do not use directly, use declare_alive instead. Returns a symbol indicating the reason to stop:

  • :no_home Daemon’s home does not exist

  • :no_process_alive Process is not currently running

  • :termination_file Found termination file



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/miga/common/with_daemon.rb', line 71

def declare_alive_loop(pid = Process.ppid)
  i = -1
  loop do
    i += 1
    return :no_home unless Dir.exist? daemon_home
    return :no_process_alive unless process_alive? pid

    write_alive_file if i % 30 == 0
    return :termination_file if termination_file? pid

    sleep(1)
  end
end

#default_optionsObject

Returns Hash containing the default options for the daemon.



115
116
117
118
119
120
# File 'lib/miga/common/with_daemon.rb', line 115

def default_options
  {
    dir_mode: :normal, dir: daemon_home, multiple: false, log_output: true,
    stop_proc: :terminate
  }
end

#in_loopObject

One loop, returns a boolean indicating if the execution should continue



187
188
189
190
191
192
193
194
195
# File 'lib/miga/common/with_daemon.rb', line 187

def in_loop
  if loop_i.nil?
    declare_alive
    daemon_first_loop
    @loop_i = -1
  end
  @loop_i += 1
  daemon_loop
end

#last_aliveObject

When was the daemon last seen active?



40
41
42
# File 'lib/miga/common/with_daemon.rb', line 40

def last_alive
  self.class.last_alive(daemon_home)
end

#launch_daemon_proc(options) ⇒ Object

Pass daemon options to Daemons. Do not use directly, use daemon instead.



169
170
171
# File 'lib/miga/common/with_daemon.rb', line 169

def launch_daemon_proc(options)
  Daemons.run_proc("#{daemon_name}", options) { while in_loop; end }
end

#output_fileObject



22
23
24
# File 'lib/miga/common/with_daemon.rb', line 22

def output_file
  File.join(daemon_home, "#{daemon_name}.output")
end

#pid_fileObject



18
19
20
# File 'lib/miga/common/with_daemon.rb', line 18

def pid_file
  File.join(daemon_home, "#{daemon_name}.pid")
end

#process_alive?(pid) ⇒ Boolean

Check if the process with PID pid is still alive, call terminate otherwise.

Returns:

  • (Boolean)


92
93
94
95
96
97
98
# File 'lib/miga/common/with_daemon.rb', line 92

def process_alive?(pid)
  Process.kill(0, pid)
  true
rescue Errno::ESRCH, Errno::EPERM, Errno::ENOENT
  terminate
  false
end

#run(opts = [], wait = true) ⇒ Object

Initializes the daemon on top with opts



181
182
183
# File 'lib/miga/common/with_daemon.rb', line 181

def run(opts = [], wait = true)
  daemon(:run, opts, wait)
end

#start(opts = [], wait = true) ⇒ Object

Initializes the daemon with opts



175
176
177
# File 'lib/miga/common/with_daemon.rb', line 175

def start(opts = [], wait = true)
  daemon(:start, opts, wait)
end

#status(opts = [], wait = true) ⇒ Object

Returns the status of the daemon with opts



159
160
161
162
163
164
165
# File 'lib/miga/common/with_daemon.rb', line 159

def status(opts = [], wait = true)
  if active?
    say "Running with pid #{File.size?(pid_file) ? File.read(pid_file) : '?'}"
  else
    say 'Not running'
  end
end

#stop(opts = [], wait = true) ⇒ Object

Stops the daemon with opts



146
147
148
149
150
151
152
153
154
155
# File 'lib/miga/common/with_daemon.rb', line 146

def stop(opts = [], wait = true)
  if active?
    say 'Sending termination message'
    FileUtils.touch(terminate_file)
    sleep(0.5) while active? if wait
    File.unlink(pid_file) if File.exist?(pid_file)
  else
    say 'No running instances'
  end
end

#terminateObject

Declares a daemon termination. Do not use, directly, use #stop instead.



199
200
201
202
203
204
205
# File 'lib/miga/common/with_daemon.rb', line 199

def terminate
  unless declare_alive_pid.nil?
    Process.kill(9, declare_alive_pid)
    @declare_alive_pid = nil
  end
  File.rename(alive_file, terminated_file) if File.exist? alive_file
end

#terminate_fileObject



26
27
28
# File 'lib/miga/common/with_daemon.rb', line 26

def terminate_file
  File.join(daemon_home, 'terminate-daemon')
end

#terminated_fileObject



34
35
36
# File 'lib/miga/common/with_daemon.rb', line 34

def terminated_file
  self.class.terminated_file(daemon_home)
end

#termination_file?(pid) ⇒ Boolean

Check if a termination file exists and terminate process with PID pid if it does. Do not kill any process if pid is nil

Returns:

  • (Boolean)


103
104
105
106
107
108
109
110
111
# File 'lib/miga/common/with_daemon.rb', line 103

def termination_file?(pid)
  return false unless File.exist? terminate_file

  say 'Found termination file, terminating'
  File.unlink(terminate_file)
  terminate
  Process.kill(9, pid) unless pid.nil?
  true
end

#write_alive_fileObject



85
86
87
# File 'lib/miga/common/with_daemon.rb', line 85

def write_alive_file
  File.open(alive_file, 'w') { |fh| fh.print Time.now.to_s }
end