daemons 1.1.9 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
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