daemons 1.1.9 → 1.2.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 (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