daemons 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,91 @@
1
+ = Daemons Version 0.0.1
2
+
3
+ (See Releases for release-specific information)
4
+
5
+ == What is Daemons?
6
+
7
+ Daemons provides an easy way to wrap existing ruby scripts (for example a self-written server)
8
+ to be <i>run as a daemon</i> and to be <i>controlled by simple start/stop/restart commands</i>.
9
+
10
+ Daemons includes the <tt>daemonize.rb</tt> script written by <i>Travis Whitton</i> to do the daemonization
11
+ process.
12
+
13
+ == Basic Usage
14
+
15
+ Layout: suppose you have your self-written server <tt>myserver.rb</tt>:
16
+
17
+ # this is myserver.rb
18
+ # it does nothing really useful at the moment
19
+
20
+ loop do
21
+ sleep(5)
22
+ end
23
+
24
+ To use <tt>myserver.rb</tt> in a production environment, you need to be able to
25
+ run <tt>myserver.rb</tt> in the _background_ (this means detach it from the console, fork it
26
+ in the background, release any directories and file descriptors).
27
+
28
+ Just create <tt>myserver_control.rb</tt> like this:
29
+
30
+ # this is myserver_control.rb
31
+
32
+ require 'rubygems' # if you use RubyGems
33
+ require 'daemons'
34
+
35
+ Daemons.run('myserver.rb')
36
+
37
+ And use it like this from the console:
38
+
39
+ $ ruby myserver_control.rb start
40
+ (myserver.rb is now running in the background)
41
+ $ ruby myserver_control.rb restart
42
+ (...)
43
+ $ ruby myserver_control.rb stop
44
+
45
+ For testing purposes you can even run <tt>myserver.rb</tt> <i>without forking</i> in the background:
46
+
47
+ $ ruby myserver_control.rb run
48
+
49
+ An additional nice feature of Daemons is that you can pass <i>additional arguments</i> to the script that
50
+ should be daemonized by seperating them by two _hyphens_:
51
+
52
+ $ ruby myserver_control.rb start -- --file=anyfile --a_switch another_argument
53
+
54
+ For further documentation, refer to the module documentation of Daemons.
55
+
56
+
57
+ == Download and Installation
58
+
59
+ *Download*: just go to http://rubyforge.org/projects/daemons/
60
+
61
+ Installation *with* RubyGems:
62
+ $ su
63
+ # gem install daemons
64
+
65
+ Installation *without* RubyGems:
66
+ $ tar xfz daemons-x.x.x.tar.gz
67
+ $ cd daemons-x.x.x
68
+ $ su
69
+ # ruby setup.rb
70
+
71
+ == Documentation
72
+
73
+ For further documentation, refer to the module documentation of Daemons (click on Daemons).
74
+
75
+ The RDoc documentation is also online at http://daemons.rubyforge.org
76
+
77
+
78
+ == Author
79
+
80
+ Written in 2005 by Thomas Uehlinger <mailto:th.uehlinger@gmx.ch>
81
+
82
+ == License
83
+
84
+ The Daemons script is copywrited free software by Thomas Uehlinger
85
+ <mailto:th.uehlinger@gmx.ch>. You can redistribute it under the terms specified in
86
+ the COPYING file of the Ruby distribution.
87
+
88
+
89
+ == Feedback and other resources
90
+
91
+ At http://rubyforge.org/projects/daemons.
data/Rakefile ADDED
@@ -0,0 +1,71 @@
1
+ require 'rubygems'
2
+ Gem::manage_gems
3
+
4
+ require 'rake/gempackagetask'
5
+ require 'rake/testtask'
6
+ require 'rake/packagetask'
7
+ require 'rake/rdoctask'
8
+
9
+ $LOAD_PATH << './lib'
10
+ require 'daemons'
11
+
12
+
13
+ PKG_NAME = "daemons"
14
+
15
+ PKG_FILES = FileList[
16
+ "Rakefile", "Releases", "TODO", "README",
17
+ "setup.rb",
18
+ "lib/**/*.rb",
19
+ "test/**/*"
20
+ ]
21
+ PKG_FILES.exclude(%r(^test/tmp/.+))
22
+
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.name = PKG_NAME
26
+ #s.version = "0.0.1"
27
+ s.version = Daemons::VERSION
28
+ s.author = "Thomas Uehlinger"
29
+ s.email = "th.uehlinger@gmx.ch"
30
+ s.homepage = "http://daemons.rubyforge.org"
31
+ s.platform = Gem::Platform::RUBY
32
+ s.summary = "A toolkit to convert your script to a controllable daemon"
33
+ #s.files = FileList["{test,lib}/**/*"].exclude("rdoc").to_a
34
+ s.files = PKG_FILES
35
+ s.require_path = "lib"
36
+ s.autorequire = "daemons"
37
+ s.test_file = "test/tc_main.rb"
38
+ s.has_rdoc = true
39
+ s.extra_rdoc_files = ["README", "Releases", "TODO"]
40
+ end
41
+
42
+ Rake::GemPackageTask.new(spec) do |pkg|
43
+ pkg.need_tar = true
44
+ end
45
+
46
+
47
+ #Rake::PackageTask.new("package") do |p|
48
+ # p.name = PKG_NAME
49
+ # p.version = Daemons::VERSION
50
+ # p.need_tar = true
51
+ # p.need_zip = true
52
+ # p.package_files = PKG_FILES
53
+ #end
54
+
55
+
56
+ task :default => [:test]
57
+
58
+ Rake::TestTask.new(:test) do |t|
59
+ t.test_files = FileList['test/tc_*.rb']
60
+ end
61
+
62
+
63
+ desc "Create the RDOC html files"
64
+ rd = Rake::RDocTask.new("rdoc") { |rdoc|
65
+ rdoc.rdoc_dir = 'html'
66
+ rdoc.title = "Daemons"
67
+ rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
68
+ rdoc.rdoc_files.include('README', 'TODO', 'Releases')
69
+ rdoc.rdoc_files.include('lib/**/*.rb')
70
+ rdoc.rdoc_files.include('test/**/*.rb')
71
+ }
data/Releases ADDED
@@ -0,0 +1,6 @@
1
+ = Daemons Release History
2
+
3
+ == Release 0.0.1: Feb 8, 2005
4
+
5
+ * Initial release
6
+
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+ * write the README (2005-02-07)
2
+ * write some real tests (2005-02-08)
data/lib/daemons.rb ADDED
@@ -0,0 +1,308 @@
1
+ require 'optparse'
2
+ require 'optparse/time'
3
+ #require 'ostruct'
4
+
5
+ require 'daemons/pidfile'
6
+ require 'daemons/cmdline'
7
+ require 'daemons/exceptions'
8
+
9
+
10
+ # All functions and classes that Daemons provides reside in this module.
11
+ #
12
+ # The function you should me most interested in is Daemons#run, because it is
13
+ # the only function you need to invoke directly from your scripts.
14
+ #
15
+ # Also, you are maybe interested in reading the documentation for the class PidFile.
16
+ # There you can find out about how Daemons works internally and how and where the so
17
+ # called <i>Pid-Files</i> are stored.
18
+ #
19
+ module Daemons
20
+
21
+ VERSION = "0.0.1"
22
+
23
+ require 'daemons/daemonize'
24
+
25
+
26
+ class Application
27
+
28
+ attr_accessor :app_argv
29
+ attr_accessor :controller_argv
30
+
31
+ attr_reader :pid_file
32
+
33
+
34
+ def initialize(group, pid_file = nil)
35
+ @group = group
36
+
37
+ @pid_file = (pid_file || PidFile.new(pidfile_dir(), @group.app_name, @group.multiple))
38
+ end
39
+
40
+ def script
41
+ @script || @group.script
42
+ end
43
+
44
+ def pidfile_dir
45
+ PidFile.dir(@dir_mode || @group.dir_mode, @dir || @group.dir, @script || @group.script)
46
+ end
47
+
48
+ def start
49
+ #puts "starting..."
50
+
51
+ unless @group.controller.options[:ontop]
52
+ Daemonize.daemonize()
53
+ end
54
+
55
+ @pid_file.write
56
+
57
+ at_exit {
58
+ @pid_file.remove rescue nil
59
+ }
60
+
61
+ trap('TERM') {
62
+ @pid_file.remove rescue nil
63
+ exit
64
+ }
65
+
66
+ run()
67
+ end
68
+
69
+ def run
70
+ $DAEMONS_ARGV = @controller_argv
71
+
72
+ ARGV.clear
73
+ ARGV.concat @app_argv if @app_argv
74
+
75
+ load script()
76
+ end
77
+
78
+ def stop
79
+ Process.kill('TERM', @pid_file.read)
80
+
81
+ # begin
82
+ # @pidfile.remove
83
+ # rescue ::Exception
84
+ # end
85
+ end
86
+ end
87
+
88
+
89
+ class ApplicationGroup
90
+
91
+ attr_reader :app_name
92
+ attr_reader :script
93
+
94
+ attr_reader :controller
95
+
96
+ # True if one can start multiple instances of the application
97
+ #attr_reader :multiple
98
+
99
+ #attr_reader :options
100
+
101
+ attr_reader :applications
102
+
103
+ attr_accessor :controller_argv
104
+ attr_accessor :app_argv
105
+
106
+ attr_accessor :dir_mode
107
+ attr_accessor :dir
108
+
109
+ attr_reader :multiple
110
+
111
+ def initialize(app_name, script, controller) #multiple = false)
112
+ @app_name = app_name
113
+ @script = script
114
+ @controller = controller
115
+
116
+ options = controller.options
117
+
118
+ @multiple = options[:multiple] || false
119
+
120
+ @dir_mode = options[:dir_mode] || :script
121
+ @dir = options[:dir] || ''
122
+
123
+ #@multiple = multiple
124
+
125
+ @applications = find_applications(pidfile_dir())
126
+ end
127
+
128
+ def pidfile_dir
129
+ PidFile.dir(@dir_mode, @dir, script)
130
+ end
131
+
132
+ def find_applications(dir)
133
+ pid_files = PidFile.find_files(dir, app_name)
134
+
135
+ #pp pid_files
136
+
137
+ return pid_files.map {|f| Application.new(self, PidFile.existing(f))}
138
+ end
139
+
140
+ def new_application(script = nil)
141
+ if @applications.size > 0 and not @multiple
142
+ raise RuntimeException.new('there is already one or more instance(s) of the program running')
143
+ end
144
+
145
+ app = Application.new(self)
146
+
147
+ app.controller_argv = @controller_argv
148
+ app.app_argv = @app_argv
149
+
150
+ @applications << app
151
+
152
+ return app
153
+ end
154
+
155
+ def start_all
156
+ @applications.each {|a| fork { a.start } }
157
+ end
158
+
159
+ def stop_all
160
+ @applications.each {|a| a.stop}
161
+ end
162
+ end
163
+
164
+
165
+
166
+ class Controller
167
+
168
+ attr_reader :app_name
169
+ attr_reader :options
170
+
171
+
172
+ COMMANDS = [
173
+ 'start',
174
+ 'stop',
175
+ 'restart',
176
+ 'run'
177
+ ]
178
+
179
+ def initialize(script, argv = [])
180
+ @argv = argv
181
+ @script = File.expand_path(script)
182
+
183
+ @app_name = File.split(@script)[1]
184
+
185
+ @command, @controller_part, @app_part = Controller.split_argv(argv)
186
+
187
+ #@options[:dir_mode] ||= :script
188
+
189
+ @optparse = Optparse.new(self)
190
+ end
191
+
192
+
193
+ def setup_options
194
+ #@options[:ontop] ||= true
195
+ end
196
+
197
+ def run(options = {})
198
+ @options = options
199
+
200
+ @options.update @optparse.parse(@controller_part).delete_if {|k,v| !v}
201
+
202
+ setup_options()
203
+
204
+ #pp @options
205
+
206
+ @group = ApplicationGroup.new(@app_name, @script, self) #options)
207
+ @group.controller_argv = @controller_part
208
+ @group.app_argv = @app_part
209
+
210
+ case @command
211
+ when 'start'
212
+ @group.new_application.start
213
+ when 'run'
214
+ @group.new_application.run
215
+ when 'stop'
216
+ @group.stop_all
217
+ when 'restart'
218
+ @group.stop_all
219
+ sleep 1
220
+ @group.start_all
221
+ when nil
222
+ raise CmdException.new('no command given')
223
+ #puts "ERROR: No command given"; puts
224
+
225
+ #print_usage()
226
+ #raise('usage function not implemented')
227
+ else
228
+ raise Error.new("not implemented command '#{@command}'")
229
+ end
230
+ end
231
+
232
+
233
+ # Split an _argv_ array.
234
+ # +argv+ is assumed to be in the following format:
235
+ # ['command', 'controller option 1', 'controller option 2', ..., '--', 'app option 1', ...]
236
+ #
237
+ # <tt>command</tt> must be one of the commands listed in <tt>COMMANDS</tt>
238
+ #
239
+ # *Returns*: the command as a string, the controller options as an array, the appliation options
240
+ # as an array
241
+ #
242
+ def Controller.split_argv(argv)
243
+ argv = argv.dup
244
+
245
+ command = nil
246
+ controller_part = []
247
+ app_part = []
248
+
249
+ if COMMANDS.include? argv[0]
250
+ command = argv.shift
251
+ end
252
+
253
+ if i = argv.index('--')
254
+ controller_part = argv[0..i-1]
255
+ app_part = argv[i+1..-1]
256
+ else
257
+ controller_part = argv[0..-1]
258
+ end
259
+
260
+ return command, controller_part, app_part
261
+ end
262
+ end
263
+
264
+
265
+ # Passes control to Daemons.
266
+ #
267
+ # +script+:: This is the path to the script that should be run as a daemon.
268
+ # Please note that Daemons runs this script with <tt>load <script></tt>.
269
+ # Also note that Daemons cannot detect the directory in which the controlling
270
+ # script resides, so this has to be either an absolute path or you have to run
271
+ # the controlling script from the appropriate directory.
272
+ #
273
+ # +options+:: A hash that may contain one or more of options listed below
274
+ #
275
+ # === Options:
276
+ # <tt>:dir_mode</tt>:: Either <tt>:script</tt> (the directory for writing the pid files to
277
+ # given by <tt>:dir</tt> is interpreted relative
278
+ # to the script location given by +script+) or <tt>:normal</tt> (the directory given by
279
+ # <tt>:dir</tt> is interpreted relative to the current directory) or <tt>:system</tt>
280
+ # (<tt>/var/run</tt> is used as the pid file directory)
281
+ #
282
+ # <tt>:dir</tt>:: Used in combination with <tt>:dir_mode</tt> (description above)
283
+ # <tt>:multiple</tt>:: Specifies whether multiple instances of the same script are allowed to run at the
284
+ # same time
285
+ #
286
+ # -----
287
+ #
288
+ # === Example:
289
+ # options = {
290
+ # :dir_mode => :script,
291
+ # :dir => 'pids',
292
+ # :multiple => true
293
+ # }
294
+ #
295
+ # Daemons.run(File.join(File.split(__FILE__)[0], 'myscript.rb'), options)
296
+ #
297
+ def run(script, options = {})
298
+ @controller = Controller.new(script, ARGV)
299
+
300
+ #pp @controller
301
+
302
+ @controller.catch_exceptions {
303
+ @controller.run(options)
304
+ }
305
+ end
306
+ module_function :run
307
+
308
+ end