process_pool 0.1.2 → 0.1.3

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/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  disable_system_gems
2
2
 
3
3
  gem 'json'
4
+ gem 'jeweler'
4
5
 
5
6
  only :test do
6
7
  gem 'shoulda'
data/TODO ADDED
@@ -0,0 +1,12 @@
1
+ Extensions stuff:
2
+ - around extensions
3
+ - handling :stop
4
+ - helpers for anonymous extensions
5
+ - a couple of generic extensions:
6
+ - restarting active record connections on worker startup
7
+ - benchmarking
8
+ - restarting failed tasks
9
+
10
+ Queue:
11
+ - posix shared memory or message queue backend
12
+ - redis backend
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.3
@@ -0,0 +1,20 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'init')
2
+
3
+ class AnonymousExtension < BaseWorkerExtension
4
+
5
+ SUPPORTED_EXTENSION_METHODS = [:before, :after, :around, :startup, :shutdown]
6
+
7
+ def initialize(method, &block)
8
+ raise ArgumentError.new("Unknown method: #{method}") unless SUPPORTED_EXTENSION_METHODS.include?(method)
9
+
10
+ @method = method
11
+ @block = lambda(&block)
12
+ end
13
+
14
+ SUPPORTED_EXTENSION_METHODS.each do |method_name|
15
+ define_method(method_name) do |*args|
16
+ @block.call(*args) if method_name == @method
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,35 @@
1
+ # base class for process pool extensions
2
+ #
3
+ # it is not required that the extensions inherit from this class,
4
+ # it's here mostly for convenience and documentation sake
5
+ class BaseWorkerExtension
6
+
7
+ attr_accessor :logger, :process_pool
8
+
9
+ # will be called before task gets executed
10
+ # thowing :stop will halt the task execution process,
11
+ # no other filters will be executed for this task
12
+ def before(task)
13
+ end
14
+
15
+ # will be called after task gets executed
16
+ def after(task, result)
17
+ end
18
+
19
+ # will be called before task gets executed
20
+ # around is responsible for calling task.run,
21
+ # failing to do so might result in unpredictable behavior
22
+ # and failing to run a ProcessPool::EndTask will result in
23
+ # the process never ending
24
+ def around(task)
25
+ end
26
+
27
+ # will be called on worker startup, before executing any tasks
28
+ def startup
29
+ end
30
+
31
+ # will be called on worker shutdown
32
+ def shutdown
33
+ end
34
+
35
+ end
@@ -4,3 +4,5 @@ require 'json'
4
4
  require File.expand_path(File.join(File.dirname(__FILE__), 'simple_logger'))
5
5
  require File.expand_path(File.join(File.dirname(__FILE__), 'simple_queue'))
6
6
  require File.expand_path(File.join(File.dirname(__FILE__), 'process_pool'))
7
+ require File.expand_path(File.join(File.dirname(__FILE__), 'base_worker_extension'))
8
+ require File.expand_path(File.join(File.dirname(__FILE__), 'anonymous_extension'))
@@ -13,14 +13,25 @@ class ProcessPool
13
13
  self.workers_count = workers_count
14
14
  self.queue = queue
15
15
  self.worker_pids = []
16
+ self.extensions = []
16
17
  end
17
18
 
18
19
  def schedule(job_class, *args)
19
20
  raise InvalidStateError.new('Can not add more jobs after shut down was called') if is_shutdown?
20
- logger.debug("Scheduling task #{job_class}(#{args})")
21
+ logger.debug("Scheduling task #{job_class}(#{args.collect{ |a| a.inspect }.join(', ')})")
21
22
  push_task(job_class, args)
22
23
  end
23
24
 
25
+ def register_extension(extension)
26
+ raise InvalidStateError.new('Can not register extensions once the pool is started.') unless is_stopped?
27
+ raise ArgumentError.new('extension can not be nil') unless extension
28
+
29
+ extension.process_pool = self if extension.respond_to?(:process_pool=)
30
+ extension.logger = logger if extension.respond_to?(:logger=)
31
+
32
+ extensions << extension
33
+ end
34
+
24
35
  def start
25
36
  raise InvalidStateError.new('Can not start a pool more than once') unless is_stopped?
26
37
  logger.info("Starting process pool")
@@ -28,12 +39,16 @@ class ProcessPool
28
39
 
29
40
  workers_count.times do
30
41
  pid = fork do
42
+ run_extensions(:startup)
43
+ at_exit { run_extensions(:shutdown) }
31
44
  child_queue = get_child_queue()
32
45
  while true
33
46
  task_class, args = child_queue.pop
34
47
  begin
35
48
  task = get_task_class(task_class).new(*args)
36
- task.run
49
+ run_extensions(:before, task) unless task.is_a?(EndTask)
50
+ result = task.run
51
+ run_extensions(:after, task, result)
37
52
  rescue => e
38
53
  logger.warn("Exception occurred while executing task #{task_class}(#{args}): #{e}")
39
54
  end
@@ -73,7 +88,7 @@ class ProcessPool
73
88
 
74
89
  protected
75
90
 
76
- attr_accessor :state, :logger, :queue, :worker_pids
91
+ attr_accessor :state, :logger, :queue, :worker_pids, :extensions
77
92
  attr_writer :workers_count
78
93
 
79
94
  def push_task(job_class, args)
@@ -93,6 +108,14 @@ class ProcessPool
93
108
  Object.module_eval("::#{$1}", __FILE__, __LINE__)
94
109
  end
95
110
 
111
+ def run_extensions(method, *args)
112
+ extensions.each do |extension|
113
+ if extension.respond_to?(method)
114
+ extension.send(method, *args)
115
+ end
116
+ end
117
+ end
118
+
96
119
  class EndTask
97
120
  def run
98
121
  exit(0)
@@ -43,10 +43,14 @@ class SimpleQueue
43
43
 
44
44
  def self.create
45
45
  file = Tempfile.new('simple_queue')
46
- file.puts [].to_json
47
- uri = file.path
48
- file.close
49
- return new(uri)
46
+ path = File.expand_path(file.path)
47
+ file.close!
48
+
49
+ File.open(path, 'w+') do |f|
50
+ f.puts [].to_json
51
+ end
52
+
53
+ return new(path)
50
54
  end
51
55
 
52
56
  def self.get(uri)
@@ -5,15 +5,16 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{process_pool}
8
- s.version = "0.1.2"
8
+ s.version = "0.1.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Adam Pohorecki"]
12
- s.date = %q{2010-01-20}
12
+ s.date = %q{2010-02-18}
13
13
  s.email = %q{adam@pohorecki.pl}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
16
- "README"
16
+ "README",
17
+ "TODO"
17
18
  ]
18
19
  s.files = [
19
20
  ".gitignore",
@@ -21,12 +22,16 @@ Gem::Specification.new do |s|
21
22
  "LICENSE",
22
23
  "README",
23
24
  "Rakefile",
25
+ "TODO",
24
26
  "VERSION",
27
+ "lib/anonymous_extension.rb",
28
+ "lib/base_worker_extension.rb",
25
29
  "lib/init.rb",
26
30
  "lib/process_pool.rb",
27
31
  "lib/simple_logger.rb",
28
32
  "lib/simple_queue.rb",
29
33
  "process_pool.gemspec",
34
+ "test/anonymous_extension_test.rb",
30
35
  "test/process_pool_test.rb",
31
36
  "test/simple_queue_test.rb",
32
37
  "test/test_helper.rb"
@@ -37,7 +42,8 @@ Gem::Specification.new do |s|
37
42
  s.rubygems_version = %q{1.3.5}
38
43
  s.summary = %q{ProcessPool with interchangeable job queue backends for Ruby}
39
44
  s.test_files = [
40
- "test/process_pool_test.rb",
45
+ "test/anonymous_extension_test.rb",
46
+ "test/process_pool_test.rb",
41
47
  "test/simple_queue_test.rb",
42
48
  "test/test_helper.rb"
43
49
  ]
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2
+
3
+ class AnonymousExtensionTest < Test::Unit::TestCase
4
+
5
+ should "raise ArgumentError when called with an unsupported method" do
6
+ assert_raises ArgumentError do
7
+ AnonymousExtension.new(:unsupported_method) {}
8
+ end
9
+ end
10
+
11
+ AnonymousExtension::SUPPORTED_EXTENSION_METHODS.each do |method_name|
12
+ should "not raise anything when called with #{method_name}" do
13
+ assert_nothing_raised do
14
+ AnonymousExtension.new(method_name) {}
15
+ end
16
+ end
17
+
18
+ context "with method #{method_name}" do
19
+ setup do
20
+ @value = 0
21
+ @ext = AnonymousExtension.new(method_name) { |x| @value = x }
22
+ end
23
+
24
+ should "execute the block when right method is called" do
25
+ @ext.send(method_name, 1)
26
+ assert_equal 1, @value
27
+ end
28
+
29
+ AnonymousExtension::SUPPORTED_EXTENSION_METHODS.each do |other_method|
30
+ next if method_name == other_method
31
+
32
+ should "not execute the block when #{other_method} is called" do
33
+ @ext.send(other_method, 1)
34
+ assert_equal 0, @value
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -65,6 +65,21 @@ class WrongArgumentsTask
65
65
  end
66
66
  end
67
67
 
68
+ class WriteToFileTask
69
+
70
+ def initialize(path, n)
71
+ @path = path
72
+ @n = n
73
+ end
74
+
75
+ def run
76
+ File.open(@path, 'a+') do |file|
77
+ file.puts "task #{@n}"
78
+ end
79
+ end
80
+
81
+ end
82
+
68
83
  class ProcessPoolTest < Test::Unit::TestCase
69
84
 
70
85
  context "state" do
@@ -102,6 +117,21 @@ class ProcessPoolTest < Test::Unit::TestCase
102
117
  end
103
118
  end
104
119
 
120
+ should "raise InvalidStateError when calling register_extension on a started pool" do
121
+ @pool.start
122
+ assert_raises ProcessPool::InvalidStateError do
123
+ @pool.register_extension(BaseWorkerExtension.new)
124
+ end
125
+ end
126
+
127
+ should "raise InvalidStateError when calling register_extension on a shutdown pool" do
128
+ @pool.start
129
+ @pool.shutdown
130
+ assert_raises ProcessPool::InvalidStateError do
131
+ @pool.register_extension(BaseWorkerExtension.new)
132
+ end
133
+ end
134
+
105
135
  should "raise InvalidStateError when calling start twice" do
106
136
  @pool.start
107
137
  assert_raises ProcessPool::InvalidStateError do
@@ -158,7 +188,7 @@ class ProcessPoolTest < Test::Unit::TestCase
158
188
  context :shutdown do
159
189
  setup do
160
190
  @queue = SampleQueue.new
161
- @pool = ProcessPool.new(3, @queue, SimpleLogger.new(:debug)) # no workers so that nothing processes the queue
191
+ @pool = ProcessPool.new(3, @queue, SimpleLogger.new(:debug))
162
192
  @pool.schedule(SampleTask)
163
193
  @pool.stubs(:fork => 1)
164
194
  @pool.start
@@ -230,4 +260,185 @@ class ProcessPoolTest < Test::Unit::TestCase
230
260
 
231
261
  end
232
262
 
263
+ def self.should_write_lines(*lines)
264
+ should "write lines #{lines.collect{|line| line.inspect}.join(', ')} to file" do
265
+ text = open(@path).read
266
+ file_lines = text.split("\n").collect { |line| line.strip }
267
+ assert_equal lines, file_lines
268
+ end
269
+ end
270
+
271
+ def write_to_shared_file(str)
272
+ File.open(@path, 'a+') do |file|
273
+ file.puts str
274
+ end
275
+ end
276
+
277
+ context "extensions" do
278
+ setup do
279
+ @logger = SimpleLogger.new(:debug)
280
+ @pool = ProcessPool.new(1, SimpleQueue.create, @logger)
281
+ @shared_file = Tempfile.new('test')
282
+ @path = @shared_file.path
283
+ end
284
+
285
+ teardown do
286
+ @shared_file.close
287
+ end
288
+
289
+ should "raise ArgumentError if called with nil" do
290
+ assert_raises ArgumentError do
291
+ @pool.register_extension nil
292
+ end
293
+ end
294
+
295
+ should "inject process_pool to the extension" do
296
+ extension = BaseWorkerExtension.new
297
+ assert_nil extension.process_pool
298
+ @pool.register_extension(extension)
299
+ assert_equal @pool, extension.process_pool
300
+ end
301
+
302
+ should "inject logger to the extension" do
303
+ extension = BaseWorkerExtension.new
304
+ assert_nil extension.logger
305
+ @pool.register_extension(extension)
306
+ assert_equal @logger, extension.logger
307
+ end
308
+
309
+ context "with no extensions" do
310
+ setup do
311
+ @pool.schedule(WriteToFileTask, @path, 1)
312
+ @pool.schedule(WriteToFileTask, @path, 2)
313
+
314
+ @pool.start
315
+ @pool.shutdown
316
+ end
317
+
318
+ should_write_lines "task 1", "task 2"
319
+ end
320
+
321
+ context "with before extension" do
322
+ setup do
323
+ @pool.schedule(WriteToFileTask, @path, 1)
324
+ @pool.schedule(WriteToFileTask, @path, 2)
325
+
326
+ @pool.register_extension AnonymousExtension.new(:before) { |t| write_to_shared_file("before") }
327
+
328
+ @pool.start
329
+ @pool.shutdown
330
+ end
331
+
332
+ should_write_lines "before", "task 1", "before", "task 2"
333
+ end
334
+
335
+ context "with after extension" do
336
+ setup do
337
+ @pool.schedule(WriteToFileTask, @path, 1)
338
+ @pool.schedule(WriteToFileTask, @path, 2)
339
+
340
+ @pool.register_extension AnonymousExtension.new(:after) { |t, r| write_to_shared_file("after") }
341
+
342
+ @pool.start
343
+ @pool.shutdown
344
+ end
345
+
346
+ should_write_lines "task 1", "after", "task 2", "after"
347
+ end
348
+
349
+ context "with around extension" do
350
+ setup do
351
+ @pool.schedule(WriteToFileTask, @path, 1)
352
+ @pool.schedule(WriteToFileTask, @path, 2)
353
+
354
+ @pool.register_extension AnonymousExtension.new(:around) { |t|
355
+ write_to_shared_file("around 1")
356
+ t.run
357
+ write_to_shared_file("around 2")
358
+ }
359
+
360
+ @pool.start
361
+ @pool.shutdown
362
+ end
363
+
364
+ should_write_lines "around 1", "task 1", "around 2", "around 1", "task 2", "around 2"
365
+ end
366
+
367
+ context "with multiple around extensions" do
368
+ setup do
369
+ @pool.schedule(WriteToFileTask, @path, 1)
370
+ @pool.schedule(WriteToFileTask, @path, 2)
371
+
372
+ @pool.register_extension AnonymousExtension.new(:around) { |t|
373
+ write_to_shared_file("around a")
374
+ t.run
375
+ write_to_shared_file("around b")
376
+ }
377
+
378
+ @pool.register_extension AnonymousExtension.new(:around) { |t|
379
+ write_to_shared_file("around 1")
380
+ t.run
381
+ write_to_shared_file("around 2")
382
+ }
383
+
384
+ @pool.start
385
+ @pool.shutdown
386
+ end
387
+
388
+ should_write_lines "around a", "around 1", "task 1", "around 2", "around b", "around a", "around 1", "task 2", "around 2", "around b"
389
+ end
390
+
391
+ context "with startup extension" do
392
+ setup do
393
+ @pool.schedule(WriteToFileTask, @path, 1)
394
+ @pool.schedule(WriteToFileTask, @path, 2)
395
+
396
+ @pool.register_extension AnonymousExtension.new(:startup) { write_to_shared_file("startup") }
397
+
398
+ @pool.start
399
+ @pool.shutdown
400
+ end
401
+
402
+ should_write_lines "startup", "task 1", "task 2"
403
+ end
404
+
405
+ context "with shutdown extension" do
406
+ setup do
407
+ @pool.schedule(WriteToFileTask, @path, 1)
408
+ @pool.schedule(WriteToFileTask, @path, 2)
409
+
410
+ @pool.register_extension AnonymousExtension.new(:shutdown) { write_to_shared_file("shutdown") }
411
+
412
+ @pool.start
413
+ @pool.shutdown
414
+ end
415
+
416
+ should_write_lines "task 1", "task 2", "shutdown"
417
+ end
418
+
419
+ context "with all kinds of extensions" do
420
+ setup do
421
+ @pool.schedule(WriteToFileTask, @path, 1)
422
+ @pool.schedule(WriteToFileTask, @path, 2)
423
+
424
+ @pool.register_extension AnonymousExtension.new(:around) { |t|
425
+ write_to_shared_file("around 1")
426
+ t.run
427
+ write_to_shared_file("around 2")
428
+ }
429
+
430
+ @pool.register_extension AnonymousExtension.new(:before) { |t| write_to_shared_file("before") }
431
+ @pool.register_extension AnonymousExtension.new(:after) { |t, r| write_to_shared_file("after") }
432
+
433
+ @pool.register_extension AnonymousExtension.new(:startup) { write_to_shared_file("startup") }
434
+ @pool.register_extension AnonymousExtension.new(:shutdown) { write_to_shared_file("shutdown") }
435
+
436
+ @pool.start
437
+ @pool.shutdown
438
+ end
439
+
440
+ should_write_lines "startup", "before", "around 1", "task 1", "around 2", "after", "before", "around 1", "task 2", "around 2", "after", "shutdown"
441
+ end
442
+ end
443
+
233
444
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Pohorecki
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-20 00:00:00 +01:00
12
+ date: 2010-02-18 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -31,18 +31,23 @@ extensions: []
31
31
  extra_rdoc_files:
32
32
  - LICENSE
33
33
  - README
34
+ - TODO
34
35
  files:
35
36
  - .gitignore
36
37
  - Gemfile
37
38
  - LICENSE
38
39
  - README
39
40
  - Rakefile
41
+ - TODO
40
42
  - VERSION
43
+ - lib/anonymous_extension.rb
44
+ - lib/base_worker_extension.rb
41
45
  - lib/init.rb
42
46
  - lib/process_pool.rb
43
47
  - lib/simple_logger.rb
44
48
  - lib/simple_queue.rb
45
49
  - process_pool.gemspec
50
+ - test/anonymous_extension_test.rb
46
51
  - test/process_pool_test.rb
47
52
  - test/simple_queue_test.rb
48
53
  - test/test_helper.rb
@@ -75,6 +80,7 @@ signing_key:
75
80
  specification_version: 3
76
81
  summary: ProcessPool with interchangeable job queue backends for Ruby
77
82
  test_files:
83
+ - test/anonymous_extension_test.rb
78
84
  - test/process_pool_test.rb
79
85
  - test/simple_queue_test.rb
80
86
  - test/test_helper.rb