fire_and_forget 0.1.2 → 0.2.0

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 (58) hide show
  1. data/Gemfile.lock +0 -8
  2. data/README.rdoc +75 -14
  3. data/bin/fire_forget +45 -22
  4. data/examples/long_task +27 -16
  5. data/fire_and_forget.gemspec +41 -4
  6. data/lib/fire_and_forget/client.rb +1 -1
  7. data/lib/fire_and_forget/command/fire.rb +23 -4
  8. data/lib/fire_and_forget/command/get_pid.rb +20 -0
  9. data/lib/fire_and_forget/command/set_pid.rb +0 -2
  10. data/lib/fire_and_forget/command/set_status.rb +1 -1
  11. data/lib/fire_and_forget/command.rb +11 -0
  12. data/lib/fire_and_forget/config.rb +3 -8
  13. data/lib/fire_and_forget/daemon.rb +14 -23
  14. data/lib/fire_and_forget/errors.rb +8 -0
  15. data/lib/fire_and_forget/launcher.rb +69 -6
  16. data/lib/fire_and_forget/server.rb +5 -1
  17. data/lib/fire_and_forget/task_description.rb +11 -0
  18. data/lib/fire_and_forget/utilities.rb +4 -4
  19. data/lib/fire_and_forget/version.rb +1 -1
  20. data/lib/fire_and_forget.rb +6 -2
  21. data/test/test_fire_and_forget.rb +59 -26
  22. data/vendor/daemons-1.1.0/LICENSE +29 -0
  23. data/vendor/daemons-1.1.0/README +224 -0
  24. data/vendor/daemons-1.1.0/Rakefile +88 -0
  25. data/vendor/daemons-1.1.0/Releases +152 -0
  26. data/vendor/daemons-1.1.0/TODO +2 -0
  27. data/vendor/daemons-1.1.0/lib/daemons/application.rb +468 -0
  28. data/vendor/daemons-1.1.0/lib/daemons/application_group.rb +194 -0
  29. data/vendor/daemons-1.1.0/lib/daemons/change_privilege.rb +19 -0
  30. data/vendor/daemons-1.1.0/lib/daemons/cmdline.rb +124 -0
  31. data/vendor/daemons-1.1.0/lib/daemons/controller.rb +140 -0
  32. data/vendor/daemons-1.1.0/lib/daemons/daemonize.rb +271 -0
  33. data/vendor/daemons-1.1.0/lib/daemons/etc_extension.rb +12 -0
  34. data/vendor/daemons-1.1.0/lib/daemons/exceptions.rb +28 -0
  35. data/vendor/daemons-1.1.0/lib/daemons/monitor.rb +138 -0
  36. data/vendor/daemons-1.1.0/lib/daemons/pid.rb +109 -0
  37. data/vendor/daemons-1.1.0/lib/daemons/pidfile.rb +116 -0
  38. data/vendor/daemons-1.1.0/lib/daemons/pidmem.rb +19 -0
  39. data/vendor/daemons-1.1.0/lib/daemons.rb +288 -0
  40. data/vendor/daemons-1.1.0/setup.rb +1360 -0
  41. data/vendor/json-1.5.0/COPYING +58 -0
  42. data/vendor/json-1.5.0/GPL +340 -0
  43. data/vendor/json-1.5.0/README +356 -0
  44. data/vendor/json-1.5.0/README-json-jruby.markdown +33 -0
  45. data/vendor/json-1.5.0/Rakefile +397 -0
  46. data/vendor/json-1.5.0/TODO +1 -0
  47. data/vendor/json-1.5.0/VERSION +1 -0
  48. data/vendor/json-1.5.0/lib/json/add/core.rb +147 -0
  49. data/vendor/json-1.5.0/lib/json/add/rails.rb +8 -0
  50. data/vendor/json-1.5.0/lib/json/common.rb +419 -0
  51. data/vendor/json-1.5.0/lib/json/editor.rb +1369 -0
  52. data/vendor/json-1.5.0/lib/json/pure/generator.rb +441 -0
  53. data/vendor/json-1.5.0/lib/json/pure/parser.rb +320 -0
  54. data/vendor/json-1.5.0/lib/json/pure.rb +15 -0
  55. data/vendor/json-1.5.0/lib/json/version.rb +8 -0
  56. data/vendor/json-1.5.0/lib/json.rb +10 -0
  57. metadata +41 -4
  58. data/lib/fire_and_forget/task.rb +0 -11
@@ -0,0 +1,194 @@
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
+ return if @monitor
145
+
146
+ if options[:monitor]
147
+ @monitor = Monitor.new(an_app)
148
+
149
+ @monitor.start(@applications)
150
+ end
151
+ end
152
+
153
+ def start_all
154
+ @monitor.stop if @monitor
155
+ @monitor = nil
156
+
157
+ @applications.each {|a|
158
+ fork {
159
+ a.start
160
+ }
161
+ }
162
+ end
163
+
164
+ def stop_all(no_wait = false)
165
+ @monitor.stop if @monitor
166
+
167
+ threads = []
168
+
169
+ @applications.each {|a|
170
+ threads << Thread.new do
171
+ a.stop(no_wait)
172
+ end
173
+ }
174
+
175
+ threads.each {|t| t.join}
176
+ end
177
+
178
+ def reload_all
179
+ @applications.each {|a| a.reload}
180
+ end
181
+
182
+ def zap_all
183
+ @monitor.stop if @monitor
184
+
185
+ @applications.each {|a| a.zap}
186
+ end
187
+
188
+ def show_status
189
+ @applications.each {|a| a.show_status}
190
+ end
191
+
192
+ end
193
+
194
+ 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,124 @@
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 = "Usage: example.rb [options]"
14
+ opts.banner = ""
15
+
16
+ # Boolean switch.
17
+ # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
18
+ # @options[:verbose] = v
19
+ # end
20
+
21
+ opts.on("-t", "--ontop", "Stay on top (does not daemonize)") do |t|
22
+ @options[:ontop] = t
23
+ end
24
+
25
+ opts.on("-f", "--force", "Force operation") do |t|
26
+ @options[:force] = t
27
+ end
28
+
29
+ opts.on("-n", "--no_wait", "Do not wait for processes to stop") do |t|
30
+ @options[:no_wait] = t
31
+ end
32
+
33
+ #opts.separator ""
34
+ #opts.separator "Specific options:"
35
+
36
+
37
+ opts.separator ""
38
+ opts.separator "Common options:"
39
+
40
+ # No argument, shows at tail. This will print an options summary.
41
+ # Try it and see!
42
+ opts.on_tail("-h", "--help", "Show this message") do
43
+ #puts opts
44
+ #@usage =
45
+ controller.print_usage()
46
+
47
+ exit
48
+ end
49
+
50
+ # Another typical switch to print the version.
51
+ opts.on_tail("--version", "Show version") do
52
+ puts "daemons version #{Daemons::VERSION}"
53
+ exit
54
+ end
55
+ end
56
+
57
+ begin
58
+ @usage = @opts.to_s
59
+ rescue ::Exception # work around a bug in ruby 1.9
60
+ @usage = <<END
61
+ -t, --ontop Stay on top (does not daemonize)
62
+ -f, --force Force operation
63
+ -n, --no_wait Do not wait for processes to stop
64
+
65
+ Common options:
66
+ -h, --help Show this message
67
+ --version Show version
68
+ END
69
+ end
70
+ end
71
+
72
+
73
+ #
74
+ # Return a hash describing the options.
75
+ #
76
+ def parse(args)
77
+ # The options specified on the command line will be collected in *options*.
78
+ # We set default values here.
79
+ #options = {}
80
+
81
+
82
+ ##pp args
83
+ @opts.parse(args)
84
+
85
+ return @options
86
+ end
87
+
88
+ end
89
+
90
+
91
+ class Controller
92
+
93
+ def print_usage
94
+ puts "Usage: #{@app_name} <command> <options> -- <application options>"
95
+ puts
96
+ puts "* where <command> is one of:"
97
+ puts " start start an instance of the application"
98
+ puts " stop stop all instances of the application"
99
+ puts " restart stop all instances and restart them afterwards"
100
+ puts " reload send a SIGHUP to all instances of the application"
101
+ puts " run start the application and stay on top"
102
+ puts " zap set the application to a stopped state"
103
+ puts " status show status (PID) of application instances"
104
+ puts
105
+ puts "* and where <options> may contain several of the following:"
106
+
107
+ puts @optparse.usage
108
+ end
109
+
110
+ def catch_exceptions(&block)
111
+ begin
112
+ block.call
113
+ rescue CmdException, OptionParser::ParseError => e
114
+ puts "ERROR: #{e.to_s}"
115
+ puts
116
+ print_usage()
117
+ rescue RuntimeException => e
118
+ puts "ERROR: #{e.to_s}"
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ 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