blue-daemons 1.1.11

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