daemons 0.0.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.
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