acts_as_service 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2010 Umamibud, Inc.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,78 @@
1
+ = acts_as_service
2
+
3
+ == Overview
4
+
5
+ A gem that gives you an easy way to make service-like code and spend all your
6
+ effort on actually writing the code to get your job done.
7
+
8
+ Limitations:
9
+ * one process at a time
10
+ * only been tested while running inside a Rails application
11
+ (e.g. script/runner MySrv.start). should work if you just specify a pidfile
12
+ explicitly, but needs a little testing
13
+ * smooth shutdown depends on your work method returning frequently (however
14
+ long you run without returning from perform_work_chunk is how long it might
15
+ take for the service to shut down
16
+
17
+ Allows you to:
18
+ * start, stop, restart your process
19
+ * ensures that only one of each process is running on a given machine (if this
20
+ is not desired, you can probably hack it or enhance to allow up to N
21
+ instances if you want
22
+ * implement a method you want called over and over, and when the service is
23
+ supposed to shut down, it stops calling the method and exits
24
+ * see what the PID is of the process by inspecting the pidfile
25
+ * explicitly shutdown the process from within the service class (but you'll
26
+ still have to return from perform_work_chunk).
27
+ * initiate shutdown of service externally by either calling MyService.stop or
28
+ adding 'shutdown' to the PID file.
29
+ * hooks to execute methods after start or before stop
30
+
31
+ == What to do
32
+
33
+ require 'acts_as_service' # or add it to your Rails env as a config.gem
34
+
35
+ class MyService
36
+
37
+ acts_as_service
38
+
39
+ def self.perform_work_chunk
40
+ # do stuff that returns in a short period (e.g. a few seconds or less)
41
+ end
42
+
43
+ end
44
+
45
+ you can get fancier and specify your process identifier and how long to sleep
46
+ between calls to perform_work_chunk:
47
+
48
+ class MyService
49
+
50
+ acts_as_service
51
+
52
+ def self.service_name
53
+ "MyAwesomeService"
54
+ end
55
+
56
+ def self.sleep_time
57
+ 5 # seconds
58
+ end
59
+
60
+ def self.perform_work_chunk
61
+ # do stuff that returns in a short period (e.g. a few seconds or less)
62
+ end
63
+
64
+ end
65
+
66
+ == Maintaining acts_as_service
67
+
68
+ Make sure you have jeweler.
69
+
70
+ To build:
71
+
72
+ cd .../acts_as_service
73
+ rake gemspec
74
+ rake build
75
+
76
+ To test:
77
+
78
+ Sorry, little lazy here. Create a sample service class and letter rip!
data/TODO ADDED
@@ -0,0 +1,9 @@
1
+ TODOs
2
+ -----
3
+
4
+ put things in here that could be worked on
5
+
6
+ * extricate the gem from needing to run within Rails (maybe nothing to do here,
7
+ maybe just coming up with different pidfile default path if RAILS_ROOT
8
+ isn't defined. but including class can always just specify explicit path and
9
+ should be fine
@@ -0,0 +1,46 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_service}
8
+ s.version = "0.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brian Percival"]
12
+ s.date = %q{2010-02-18}
13
+ s.description = %q{A gem with a mixin to let you turn a class into something that runs like a service,
14
+ which you can MyService.start, MyService.stop, and MyService.restart. It tracks
15
+ its own pid. For now, pretty sure it requires that the class is running inside
16
+ a rails context (e.g. run by script/runner MyService.start), but that could
17
+ probably be changed without too much difficulty.
18
+ }
19
+ s.email = %q{percivalatumamibuddotcom}
20
+ s.extra_rdoc_files = [
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "TODO"
24
+ ]
25
+ s.files = [
26
+ "README.rdoc",
27
+ "acts_as_service.gemspec",
28
+ "lib/acts_as_service.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/bmpercy/acts_as_service}
31
+ s.rdoc_options = ["--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.rubygems_version = %q{1.3.5}
34
+ s.summary = %q{Makes it very easy to create a service-like class that's easy to start and stop, taken from work at discovereads.com}
35
+
36
+ if s.respond_to? :specification_version then
37
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
+ s.specification_version = 3
39
+
40
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
41
+ else
42
+ end
43
+ else
44
+ end
45
+ end
46
+
@@ -0,0 +1,306 @@
1
+ # module that makes it easy to turn a class into a service, giving it methods
2
+ # like stop, start, restart for easy cronjobbage, etc.
3
+ #
4
+ # simply require 'acts_as_service' in your class' file and then call
5
+ # acts_as_service in your class definition, then implement a few required
6
+ # and option methods:
7
+ # - required:
8
+ # - self.perform_work_chunk: do a chunk of work that the service is intended
9
+ # to do. note that this method should return
10
+ # periodically to enable shutdown. alternatively,
11
+ # your method /could/ (but not recommended) check
12
+ # the status to see if it's set to SHUTTING_DOWN
13
+ # and return if so. otherwise, the shutdown
14
+ # by this module will not work.
15
+ #
16
+ # - optional:
17
+ # - self.service_name: return the string name of the service.
18
+ # default is the class name (without module prefixes)
19
+ # - self.service_pid_filename: path to the file that should contain the PID for
20
+ # the process while it's running.
21
+ # default is #{RAILS_ROOT}/tmp/pids/<underscore version of service name>
22
+ # - self.sleep_time: seconds to sleep between calls to peform_work_chunk
23
+ # default: no sleep till brooklyn!
24
+ # - self.after_start: a hook to run a method after the service is started
25
+ # but before first call to perform_work_chunk
26
+ # - self.before_stop: a hook to run a method before final shutdown (and
27
+ # after last run to perform_work_chunk)
28
+ #
29
+ # you can also call self.shutdown() from within your perform_work_chunk
30
+ # code to initiate a shutdown (don't just exit() because there's pidfile
31
+ # cleanup etc. to peform).
32
+ #
33
+ # # my_service.rb:
34
+ #
35
+ # require 'acts_as_service'
36
+ #
37
+ # class MyService
38
+ #
39
+ # acts_as_service
40
+ #
41
+ # # ... define methods
42
+ #
43
+ # end
44
+ #-------------------------------------------------------------------------------
45
+ module ActsAsService
46
+
47
+ ACTS_AS_SERVICE_RUNNING = 'running'
48
+ ACTS_AS_SERVICE_OTHER_RUNNING = 'other running'
49
+ ACTS_AS_SERVICE_STOPPED = 'stopped'
50
+ ACTS_AS_SERVICE_SHUTTING_DOWN = 'shutting down'
51
+ ACTS_AS_SERVICE_PID_NO_PROCESS = 'pid no process'
52
+
53
+ def self.included(base)
54
+ base.extend ClassMethods
55
+ end
56
+
57
+ module ClassMethods
58
+ # starts the process if it's not already running
59
+ #---------------------------------------------------------------------------
60
+ def start
61
+ begin
62
+ if _status != ACTS_AS_SERVICE_STOPPED &&
63
+ _status != ACTS_AS_SERVICE_PID_NO_PROCESS
64
+
65
+ puts "#{_display_name} (#{_pid_file_pid}) is already running. Ignoring."
66
+ else
67
+ # clean out a stale pid
68
+ if _status == ACTS_AS_SERVICE_PID_NO_PROCESS
69
+ puts 'Pid file exists but process is not running. Removing old pid file.'
70
+ File.delete(_pid_filename)
71
+ end
72
+ puts "Starting #{_display_name} (#{_pid})...."
73
+ puts "Run #{name}.stop to stop\n\n"
74
+ File.open(_pid_filename, 'w') {|f| f.write(_pid.to_s) }
75
+ if self.respond_to?(:after_start)
76
+ after_start
77
+ end
78
+ while (_status == ACTS_AS_SERVICE_RUNNING)
79
+ # only sleep if asked to
80
+ if self.respond_to?(:sleep_time)
81
+ sleep sleep_time
82
+ end
83
+ perform_work_chunk
84
+ end
85
+ puts "Shutting down #{_display_name} (#{_pid})"
86
+ File.delete(_pid_filename)
87
+ end
88
+ # if something happens, dump an error and clean up the pidfile if
89
+ # it's owned by this process
90
+ rescue Object => e
91
+ puts "ERROR: #{e}\n#{e.respond_to?(:backtrace) ? e.backtrace.join("\n ") : ''}"
92
+ puts "Exiting (#{_pid})\n"
93
+ if _process_running?
94
+ File.delete(_pid_filename)
95
+ end
96
+ end
97
+ end
98
+
99
+
100
+ # stops the process if it's running
101
+ #---------------------------------------------------------------------------
102
+ def stop
103
+ if _status == ACTS_AS_SERVICE_STOPPED
104
+ puts "#{_display_name} is not running"
105
+ elsif _status == ACTS_AS_SERVICE_PID_NO_PROCESS
106
+ puts 'Pid file exists but process is not running. Removing old pid file.'
107
+ File.delete(_pid_filename)
108
+ else
109
+ pid_to_stop = _pid_file_pid
110
+ puts "Stopping #{_display_name} (#{pid_to_stop})...."
111
+ shutdown
112
+ while (_status != ACTS_AS_SERVICE_STOPPED)
113
+ sleep(1)
114
+ end
115
+ puts "#{_display_name} (#{pid_to_stop}) stopped\n"
116
+ end
117
+ end
118
+
119
+
120
+ # stops the current service process and runs a new one in the current process
121
+ #-----------------------------------------------------------------------------
122
+ def restart
123
+ stop
124
+ start
125
+ end
126
+
127
+
128
+ # initiate shutdown. call this from within perform_work_chunk if the service's
129
+ # work is done and it should shut down (makes sense for cronjobs, say)
130
+ #-----------------------------------------------------------------------------
131
+ def shutdown
132
+ if self.respond_to?(:before_stop)
133
+ before_stop
134
+ end
135
+ # change the pid file so the original process sees this 'stop' signal
136
+ File.open(_pid_filename, 'a') do |f|
137
+ f.write("\n#{ACTS_AS_SERVICE_SHUTTING_DOWN}")
138
+ end
139
+ end
140
+
141
+
142
+ # method for outside consumption of status info. hopefully not
143
+ # tempting method name for class-writers to conflict with...
144
+ #---------------------------------------------------------------------------
145
+ def service_running?
146
+ _status == ACTS_AS_SERVICE_RUNNING || _status == ACTS_AS_SERVICE_OTHER_RUNNING
147
+ end
148
+
149
+
150
+ # method for outside consumption of the pid value. hopefully not
151
+ # tempting method name for class-writers to conflict with...
152
+ #---------------------------------------------------------------------------
153
+ def service_pid
154
+ _pid_file_pid
155
+ end
156
+
157
+
158
+ #---------------------------------------------------------------------------
159
+ # helper methods, no real need to access these from client code
160
+ #---------------------------------------------------------------------------
161
+
162
+
163
+ # fetches the service's name, using class name as default
164
+ #---------------------------------------------------------------------------
165
+ def _display_name
166
+ @@_display_name ||= (respond_to?(:service_name) ?
167
+ service_name :
168
+ name.split("::").last)
169
+ end
170
+
171
+
172
+ # returns the pid filename
173
+ #---------------------------------------------------------------------------
174
+ def _pid_filename
175
+ if defined?(@@_pid_filename)
176
+ return @@_pid_filename
177
+ end
178
+
179
+ if respond_to?(:service_pid_filename)
180
+ @@_pid_filename = service_pid_filename
181
+ else
182
+ @@_pid_filename = File.join(RAILS_ROOT, 'tmp', 'pids',
183
+ "#{_display_name.underscore.gsub(/\s+/, '_')}.pid")
184
+ end
185
+ return @@_pid_filename
186
+ end
187
+
188
+
189
+ # the current status of the service. possible values:
190
+ # ACTS_AS_SERVICE_STOPPED : no service process is running
191
+ # ACTS_AS_SERVICE_SHUTTING_DOWN : process currently shutting down
192
+ # ACTS_AS_SERVICE_RUNNING : the current process is the service process
193
+ # ACTS_AS_SERVICE_OTHER_RUNNING : another pid is running the service
194
+ # ACTS_AS_SERVICE_PID_NO_PROCESS : pidfile exists, but no process running
195
+ #-----------------------------------------------------------------------------
196
+ def _status
197
+ _status = nil
198
+
199
+ # logic:
200
+ # if the pid file doesn't exist, it's stopped
201
+ # otherwise, if 'shutting down', it's shutting down
202
+ # or if the pidfile's pid is running, another process is running
203
+ # or if the pidfile's pid matches this process, it's running
204
+ # otherwise, the pidfile's there but no one's running
205
+ if !_pid_file_exists?
206
+ _status = ACTS_AS_SERVICE_STOPPED
207
+ elsif Regexp.new(ACTS_AS_SERVICE_SHUTTING_DOWN) =~ _pid_file_content
208
+ _status = ACTS_AS_SERVICE_SHUTTING_DOWN
209
+ elsif _process_running?
210
+ _status = ACTS_AS_SERVICE_RUNNING
211
+ elsif _pid_file_process_running?
212
+ _status = ACTS_AS_SERVICE_OTHER_RUNNING
213
+ else
214
+ _status = ACTS_AS_SERVICE_PID_NO_PROCESS
215
+ end
216
+
217
+ return _status
218
+ end
219
+
220
+
221
+ # indicates if the pid file exists for this service
222
+ #---------------------------------------------------------------------------
223
+ def _pid_file_exists?
224
+ File.exist?(_pid_filename)
225
+ end
226
+
227
+
228
+ # returns the entire contents of the pid file
229
+ #---------------------------------------------------------------------------
230
+ def _pid_file_content
231
+ begin
232
+ f = File.open(_pid_filename, 'r')
233
+ return f.blank? ? f : f.read
234
+ rescue Errno::ENOENT
235
+ return nil
236
+ end
237
+ end
238
+
239
+
240
+ # the pid found in the pidfile (if it exists, nil if it doesn't)
241
+ #---------------------------------------------------------------------------
242
+ def _pid_file_pid
243
+ /^(\d*)$/ =~ _pid_file_content
244
+ return $1.blank? ? nil : $1.to_i
245
+ end
246
+
247
+
248
+ # the current process' pid
249
+ #---------------------------------------------------------------------------
250
+ def _pid
251
+ @@_pid ||= Process.pid
252
+ end
253
+
254
+
255
+ # Checks to see if the process pointed to by the pid file is actually
256
+ # running Note that I couldn't find a good way to check for the status of a
257
+ # different process by its pid, so this checks to see if the proces has a
258
+ # process group id, and if the process doesn't exist, an exception is
259
+ # returned
260
+ #---------------------------------------------------------------------------
261
+ def _pid_file_process_running?
262
+ begin
263
+ Process.getpgid(_pid_file_pid)
264
+ return true
265
+ rescue
266
+ return false
267
+ end
268
+ end
269
+
270
+
271
+ # returns true if the current process is the pid in the pid file. false
272
+ # otherwise
273
+ #---------------------------------------------------------------------------
274
+ def _process_running?
275
+ return _pid == _pid_file_pid
276
+ end
277
+
278
+ end
279
+ end
280
+
281
+ # gives us the handy acts_as_service method we can declare inside any class
282
+ #-------------------------------------------------------------------------------
283
+ class Object
284
+ def self.acts_as_service
285
+ self.send(:include, ActsAsService)
286
+ end
287
+ end
288
+
289
+
290
+ # totally addicted to blank?
291
+ #-------------------------------------------------------------------------------
292
+ class File
293
+ def blank?
294
+ return self.nil?
295
+ end
296
+ end
297
+ class String
298
+ def blank?
299
+ return self.nil? || self == ''
300
+ end
301
+ end
302
+ class NilClass
303
+ def blank?
304
+ return self.nil?
305
+ end
306
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_service
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Brian Percival
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-18 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: |
17
+ A gem with a mixin to let you turn a class into something that runs like a service,
18
+ which you can MyService.start, MyService.stop, and MyService.restart. It tracks
19
+ its own pid. For now, pretty sure it requires that the class is running inside
20
+ a rails context (e.g. run by script/runner MyService.start), but that could
21
+ probably be changed without too much difficulty.
22
+
23
+ email: percivalatumamibuddotcom
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - LICENSE
30
+ - README.rdoc
31
+ - TODO
32
+ files:
33
+ - README.rdoc
34
+ - acts_as_service.gemspec
35
+ - lib/acts_as_service.rb
36
+ - LICENSE
37
+ - TODO
38
+ has_rdoc: true
39
+ homepage: http://github.com/bmpercy/acts_as_service
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --charset=UTF-8
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.5
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Makes it very easy to create a service-like class that's easy to start and stop, taken from work at discovereads.com
66
+ test_files: []
67
+