daemons 1.1.9 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/README.md +207 -0
  4. data/Releases +85 -24
  5. data/examples/call/call.rb +13 -16
  6. data/examples/call/call_monitor.rb +13 -17
  7. data/examples/daemonize/daemonize.rb +4 -8
  8. data/examples/run/ctrl_crash.rb +0 -1
  9. data/examples/run/ctrl_custom_logfiles.rb +18 -0
  10. data/examples/run/ctrl_exec.rb +0 -1
  11. data/examples/run/ctrl_exit.rb +0 -1
  12. data/examples/run/ctrl_keep_pid_files.rb +1 -3
  13. data/examples/run/ctrl_monitor.rb +0 -1
  14. data/examples/run/ctrl_monitor_multiple.rb +17 -0
  15. data/examples/run/ctrl_monitor_nocrash.rb +15 -0
  16. data/examples/run/ctrl_multiple.rb +0 -1
  17. data/examples/run/ctrl_ontop.rb +0 -1
  18. data/examples/run/ctrl_optionparser.rb +5 -7
  19. data/examples/run/ctrl_proc.rb +8 -9
  20. data/examples/run/ctrl_proc_multiple.rb +4 -6
  21. data/examples/run/ctrl_proc_rand.rb +2 -4
  22. data/examples/run/ctrl_proc_simple.rb +0 -1
  23. data/examples/run/myserver.rb +0 -1
  24. data/examples/run/myserver_crashing.rb +5 -5
  25. data/examples/run/myserver_exiting.rb +2 -2
  26. data/examples/run/myserver_hanging.rb +4 -5
  27. data/examples/run/myserver_slowstop.rb +5 -6
  28. data/lib/daemons/application.rb +235 -229
  29. data/lib/daemons/application_group.rb +115 -100
  30. data/lib/daemons/change_privilege.rb +2 -4
  31. data/lib/daemons/cmdline.rb +75 -62
  32. data/lib/daemons/controller.rb +36 -54
  33. data/lib/daemons/daemonize.rb +74 -75
  34. data/lib/daemons/etc_extension.rb +3 -4
  35. data/lib/daemons/exceptions.rb +11 -13
  36. data/lib/daemons/monitor.rb +57 -77
  37. data/lib/daemons/pid.rb +26 -56
  38. data/lib/daemons/pidfile.rb +49 -44
  39. data/lib/daemons/pidmem.rb +5 -9
  40. data/lib/daemons/reporter.rb +54 -0
  41. data/lib/daemons/syslogio.rb +240 -0
  42. data/lib/daemons/version.rb +3 -0
  43. data/lib/daemons.rb +87 -77
  44. metadata +111 -46
  45. data/README +0 -214
  46. data/Rakefile +0 -90
  47. data/TODO +0 -2
  48. data/setup.rb +0 -1360
@@ -1,5 +1,4 @@
1
1
  module Daemonize
2
-
3
2
  # Try to fork if at all possible retrying every 5 sec if the
4
3
  # maximum process limit for the system has been reached
5
4
  def safefork
@@ -18,152 +17,152 @@ module Daemonize
18
17
  end
19
18
  end
20
19
  module_function :safefork
21
-
22
-
20
+
23
21
  # Simulate the daemonization process (:ontop mode)
24
- # NOTE: STDOUT and STDERR will not be redirected to the logfile,
22
+ # NOTE: $stdout and $stderr will not be redirected to the logfile,
25
23
  # because in :ontop mode, we normally want to see the output
26
- def simulate(logfile_name = nil)
24
+ def simulate(logfile_name = nil, app_name = nil)
25
+ $0 = app_name if app_name
26
+
27
27
  # Release old working directory
28
- Dir.chdir "/"
28
+ Dir.chdir '/'
29
+
30
+ close_io
29
31
 
30
- close_io()
32
+ # Free $stdin and point it to somewhere sensible
33
+ begin; $stdin.reopen '/dev/null'; rescue ::Exception; end
31
34
 
32
- # Free STDIN and point them somewhere sensible
33
- begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
35
+ # Split rand streams between spawning and daemonized process
36
+ srand
34
37
  end
35
38
  module_function :simulate
36
-
37
-
39
+
38
40
  # Call a given block as a daemon
39
41
  def call_as_daemon(block, logfile_name = nil, app_name = nil)
40
42
  # we use a pipe to return the PID of the daemon
41
43
  rd, wr = IO.pipe
42
-
44
+
43
45
  if tmppid = safefork
44
46
  # in the parent
45
-
47
+
46
48
  wr.close
47
49
  pid = rd.read.to_i
48
50
  rd.close
49
-
51
+
50
52
  Process.waitpid(tmppid)
51
-
53
+
52
54
  return pid
53
55
  else
54
56
  # in the child
55
-
57
+
56
58
  rd.close
57
-
59
+
58
60
  # Detach from the controlling terminal
59
- unless sess_id = Process.setsid
60
- raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
61
+ unless Process.setsid
62
+ fail Daemons.RuntimeException.new('cannot detach from controlling terminal')
61
63
  end
62
-
64
+
63
65
  # Prevent the possibility of acquiring a controlling terminal
64
66
  trap 'SIGHUP', 'IGNORE'
65
67
  exit if pid = safefork
66
-
68
+
67
69
  wr.write Process.pid
68
70
  wr.close
69
-
71
+
70
72
  $0 = app_name if app_name
71
-
73
+
72
74
  # Release old working directory
73
- Dir.chdir "/"
74
-
75
- close_io()
75
+ Dir.chdir '/'
76
+
77
+ close_io
78
+
79
+ redirect_io(logfile_name)
76
80
 
77
- redirect_io(logfile_name)
78
-
79
81
  # Split rand streams between spawning and daemonized process
80
82
  srand
81
-
83
+
82
84
  block.call
83
-
85
+
84
86
  exit
85
87
  end
86
88
  end
87
89
  module_function :call_as_daemon
88
-
89
-
90
+
90
91
  # Transform the current process into a daemon
91
92
  def daemonize(logfile_name = nil, app_name = nil)
92
93
  # Fork and exit from the parent
93
- safefork and exit
94
+ safefork && exit
94
95
 
95
96
  # Detach from the controlling terminal
96
97
  unless sess_id = Process.setsid
97
- raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
98
+ fail Daemons.RuntimeException.new('cannot detach from controlling terminal')
98
99
  end
99
100
 
100
101
  # Prevent the possibility of acquiring a controlling terminal
101
102
  trap 'SIGHUP', 'IGNORE'
102
- exit if pid = safefork
103
-
103
+ exit if safefork
104
+
104
105
  $0 = app_name if app_name
105
-
106
+
106
107
  # Release old working directory
107
- Dir.chdir "/"
108
+ Dir.chdir '/'
108
109
 
109
- close_io()
110
+ close_io
110
111
 
111
112
  redirect_io(logfile_name)
112
-
113
+
113
114
  # Split rand streams between spawning and daemonized process
114
115
  srand
115
-
116
- return sess_id
116
+
117
+ sess_id
117
118
  end
118
119
  module_function :daemonize
119
-
120
-
121
- def close_io()
120
+
121
+ def close_io
122
122
  # Make sure all input/output streams are closed
123
- # Part I: close all IO objects (except for STDIN/STDOUT/STDERR)
123
+ # Part I: close all IO objects (except for $stdin/$stdout/$stderr)
124
124
  ObjectSpace.each_object(IO) do |io|
125
- unless [STDIN, STDOUT, STDERR].include?(io)
126
- begin
127
- unless io.closed?
128
- io.close
129
- end
130
- rescue ::Exception
131
- end
125
+ unless [$stdin, $stdout, $stderr].include?(io)
126
+ io.close rescue nil
132
127
  end
133
128
  end
134
-
129
+
135
130
  # Make sure all input/output streams are closed
136
- # Part II: close all file decriptors (except for STDIN/STDOUT/STDERR)
137
- ios = Array.new(8192) {|i| IO.for_fd(i) rescue nil}.compact
138
- ios.each do |io|
139
- next if io.fileno < 3
140
- io.close
131
+ # Part II: close all file decriptors (except for $stdin/$stdout/$stderr)
132
+ 3.upto(8192) do |i|
133
+ IO.for_fd(i).close rescue nil
141
134
  end
142
135
  end
143
136
  module_function :close_io
144
-
145
-
146
- # Free STDIN/STDOUT/STDERR file descriptors and
137
+
138
+ # Free $stdin/$stdout/$stderr file descriptors and
147
139
  # point them somewhere sensible
148
140
  def redirect_io(logfile_name)
149
- begin; STDIN.reopen "/dev/null"; rescue ::Exception; end
150
-
151
- if logfile_name
141
+ begin; $stdin.reopen '/dev/null'; rescue ::Exception; end
142
+
143
+ if logfile_name == 'SYSLOG'
144
+ # attempt to use syslog via syslogio
152
145
  begin
153
- STDOUT.reopen logfile_name, "a"
154
- File.chmod(0644, logfile_name)
155
- STDOUT.sync = true
146
+ require 'syslogio'
147
+ $stdout = ::Daemons::SyslogIO.new($0, :local0, :info, $stdout)
148
+ $stderr = ::Daemons::SyslogIO.new($0, :local0, :err, $stderr)
149
+ # error out early so we can fallback to null
150
+ $stdout.puts "no logfile provided, output redirected to syslog"
156
151
  rescue ::Exception
157
- begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
152
+ # on unsupported platforms simply reopen /dev/null
153
+ begin; $stdout.reopen '/dev/null'; rescue ::Exception; end
154
+ begin; $stderr.reopen '/dev/null'; rescue ::Exception; end
158
155
  end
156
+ elsif logfile_name
157
+ $stdout.reopen logfile_name, 'a'
158
+ File.chmod(0644, logfile_name)
159
+ $stdout.sync = true
160
+ begin; $stderr.reopen $stdout; rescue ::Exception; end
161
+ $stderr.sync = true
159
162
  else
160
- begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
163
+ begin; $stdout.reopen '/dev/null'; rescue ::Exception; end
164
+ begin; $stderr.reopen '/dev/null'; rescue ::Exception; end
161
165
  end
162
-
163
- begin; STDERR.reopen STDOUT; rescue ::Exception; end
164
- STDERR.sync = true
165
166
  end
166
167
  module_function :redirect_io
167
-
168
-
169
168
  end
@@ -2,11 +2,10 @@ require 'etc'
2
2
 
3
3
  Etc.instance_eval do
4
4
  def groupname(gid)
5
- Etc.group {|e| return e.name if gid == e.gid }
6
- nil
5
+ Etc.group { |e| return e.name if gid == e.gid }
7
6
  end
7
+
8
8
  def username(uid)
9
- Etc.passwd {|e| return e.name if uid == e.uid }
10
- nil
9
+ Etc.passwd { |e| return e.name if uid == e.uid }
11
10
  end
12
11
  end
@@ -1,28 +1,26 @@
1
-
2
1
  module Daemons
3
-
4
- class Exception < ::RuntimeError
2
+ class Exception < RuntimeError
5
3
  end
6
-
4
+
7
5
  class RuntimeException < Exception
8
6
  end
9
-
7
+
10
8
  class CmdException < Exception
11
9
  end
12
-
10
+
13
11
  class Error < Exception
14
12
  end
15
-
13
+
16
14
  class SystemError < Error
17
-
18
15
  attr_reader :system_error
19
-
16
+
20
17
  def initialize(msg, system_error)
21
18
  super(msg)
22
-
19
+
23
20
  @system_error = system_error
24
21
  end
25
-
26
22
  end
27
-
28
- end
23
+
24
+ class TimeoutError < Error
25
+ end
26
+ end
@@ -1,95 +1,78 @@
1
+ require 'daemons/exceptions'
1
2
 
2
3
  module Daemons
3
-
4
4
  require 'daemons/daemonize'
5
-
5
+
6
6
  class Monitor
7
-
8
7
  def self.find(dir, app_name)
9
8
  pid = PidFile.find_files(dir, app_name, false)[0]
10
-
9
+
11
10
  if pid
12
11
  pid = PidFile.existing(pid)
13
-
12
+
14
13
  unless PidFile.running?(pid.pid)
15
14
  begin; pid.cleanup; rescue ::Exception; end
16
15
  return
17
16
  end
18
-
19
- monitor = self.allocate
20
-
17
+
18
+ monitor = allocate
19
+
21
20
  monitor.instance_variable_set(:@pid, pid)
22
-
21
+
23
22
  return monitor
24
23
  end
25
-
26
- return nil
24
+
25
+ nil
27
26
  end
28
-
29
-
30
- def initialize(an_app)
27
+
28
+ def initialize(an_app, options = {})
31
29
  @app = an_app
32
30
  @app_name = an_app.group.app_name + '_monitor'
33
-
31
+ @monitor_interval = options[:monitor_interval] || 30
32
+
34
33
  if an_app.pidfile_dir
35
34
  @pid = PidFile.new(an_app.pidfile_dir, @app_name, false)
36
35
  else
37
36
  @pid = PidMem.new
38
37
  end
39
38
  end
40
-
41
- def watch(applications)
42
- sleep(30)
43
-
39
+
40
+ def watch(application_group)
41
+ sleep(5)
42
+
44
43
  loop do
45
- applications.each {|a|
46
- sleep(10)
47
-
44
+ application_group.applications.each do |a|
48
45
  unless a.running?
49
46
  a.zap!
50
-
51
- Process.detach(fork { a.start })
52
-
53
- sleep(10)
47
+
48
+ sleep(1)
49
+
50
+ Process.detach(fork { a.start(true) })
51
+
52
+ sleep(5)
54
53
  end
55
- }
56
-
57
- sleep(30)
54
+ end
55
+
56
+ sleep(@monitor_interval)
58
57
  end
59
58
  end
60
59
  private :watch
61
-
62
-
63
- def start_with_pidfile(applications)
60
+
61
+ def start_with_pidfile(application_group)
64
62
  fork do
65
63
  Daemonize.daemonize(nil, @app_name)
66
-
67
- begin
64
+
65
+ begin
68
66
  @pid.pid = Process.pid
69
-
70
- # at_exit {
71
- # begin; @pid.cleanup; rescue ::Exception; end
72
- # }
73
-
74
- # This part is needed to remove the pid-file if the application is killed by
75
- # daemons or manually by the user.
76
- # Note that the applications is not supposed to overwrite the signal handler for
77
- # 'TERM'.
78
- #
79
- # trap('TERM') {
80
- # begin; @pid.cleanup; rescue ::Exception; end
81
- # exit
82
- # }
83
-
84
- watch(applications)
67
+ watch(application_group)
85
68
  rescue ::Exception => e
86
69
  begin
87
- File.open(@app.logfile, 'a') {|f|
70
+ File.open(@app.logfile, 'a') do |f|
88
71
  f.puts Time.now
89
72
  f.puts e
90
73
  f.puts e.backtrace.inspect
91
- }
92
- ensure
74
+ end
75
+ ensure
93
76
  begin; @pid.cleanup; rescue ::Exception; end
94
77
  exit!
95
78
  end
@@ -97,42 +80,39 @@ module Daemons
97
80
  end
98
81
  end
99
82
  private :start_with_pidfile
100
-
101
- def start_without_pidfile(applications)
102
- Thread.new { watch(applications) }
83
+
84
+ def start_without_pidfile(application_group)
85
+ Thread.new { watch(application_group) }
103
86
  end
104
87
  private :start_without_pidfile
105
-
106
-
107
- def start(applications)
108
- return if applications.empty?
109
-
88
+
89
+ def start(application_group)
90
+ return if application_group.applications.empty?
91
+
110
92
  if @pid.kind_of?(PidFile)
111
- start_with_pidfile(applications)
93
+ start_with_pidfile(application_group)
112
94
  else
113
- start_without_pidfile(applications)
95
+ start_without_pidfile(application_group)
114
96
  end
115
97
  end
116
-
117
-
98
+
118
99
  def stop
119
100
  begin
120
101
  pid = @pid.pid
121
102
  Process.kill(Application::SIGNAL, pid)
122
- Timeout::timeout(5) {
123
- while Pid.running?(pid)
124
- sleep(0.1)
103
+ Timeout.timeout(5, TimeoutError) do
104
+ while Pid.running?(pid)
105
+ sleep(0.1)
106
+ end
125
107
  end
126
- }
127
108
  rescue ::Exception => e
128
- puts "#{e} #{pid}"
129
- puts "deleting pid-file."
109
+ $stderr.puts "exception while trying to stop monitor process #{pid}: #{e}"
110
+ $stderr.puts "deleting pid-file."
130
111
  end
131
-
132
- # We try to remove the pid-files by ourselves, in case the application
112
+
113
+ # We try to remove the pid-files by ourselves, in case the monitor
133
114
  # didn't clean it up.
134
- begin; @pid.cleanup; rescue ::Exception; end
115
+ begin; @pid.zap; rescue ::Exception; end
135
116
  end
136
-
137
- end
138
- end
117
+ end
118
+ end
data/lib/daemons/pid.rb CHANGED
@@ -1,12 +1,10 @@
1
-
1
+ require 'daemons/exceptions'
2
2
 
3
3
  module Daemons
4
-
5
4
  class Pid
6
-
7
- def Pid.running?(pid)
5
+ def self.running?(pid)
8
6
  return false unless pid
9
-
7
+
10
8
  # Check if process is in existence
11
9
  # The simplest way to do this is to send signal '0'
12
10
  # (which is a single system call) that doesn't actually
@@ -14,95 +12,67 @@ module Daemons
14
12
  begin
15
13
  Process.kill(0, pid)
16
14
  return true
15
+ rescue TimeoutError
16
+ raise
17
17
  rescue Errno::ESRCH
18
18
  return false
19
19
  rescue ::Exception # for example on EPERM (process exists but does not belong to us)
20
20
  return true
21
- #rescue Errno::EPERM
22
- # return false
23
21
  end
24
22
  end
25
-
26
- # def Pid.running?(pid, additional = nil)
27
- # match_pid = Regexp.new("^\\s*#{pid}\\s")
28
- # got_match = false
29
- #
30
- # #ps_all = IO.popen('ps ax') # the correct syntax is without a dash (-) !
31
- # ps_in, ps_out, ps_err = Open3.popen3('ps ax') # the correct syntax is without a dash (-) !
32
- #
33
- # return true unless ps_out.gets
34
- #
35
- # begin
36
- # ps_out.each { |psline|
37
- # next unless psline =~ match_pid
38
- # got_match = true
39
- # got_match = false if additional and psline !~ /#{additional}/
40
- # break
41
- # }
42
- # ensure
43
- # begin; begin; ps_in.close; rescue ::Exception; end; begin; ps_out.close; rescue ::Exception; end; ps_err.close; rescue ::Exception; end
44
- # end
45
- #
46
- # # an alternative would be to use the code below, but I don't know whether this is portable
47
- # # `ps axo pid=`.split.include? pid.to_s
48
- #
49
- # return got_match
50
- # end
51
-
52
-
53
-
23
+
54
24
  # Returns the directory that should be used to write the pid file to
55
25
  # depending on the given mode.
56
- #
57
- # Some modes may require an additionaly hint, others may determine
26
+ #
27
+ # Some modes may require an additionaly hint, others may determine
58
28
  # the directory automatically.
59
29
  #
60
30
  # If no valid directory is found, returns nil.
61
31
  #
62
- def Pid.dir(dir_mode, dir, script)
32
+ def self.dir(dir_mode, dir, script)
63
33
  # nil script parameter is allowed as long as dir_mode is not :script
64
- return nil if dir_mode == :script && script.nil?
65
-
34
+ return nil if dir_mode == :script && script.nil?
35
+
66
36
  case dir_mode
67
37
  when :normal
68
38
  return File.expand_path(dir)
69
39
  when :script
70
- return File.expand_path(File.join(File.dirname(script),dir))
71
- when :system
40
+ return File.expand_path(File.join(File.dirname(script), dir))
41
+ when :system
72
42
  return '/var/run'
73
43
  else
74
- raise Error.new("pid file mode '#{dir_mode}' not implemented")
44
+ fail Error.new("pid file mode '#{dir_mode}' not implemented")
75
45
  end
76
46
  end
77
-
47
+
78
48
  # Initialization method
79
49
  def initialize
80
50
  end
81
-
82
-
51
+
83
52
  # Get method
84
53
  def pid
85
54
  end
86
-
55
+
87
56
  # Set method
88
57
  def pid=(p)
89
58
  end
90
-
59
+
91
60
  # Check whether the process is running
92
61
  def running?
93
- return Pid.running?(pid())
62
+ Pid.running?(pid)
94
63
  end
95
-
64
+
96
65
  # Cleanup method
97
66
  def cleanup
98
67
  end
99
68
 
69
+ # Zap method
70
+ def zap
71
+ end
72
+
100
73
  # Exist? method
101
74
  def exist?
102
75
  true
103
76
  end
104
-
105
- end
106
-
107
-
108
- end
77
+ end
78
+ end