babypool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ doc
3
+ coverage
data/LICENCE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Jeremy Hopple
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,25 @@
1
+ = Baby Pool: _Simple_ thread pool.
2
+
3
+ Baby Pool is a simple, but solid implementation of a thread pool using Ruby green threads.
4
+ Pools are initialized with a thread count and an execution limit. Baby Pool creates an array or workers
5
+ each in their own thread that listen for incoming jobs.
6
+
7
+ The pool will continue to accept work until it is drained by calling the drain method.
8
+
9
+ A quick example:
10
+
11
+ * Initialize the pool with a thread count and an execution limit. Each thread's worker will timeout it's current job when it reaches the pool's execution limit.
12
+
13
+ pool = Babypool.new(:thread_count => 10, :execution_limit => 20)
14
+
15
+ * Give the pool 20 jobs to do.
16
+
17
+ (0..20).each do |job|
18
+ pool.work(job) do
19
+ puts "Running job #{job}."
20
+ end
21
+ end
22
+
23
+ * When you are done, shut down the pool by calling the drain method
24
+
25
+ pool.drain
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake'
2
+
3
+ $LOAD_PATH.unshift('lib')
4
+
5
+ require 'rake/testtask'
6
+
7
+ begin
8
+ require 'jeweler'
9
+ Jeweler::Tasks.new do |gemspec|
10
+ gemspec.name = 'babypool'
11
+ gemspec.summary = "A ruby thread pool."
12
+ gemspec.description = "Simple thread pool implementation for Ruby green threads."
13
+ gemspec.authors = ["Jeremy T Hopple", "Benjamin P Blackburne"]
14
+ gemspec.email = "jeremy@jthopple.com"
15
+ gemspec.homepage = "http://github.com/jthopple/babypool"
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ test_files_pattern = 'test/**/*_test.rb'
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << 'lib'
25
+ t.pattern = test_files_pattern
26
+ t.verbose = true
27
+ end
28
+
29
+ desc "Run code-coverage analysis using rcov"
30
+ task :rcov do
31
+ rm_rf "coverage"
32
+ files = Dir[test_files_pattern]
33
+ system "rcov -T --sort coverage -x shoulda,rcov -Ilib #{files.join(' ')}"
34
+ end
35
+
36
+ desc 'Default: run tests.'
37
+ task :default => ['test']
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/babypool.gemspec ADDED
@@ -0,0 +1,48 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{babypool}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jeremy T Hopple", "Benjamin P Blackburne"]
12
+ s.date = %q{2009-10-16}
13
+ s.description = %q{Simple thread pool implementation for Ruby green threads.}
14
+ s.email = %q{jeremy@jthopple.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "LICENCE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "babypool.gemspec",
25
+ "lib/babypool.rb",
26
+ "test/babypool_test.rb",
27
+ "test/test_helper.rb"
28
+ ]
29
+ s.homepage = %q{http://github.com/jthopple/babypool}
30
+ s.rdoc_options = ["--charset=UTF-8"]
31
+ s.require_paths = ["lib"]
32
+ s.rubygems_version = %q{1.3.5}
33
+ s.summary = %q{A ruby thread pool.}
34
+ s.test_files = [
35
+ "test/babypool_test.rb",
36
+ "test/test_helper.rb"
37
+ ]
38
+
39
+ if s.respond_to? :specification_version then
40
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
44
+ else
45
+ end
46
+ else
47
+ end
48
+ end
data/lib/babypool.rb ADDED
@@ -0,0 +1,75 @@
1
+ require "thread"
2
+ require "timeout"
3
+
4
+ # Babypool implements a simple thread pool.
5
+ #
6
+ # == Options
7
+ # * :thread_count - The number of threads in the pool. Each thread has its own worker listening for jobs.
8
+ # * :execution_limit - This specifies how long the worker should attempt to complete its job. The job is terminated when it reaches this limt
9
+
10
+ class Babypool
11
+ attr_reader :queue, :busy_threads, :execution_limit
12
+
13
+ # Constructs a Babypool object and initializes the pool of workers
14
+ def initialize(options={})
15
+ @draining = false
16
+ @queue = Queue.new
17
+ @busy_threads = Queue.new
18
+ @execution_limit = options[:execution_limit] || 120
19
+ @thread_count = options[:thread_count] || 10
20
+ @verbose = options[:verbose] || false
21
+
22
+ @threads = Array.new(@thread_count){ Thread.new{ Worker.new(self) } }
23
+ end
24
+
25
+ # Accepts work items, or jobs, as blocks and adds them to the queue.
26
+ def work(*args,&block)
27
+ if (@draining)
28
+ raise "Pool is shutting down and not accepting more work"
29
+ end
30
+
31
+ @queue << [ args, block ]
32
+ end
33
+
34
+ # Returns true if the pool is still busy handling items, false otherwise.
35
+ def busy?
36
+ puts "Busy Check (items in queue: #{@queue.size}, busy threads: #{@busy_threads.size})" if @verbose
37
+ !@queue.empty? || !@busy_threads.empty?
38
+ end
39
+
40
+ # Sends a message to each thread telling it to exit, puts the pool in "draining" mode so that it does not accept any more work, and
41
+ # then waits for each thread to complete.
42
+ def drain
43
+ puts "Draining the pool..." if @verbose
44
+ @threads.each{ work{ Thread.exit } }
45
+ @draining = true
46
+ @threads.each{ |t| t.join }
47
+ puts "Pool Drained." if @verbose
48
+ end
49
+
50
+ # Implements a worker object that listens for work until it is told to exit.
51
+ class Worker
52
+
53
+ # Initializes a worker for a given pool.
54
+ def initialize(pool)
55
+ puts "#{self.to_s} Initialized" if @verbose
56
+ loop do
57
+ # Listen for work on the pool's queue. If the queue is empty, the calling thread is suspended until data is pushed onto the queue.
58
+ args,block = pool.queue.deq
59
+
60
+ # Punch in - add a marker to the busy_thread queue indicating that we're working
61
+ pool.busy_threads.push(:marker)
62
+
63
+ # Do the work by calling the block.
64
+ begin
65
+ Timeout::timeout(pool.execution_limit){ block.call(*args) }
66
+ rescue Timeout::Error => e
67
+ puts "#{self.to_s} - Worker timed out." if @verbose
68
+ end
69
+
70
+ # Punch out - Remove our marker indicating we're finished and idle. Do not suspend if busy_threads queue is empty (the "true" passed in).
71
+ pool.busy_threads.pop(true)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,65 @@
1
+ require 'test/test_helper'
2
+
3
+ class TestBabypool < Test::Unit::TestCase
4
+ should "setup a pool and do some work" do
5
+ pool = Babypool.new(:thread_count => 10, :execution_limit => 2)
6
+
7
+ completed = 0
8
+ 20.times do |job|
9
+ pool.work(job) do
10
+ completed += 1
11
+ end
12
+ end
13
+
14
+ pool.drain
15
+
16
+ assert_equal completed, 20
17
+ end
18
+
19
+ should "only finish jobs that take less than 2 seconds" do
20
+ pool = Babypool.new(:thread_count => 10, :execution_limit => 1)
21
+
22
+ completed = 0
23
+
24
+ # Try 20 jobs, but even jobs should timeout because they take longer than 2 seconds. 10 should complete
25
+ 20.times do |job|
26
+ pool.work(job) do
27
+ sleep(2) if job % 2 == 0
28
+ completed += 1
29
+ end
30
+ end
31
+
32
+ pool.drain
33
+
34
+ assert_equal completed, 10
35
+ end
36
+
37
+ should "have been busy while work was being done" do
38
+ pool = Babypool.new(:thread_count => 10)
39
+ completed = 0
40
+ was_busy = false
41
+
42
+ # Do 10 jobs, each should sleep for a few seconds allowing busy? to return true
43
+ 10.times{ |job| pool.work(job){ sleep(1) } }
44
+
45
+ was_busy = pool.busy?
46
+ pool.drain
47
+ assert was_busy
48
+ end
49
+
50
+ should "have been done by the time we checked if it was busy" do
51
+ pool = Babypool.new(:thread_count => 10)
52
+ completed = 0
53
+ was_busy = false
54
+
55
+ # Do 10 jobs, each should sleep for a few seconds allowing busy? to return true
56
+ 10.times{ |job| pool.work(job){ completed += 1 } }
57
+
58
+ # Sleep for a second to let the jobs finish
59
+ sleep(1)
60
+ was_busy = pool.busy?
61
+
62
+ pool.drain
63
+ assert !was_busy
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
6
+ require 'babypool'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: babypool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy T Hopple
8
+ - Benjamin P Blackburne
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-10-16 00:00:00 -06:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Simple thread pool implementation for Ruby green threads.
18
+ email: jeremy@jthopple.com
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - LICENCE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - VERSION
31
+ - babypool.gemspec
32
+ - lib/babypool.rb
33
+ - test/babypool_test.rb
34
+ - test/test_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/jthopple/babypool
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: A ruby thread pool.
63
+ test_files:
64
+ - test/babypool_test.rb
65
+ - test/test_helper.rb