blue-daemons 1.1.11

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