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,200 @@
1
+
2
+ module Daemons
3
+ class ApplicationGroup
4
+
5
+ attr_reader :app_name
6
+ attr_reader :script
7
+
8
+ attr_reader :monitor
9
+
10
+ #attr_reader :controller
11
+
12
+ attr_reader :options
13
+
14
+ attr_reader :applications
15
+
16
+ attr_accessor :controller_argv
17
+ attr_accessor :app_argv
18
+
19
+ attr_accessor :dir_mode
20
+ attr_accessor :dir
21
+
22
+ # true if the application is supposed to run in multiple instances
23
+ attr_reader :multiple
24
+
25
+
26
+ def initialize(app_name, options = {})
27
+ @app_name = app_name
28
+ @options = options
29
+
30
+ if options[:script]
31
+ @script = File.expand_path(options[:script])
32
+ end
33
+
34
+ #@controller = controller
35
+ @monitor = nil
36
+
37
+ #options = controller.options
38
+
39
+ @multiple = options[:multiple] || false
40
+
41
+ @dir_mode = options[:dir_mode] || :script
42
+ @dir = options[:dir] || ''
43
+
44
+ @keep_pid_files = options[:keep_pid_files] || false
45
+ @no_pidfiles = options[:no_pidfiles] || false
46
+
47
+ #@applications = find_applications(pidfile_dir())
48
+ @applications = []
49
+ end
50
+
51
+ # Setup the application group.
52
+ # Currently this functions calls <tt>find_applications</tt> which finds
53
+ # all running instances of the application and populates the application array.
54
+ #
55
+ def setup
56
+ @applications = find_applications(pidfile_dir())
57
+ end
58
+
59
+ def pidfile_dir
60
+ PidFile.dir(@dir_mode, @dir, script)
61
+ end
62
+
63
+ def find_applications(dir)
64
+ if @no_pidfiles
65
+ return find_applications_by_app_name(app_name)
66
+ else
67
+ return find_applications_by_pidfiles(dir)
68
+ end
69
+ end
70
+
71
+ # TODO: identifiy the monitor process
72
+ def find_applications_by_app_name(app_name)
73
+ pids = []
74
+
75
+ begin
76
+ x = `ps auxw | grep -v grep | awk '{print $2, $11, $12}' | grep #{app_name}`
77
+ if x && x.chomp!
78
+ processes = x.split(/\n/).compact
79
+ processes = processes.delete_if do |p|
80
+ pid, name, add = p.split(/\s/)
81
+ # We want to make sure that the first part of the process name matches
82
+ # so that app_name matches app_name_22
83
+
84
+ app_name != name[0..(app_name.length - 1)] and not add.include?(app_name)
85
+ end
86
+ pids = processes.map {|p| p.split(/\s/)[0].to_i}
87
+ end
88
+ rescue ::Exception
89
+ end
90
+
91
+ return pids.map {|f|
92
+ app = Application.new(self, {}, PidMem.existing(f))
93
+ setup_app(app)
94
+ app
95
+ }
96
+ end
97
+
98
+ def find_applications_by_pidfiles(dir)
99
+ pid_files = PidFile.find_files(dir, app_name, ! @keep_pid_files)
100
+
101
+ #pp pid_files
102
+
103
+ @monitor = Monitor.find(dir, app_name + '_monitor')
104
+
105
+ pid_files.reject! {|f| f =~ /_monitor.pid$/}
106
+
107
+ return pid_files.map {|f|
108
+ app = Application.new(self, {}, PidFile.existing(f))
109
+ setup_app(app)
110
+ app
111
+ }
112
+ end
113
+
114
+ def new_application(add_options = {})
115
+ if @applications.size > 0 and not @multiple
116
+ if options[:force]
117
+ @applications.delete_if {|a|
118
+ unless a.running?
119
+ a.zap
120
+ true
121
+ end
122
+ }
123
+ end
124
+
125
+ raise RuntimeException.new('there is already one or more instance(s) of the program running') unless @applications.empty?
126
+ end
127
+
128
+ app = Application.new(self, add_options)
129
+
130
+ setup_app(app)
131
+
132
+ @applications << app
133
+
134
+ return app
135
+ end
136
+
137
+ def setup_app(app)
138
+ app.controller_argv = @controller_argv
139
+ app.app_argv = @app_argv
140
+ end
141
+ private :setup_app
142
+
143
+ def create_monitor(an_app)
144
+ if @monitor and options[:monitor]
145
+ @monitor.stop
146
+ @monitor = nil
147
+ end
148
+
149
+ if options[:monitor]
150
+ @monitor = Monitor.new(an_app)
151
+ @monitor.start(self)
152
+ end
153
+ end
154
+
155
+ def start_all
156
+ @monitor.stop if @monitor
157
+ @monitor = nil
158
+
159
+ @applications.each {|a|
160
+ fork {
161
+ a.start
162
+ }
163
+ }
164
+ end
165
+
166
+ def stop_all(no_wait = false)
167
+ if @monitor
168
+ @monitor.stop
169
+ @monitor = nil
170
+ setup
171
+ end
172
+
173
+ threads = []
174
+
175
+ @applications.each {|a|
176
+ threads << Thread.new do
177
+ a.stop(no_wait)
178
+ end
179
+ }
180
+
181
+ threads.each {|t| t.join}
182
+ end
183
+
184
+ def reload_all
185
+ @applications.each {|a| a.reload}
186
+ end
187
+
188
+ def zap_all
189
+ @monitor.stop if @monitor
190
+
191
+ @applications.each {|a| a.zap}
192
+ end
193
+
194
+ def show_status
195
+ @applications.each {|a| a.show_status}
196
+ end
197
+
198
+ end
199
+
200
+ end
@@ -0,0 +1,19 @@
1
+ require 'daemons/etc_extension'
2
+
3
+ class CurrentProcess
4
+ def self.change_privilege(user, group=user)
5
+ puts "Changing process privilege to #{user}:#{group}"
6
+
7
+ uid, gid = Process.euid, Process.egid
8
+ target_uid = Etc.getpwnam(user).uid
9
+ target_gid = Etc.getgrnam(group).gid
10
+
11
+ if uid != target_uid || gid != target_gid
12
+ Process.initgroups(user, target_gid)
13
+ Process::GID.change_privilege(target_gid)
14
+ Process::UID.change_privilege(target_uid)
15
+ end
16
+ rescue Errno::EPERM => e
17
+ raise "Couldn't change user and group to #{user}:#{group}: #{e}"
18
+ end
19
+ end
@@ -0,0 +1,121 @@
1
+
2
+ module Daemons
3
+
4
+ class Optparse
5
+
6
+ attr_reader :usage
7
+
8
+ def initialize(controller)
9
+ @controller = controller
10
+ @options = {}
11
+
12
+ @opts = OptionParser.new do |opts|
13
+ opts.banner = ""
14
+
15
+ # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
16
+ # @options[:verbose] = v
17
+ # end
18
+
19
+ opts.on("-t", "--ontop", "Stay on top (does not daemonize)") do |t|
20
+ @options[:ontop] = t
21
+ end
22
+
23
+ opts.on("-f", "--force", "Force operation") do |t|
24
+ @options[:force] = t
25
+ end
26
+
27
+ opts.on("-n", "--no_wait", "Do not wait for processes to stop") do |t|
28
+ @options[:no_wait] = t
29
+ end
30
+
31
+ #opts.separator ""
32
+ #opts.separator "Specific options:"
33
+
34
+
35
+ opts.separator ""
36
+ opts.separator "Common options:"
37
+
38
+ # No argument, shows at tail. This will print an options summary
39
+ opts.on_tail("-h", "--help", "Show this message") do
40
+ #puts opts
41
+ #@usage =
42
+ controller.print_usage()
43
+
44
+ exit
45
+ end
46
+
47
+ # Switch to print the version.
48
+ opts.on_tail("--version", "Show version") do
49
+ puts "daemons version #{Daemons::VERSION}"
50
+ exit
51
+ end
52
+ end
53
+
54
+ begin
55
+ @usage = @opts.to_s
56
+ rescue ::Exception # work around a bug in ruby 1.9
57
+ @usage = <<END
58
+ -t, --ontop Stay on top (does not daemonize)
59
+ -f, --force Force operation
60
+ -n, --no_wait Do not wait for processes to stop
61
+
62
+ Common options:
63
+ -h, --help Show this message
64
+ --version Show version
65
+ END
66
+ end
67
+ end
68
+
69
+
70
+ #
71
+ # Return a hash describing the options.
72
+ #
73
+ def parse(args)
74
+ # The options specified on the command line will be collected in *options*.
75
+ # We set default values here.
76
+ #options = {}
77
+
78
+
79
+ ##pp args
80
+ @opts.parse(args)
81
+
82
+ return @options
83
+ end
84
+
85
+ end
86
+
87
+
88
+ class Controller
89
+
90
+ def print_usage
91
+ puts "Usage: #{@app_name} <command> <options> -- <application options>"
92
+ puts
93
+ puts "* where <command> is one of:"
94
+ puts " start start an instance of the application"
95
+ puts " stop stop all instances of the application"
96
+ puts " restart stop all instances and restart them afterwards"
97
+ puts " reload send a SIGHUP to all instances of the application"
98
+ puts " run start the application and stay on top"
99
+ puts " zap set the application to a stopped state"
100
+ puts " status show status (PID) of application instances"
101
+ puts
102
+ puts "* and where <options> may contain several of the following:"
103
+
104
+ puts @optparse.usage
105
+ end
106
+
107
+ def catch_exceptions(&block)
108
+ begin
109
+ block.call
110
+ rescue CmdException, OptionParser::ParseError => e
111
+ puts "ERROR: #{e.to_s}"
112
+ puts
113
+ print_usage()
114
+ rescue RuntimeException => e
115
+ puts "ERROR: #{e.to_s}"
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,140 @@
1
+
2
+ module Daemons
3
+ class Controller
4
+
5
+ attr_reader :app_name
6
+
7
+ attr_reader :group
8
+
9
+ attr_reader :options
10
+
11
+
12
+ COMMANDS = [
13
+ 'start',
14
+ 'stop',
15
+ 'restart',
16
+ 'run',
17
+ 'zap',
18
+ 'reload',
19
+ 'status'
20
+ ]
21
+
22
+ def initialize(options = {}, argv = [])
23
+ @options = options
24
+ @argv = argv
25
+
26
+ # Allow an app_name to be specified. If not specified use the
27
+ # basename of the script.
28
+ @app_name = options[:app_name]
29
+
30
+ if options[:script]
31
+ @script = File.expand_path(options[:script])
32
+
33
+ @app_name ||= File.split(@script)[1]
34
+ end
35
+
36
+ @app_name ||= 'unknown_application'
37
+
38
+ @command, @controller_part, @app_part = Controller.split_argv(argv)
39
+
40
+ #@options[:dir_mode] ||= :script
41
+
42
+ @optparse = Optparse.new(self)
43
+ end
44
+
45
+
46
+ # This function is used to do a final update of the options passed to the application
47
+ # before they are really used.
48
+ #
49
+ # Note that this function should only update <tt>@options</tt> and no other variables.
50
+ #
51
+ def setup_options
52
+ #@options[:ontop] ||= true
53
+ end
54
+
55
+ def run
56
+ @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
57
+
58
+ setup_options()
59
+
60
+ #pp @options
61
+
62
+ @group = ApplicationGroup.new(@app_name, @options)
63
+ @group.controller_argv = @controller_part
64
+ @group.app_argv = @app_part
65
+
66
+ @group.setup
67
+
68
+ case @command
69
+ when 'start'
70
+ @group.new_application.start
71
+ when 'run'
72
+ @options[:ontop] ||= true
73
+ @group.new_application.start
74
+ when 'stop'
75
+ @group.stop_all(@options[:no_wait])
76
+ when 'restart'
77
+ unless @group.applications.empty?
78
+ @group.stop_all
79
+ sleep(1)
80
+ @group.start_all
81
+ else
82
+ puts "Warning: no instances running. Starting..."
83
+ @group.new_application.start
84
+ end
85
+ when 'reload'
86
+ @group.reload_all
87
+ when 'zap'
88
+ @group.zap_all
89
+ when 'status'
90
+ unless @group.applications.empty?
91
+ @group.show_status
92
+ else
93
+ puts "#{@group.app_name}: no instances running"
94
+ end
95
+ 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')
101
+ else
102
+ raise Error.new("command '#{@command}' not implemented")
103
+ end
104
+ end
105
+
106
+
107
+ # Split an _argv_ array.
108
+ # +argv+ is assumed to be in the following format:
109
+ # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
110
+ #
111
+ # <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
112
+ #
113
+ # *Returns*: the command as a string, the controller options as an array, the appliation options
114
+ # as an array
115
+ #
116
+ def Controller.split_argv(argv)
117
+ argv = argv.dup
118
+
119
+ command = nil
120
+ controller_part = []
121
+ app_part = []
122
+
123
+ if COMMANDS.include? argv[0]
124
+ command = argv.shift
125
+ end
126
+
127
+ if i = argv.index('--')
128
+ # Handle the case where no controller options are given, just
129
+ # options after "--" as well (i == 0)
130
+ controller_part = (i == 0 ? [] : argv[0..i-1])
131
+ app_part = argv[i+1..-1]
132
+ else
133
+ controller_part = argv[0..-1]
134
+ end
135
+
136
+ return command, controller_part, app_part
137
+ end
138
+ end
139
+
140
+ end