daemons 1.1.9 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -1
  3. data/README.md +206 -0
  4. data/Releases +17 -0
  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_multiple.rb +0 -1
  16. data/examples/run/ctrl_ontop.rb +0 -1
  17. data/examples/run/ctrl_optionparser.rb +4 -6
  18. data/examples/run/ctrl_proc.rb +8 -9
  19. data/examples/run/ctrl_proc_multiple.rb +4 -6
  20. data/examples/run/ctrl_proc_rand.rb +2 -4
  21. data/examples/run/ctrl_proc_simple.rb +0 -1
  22. data/examples/run/myserver.rb +0 -1
  23. data/examples/run/myserver_crashing.rb +5 -5
  24. data/examples/run/myserver_exiting.rb +2 -2
  25. data/examples/run/myserver_hanging.rb +4 -5
  26. data/examples/run/myserver_slowstop.rb +5 -6
  27. data/lib/daemons.rb +66 -68
  28. data/lib/daemons/application.rb +171 -188
  29. data/lib/daemons/application_group.rb +99 -92
  30. data/lib/daemons/change_privilege.rb +3 -3
  31. data/lib/daemons/cmdline.rb +43 -54
  32. data/lib/daemons/controller.rb +36 -53
  33. data/lib/daemons/daemonize.rb +54 -64
  34. data/lib/daemons/etc_extension.rb +3 -2
  35. data/lib/daemons/exceptions.rb +10 -11
  36. data/lib/daemons/monitor.rb +60 -62
  37. data/lib/daemons/pid.rb +24 -56
  38. data/lib/daemons/pidfile.rb +38 -40
  39. data/lib/daemons/pidmem.rb +5 -9
  40. data/lib/daemons/version.rb +3 -0
  41. metadata +45 -45
  42. data/README +0 -214
  43. data/Rakefile +0 -90
  44. data/TODO +0 -2
  45. data/setup.rb +0 -1360
@@ -1,70 +1,57 @@
1
1
 
2
2
  module Daemons
3
3
  class Controller
4
-
5
4
  attr_reader :app_name
6
-
5
+
7
6
  attr_reader :group
8
-
7
+
9
8
  attr_reader :options
10
-
11
-
12
- COMMANDS = [
13
- 'start',
14
- 'stop',
15
- 'restart',
16
- 'run',
17
- 'zap',
18
- 'reload',
19
- 'status'
20
- ]
21
-
9
+
10
+ COMMANDS = %w(start stop restart run zap reload status)
11
+
22
12
  def initialize(options = {}, argv = [])
23
13
  @options = options
24
14
  @argv = argv
25
-
15
+
26
16
  # Allow an app_name to be specified. If not specified use the
27
17
  # basename of the script.
28
18
  @app_name = options[:app_name]
29
-
19
+
30
20
  if options[:script]
31
21
  @script = File.expand_path(options[:script])
32
-
22
+
33
23
  @app_name ||= File.split(@script)[1]
34
24
  end
35
-
25
+
36
26
  @app_name ||= 'unknown_application'
37
-
27
+
38
28
  @command, @controller_part, @app_part = Controller.split_argv(argv)
39
-
40
- #@options[:dir_mode] ||= :script
41
-
29
+
30
+ # @options[:dir_mode] ||= :script
31
+
42
32
  @optparse = Optparse.new(self)
43
33
  end
44
-
45
-
34
+
46
35
  # This function is used to do a final update of the options passed to the application
47
36
  # before they are really used.
48
37
  #
49
38
  # Note that this function should only update <tt>@options</tt> and no other variables.
50
39
  #
51
40
  def setup_options
52
- #@options[:ontop] ||= true
41
+ # @options[:ontop] ||= true
53
42
  end
54
-
43
+
55
44
  def run
56
- @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
57
-
58
- setup_options()
59
-
60
- #pp @options
45
+ @options.update @optparse.parse(@controller_part).delete_if { |k, v| !v }
46
+
47
+ setup_options
61
48
 
62
49
  @group = ApplicationGroup.new(@app_name, @options)
63
50
  @group.controller_argv = @controller_part
64
51
  @group.app_argv = @app_part
65
-
52
+
66
53
  @group.setup
67
-
54
+
68
55
  case @command
69
56
  when 'start'
70
57
  @group.new_application.start
@@ -79,7 +66,7 @@ module Daemons
79
66
  sleep(1)
80
67
  @group.start_all
81
68
  else
82
- puts "Warning: no instances running. Starting..."
69
+ $stderr.puts "#{@group.app_name}: warning: no instances running. Starting..."
83
70
  @group.new_application.start
84
71
  end
85
72
  when 'reload'
@@ -89,21 +76,18 @@ module Daemons
89
76
  when 'status'
90
77
  unless @group.applications.empty?
91
78
  @group.show_status
79
+ exit 3 if not @group.running? # exit with status 3 to indicate that no apps are running
92
80
  else
93
- puts "#{@group.app_name}: no instances running"
81
+ $stderr.puts "#{@group.app_name}: no instances running"
82
+ exit 3 # exit with status 3 to indicate that no apps are running
94
83
  end
95
84
  when nil
96
- raise CmdException.new('no command given')
97
- #puts "ERROR: No command given"; puts
98
-
99
- #print_usage()
100
- #raise('usage function not implemented')
85
+ fail CmdException.new('no command given')
101
86
  else
102
- raise Error.new("command '#{@command}' not implemented")
87
+ fail Error.new("command '#{@command}' not implemented")
103
88
  end
104
89
  end
105
-
106
-
90
+
107
91
  # Split an _argv_ array.
108
92
  # +argv+ is assumed to be in the following format:
109
93
  # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
@@ -113,28 +97,27 @@ module Daemons
113
97
  # *Returns*: the command as a string, the controller options as an array, the appliation options
114
98
  # as an array
115
99
  #
116
- def Controller.split_argv(argv)
100
+ def self.split_argv(argv)
117
101
  argv = argv.dup
118
-
102
+
119
103
  command = nil
120
104
  controller_part = []
121
105
  app_part = []
122
-
106
+
123
107
  if COMMANDS.include? argv[0]
124
108
  command = argv.shift
125
109
  end
126
-
110
+
127
111
  if i = argv.index('--')
128
112
  # Handle the case where no controller options are given, just
129
113
  # options after "--" as well (i == 0)
130
- controller_part = (i == 0 ? [] : argv[0..i-1])
131
- app_part = argv[i+1..-1]
114
+ controller_part = (i == 0 ? [] : argv[0..i - 1])
115
+ app_part = argv[i + 1..-1]
132
116
  else
133
117
  controller_part = argv[0..-1]
134
118
  end
135
-
136
- return command, controller_part, app_part
119
+
120
+ [command, controller_part, app_part]
137
121
  end
138
122
  end
139
-
140
123
  end
@@ -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,143 @@ 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
61
  unless sess_id = Process.setsid
60
- raise Daemons.RuntimeException.new('cannot detach from controlling terminal')
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
103
  exit if pid = safefork
103
-
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
123
  # Part I: close all IO objects (except for STDIN/STDOUT/STDERR)
124
124
  ObjectSpace.each_object(IO) do |io|
125
125
  unless [STDIN, STDOUT, STDERR].include?(io)
126
- begin
127
- unless io.closed?
128
- io.close
129
- end
130
- rescue ::Exception
131
- end
126
+ io.close rescue nil
132
127
  end
133
128
  end
134
-
129
+
135
130
  # Make sure all input/output streams are closed
136
131
  # 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
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
-
137
+
146
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
-
141
+ begin; STDIN.reopen '/dev/null'; rescue ::Exception; end
142
+
151
143
  if logfile_name
152
144
  begin
153
- STDOUT.reopen logfile_name, "a"
145
+ STDOUT.reopen logfile_name, 'a'
154
146
  File.chmod(0644, logfile_name)
155
147
  STDOUT.sync = true
156
148
  rescue ::Exception
157
- begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
149
+ begin; STDOUT.reopen '/dev/null'; rescue ::Exception; end
158
150
  end
159
151
  else
160
- begin; STDOUT.reopen "/dev/null"; rescue ::Exception; end
152
+ begin; STDOUT.reopen '/dev/null'; rescue ::Exception; end
161
153
  end
162
-
154
+
163
155
  begin; STDERR.reopen STDOUT; rescue ::Exception; end
164
156
  STDERR.sync = true
165
157
  end
166
158
  module_function :redirect_io
167
-
168
-
169
159
  end
@@ -2,11 +2,12 @@ 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 }
5
+ Etc.group { |e| return e.name if gid == e.gid }
6
6
  nil
7
7
  end
8
+
8
9
  def username(uid)
9
- Etc.passwd {|e| return e.name if uid == e.uid }
10
+ Etc.passwd { |e| return e.name if uid == e.uid }
10
11
  nil
11
12
  end
12
13
  end
@@ -1,28 +1,27 @@
1
1
 
2
2
  module Daemons
3
-
4
3
  class Exception < ::RuntimeError
5
4
  end
6
-
5
+
7
6
  class RuntimeException < Exception
8
7
  end
9
-
8
+
10
9
  class CmdException < Exception
11
10
  end
12
-
11
+
13
12
  class Error < Exception
14
13
  end
15
-
14
+
16
15
  class SystemError < Error
17
-
18
16
  attr_reader :system_error
19
-
17
+
20
18
  def initialize(msg, system_error)
21
19
  super(msg)
22
-
20
+
23
21
  @system_error = system_error
24
22
  end
25
-
26
23
  end
27
-
28
- end
24
+
25
+ class TimeoutError < Error
26
+ end
27
+ end
@@ -1,77 +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
-
27
+
30
28
  def initialize(an_app)
31
29
  @app = an_app
32
30
  @app_name = an_app.group.app_name + '_monitor'
33
-
31
+
34
32
  if an_app.pidfile_dir
35
33
  @pid = PidFile.new(an_app.pidfile_dir, @app_name, false)
36
34
  else
37
35
  @pid = PidMem.new
38
36
  end
39
37
  end
40
-
41
- def watch(applications)
42
- sleep(30)
43
-
38
+
39
+ def watch(application_group)
40
+ sleep(5)
41
+
44
42
  loop do
45
- applications.each {|a|
46
- sleep(10)
47
-
43
+ application_group.applications.each do |a|
48
44
  unless a.running?
49
45
  a.zap!
50
-
51
- Process.detach(fork { a.start })
52
-
53
- sleep(10)
46
+
47
+ sleep(1)
48
+
49
+ Process.detach(fork { a.start(restart = true) })
50
+
51
+ sleep(5)
52
+
53
+ # application_group.setup
54
54
  end
55
- }
56
-
57
- sleep(30)
55
+ end
56
+
57
+ # sleep(5)
58
+ # application_group.setup
59
+ # sleep(30)
58
60
  end
59
61
  end
60
62
  private :watch
61
-
62
-
63
- def start_with_pidfile(applications)
63
+
64
+ def start_with_pidfile(application_group)
64
65
  fork do
65
66
  Daemonize.daemonize(nil, @app_name)
66
-
67
- begin
67
+
68
+ begin
68
69
  @pid.pid = Process.pid
69
-
70
+
70
71
  # at_exit {
71
72
  # begin; @pid.cleanup; rescue ::Exception; end
72
73
  # }
73
-
74
- # This part is needed to remove the pid-file if the application is killed by
74
+
75
+ # This part is needed to remove the pid-file if the application is killed by
75
76
  # daemons or manually by the user.
76
77
  # Note that the applications is not supposed to overwrite the signal handler for
77
78
  # 'TERM'.
@@ -80,16 +81,16 @@ module Daemons
80
81
  # begin; @pid.cleanup; rescue ::Exception; end
81
82
  # exit
82
83
  # }
83
-
84
- watch(applications)
84
+
85
+ watch(application_group)
85
86
  rescue ::Exception => e
86
87
  begin
87
- File.open(@app.logfile, 'a') {|f|
88
+ File.open(@app.logfile, 'a') do |f|
88
89
  f.puts Time.now
89
90
  f.puts e
90
91
  f.puts e.backtrace.inspect
91
- }
92
- ensure
92
+ end
93
+ ensure
93
94
  begin; @pid.cleanup; rescue ::Exception; end
94
95
  exit!
95
96
  end
@@ -97,42 +98,39 @@ module Daemons
97
98
  end
98
99
  end
99
100
  private :start_with_pidfile
100
-
101
- def start_without_pidfile(applications)
102
- Thread.new { watch(applications) }
101
+
102
+ def start_without_pidfile(application_group)
103
+ Thread.new { watch(application_group) }
103
104
  end
104
105
  private :start_without_pidfile
105
-
106
-
107
- def start(applications)
108
- return if applications.empty?
109
-
106
+
107
+ def start(application_group)
108
+ return if application_group.applications.empty?
109
+
110
110
  if @pid.kind_of?(PidFile)
111
- start_with_pidfile(applications)
111
+ start_with_pidfile(application_group)
112
112
  else
113
- start_without_pidfile(applications)
113
+ start_without_pidfile(application_group)
114
114
  end
115
115
  end
116
-
117
-
116
+
118
117
  def stop
119
118
  begin
120
119
  pid = @pid.pid
121
120
  Process.kill(Application::SIGNAL, pid)
122
- Timeout::timeout(5) {
123
- while Pid.running?(pid)
124
- sleep(0.1)
121
+ Timeout.timeout(5, TimeoutError) do
122
+ while Pid.running?(pid)
123
+ sleep(0.1)
124
+ end
125
125
  end
126
- }
127
126
  rescue ::Exception => e
128
127
  puts "#{e} #{pid}"
129
- puts "deleting pid-file."
128
+ puts 'deleting pid-file.'
130
129
  end
131
-
130
+
132
131
  # We try to remove the pid-files by ourselves, in case the application
133
132
  # didn't clean it up.
134
133
  begin; @pid.cleanup; rescue ::Exception; end
135
134
  end
136
-
137
- end
138
- end
135
+ end
136
+ end