objectpool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.md
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ *.gem
23
+ .yardoc
24
+ doc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kriss Kowalik
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.
@@ -0,0 +1,47 @@
1
+ # Object Pool
2
+
3
+ Little bit different and magical implementation of thread pool pattern in ruby.
4
+
5
+ ## Installation
6
+
7
+ gem install objectpool
8
+
9
+ ## Examples
10
+
11
+ require 'objectpool'
12
+
13
+ class MyClass
14
+ pool_methods :one, :two
15
+
16
+ def one; sleep 1; end
17
+ def two(arg) sleep 2; return arg end
18
+ end
19
+
20
+ pool = MyClass.to_pool(10).new
21
+ pool.one.async # the `one` method will be called asynchronously
22
+ pool.two(2).sync # this will be called synchronously and it will return result
23
+ # Result can be handled inside the asynchronous block
24
+ pool.two(2).async {|result| puts result }
25
+
26
+ # Now you can wait for results
27
+ pool.wait
28
+
29
+ # "Join" infinity loop with main thread...
30
+ pool.join
31
+
32
+ # ... or close this object pool
33
+ pool.close
34
+
35
+ ## Note on Patches/Pull Requests
36
+
37
+ * Fork the project.
38
+ * Make your feature addition or bug fix.
39
+ * Add tests for it. This is important so I don't break it in a
40
+ future version unintentionally.
41
+ * Commit, do not mess with rakefile, version, or history.
42
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
43
+ * Send me a pull request. Bonus points for topic branches.
44
+
45
+ ## Copyright
46
+
47
+ Copyright (c) 2010 Kriss Kowalik. See LICENSE for details.
@@ -0,0 +1,92 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "objectpool"
8
+ gem.summary = %Q{Magical thread pool implementation}
9
+ gem.description = %Q{Little bit different and magical implementation of thread pool pattern in ruby.}
10
+ gem.email = "kriss.kowalik@gmail.com"
11
+ gem.homepage = "http://github.com/kriss/objectpool"
12
+ gem.authors = ["Kriss Kowalik"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'spec/rake/spectask'
23
+ Spec::Rake::SpecTask.new(:spec) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.spec_files = FileList['spec/**/*_spec.rb']
26
+ end
27
+
28
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
29
+ spec.libs << 'lib' << 'spec'
30
+ spec.pattern = 'spec/**/*_spec.rb'
31
+ spec.rcov = true
32
+ end
33
+
34
+ task :spec => :check_dependencies
35
+
36
+ task :default => :spec
37
+
38
+ begin
39
+ require 'yard'
40
+ YARD::Rake::YardocTask.new do |t|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+ title = "Leech #{version}"
43
+ t.files = ['lib/**/*.rb', 'README*']
44
+ t.options = ['--title', title, '--markup', 'markdown']
45
+ end
46
+ rescue LoadError
47
+ task :yard do
48
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
49
+ end
50
+ end
51
+
52
+ desc "Benchmarking reports"
53
+ task :benchmark do
54
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
55
+ require 'rubygems'
56
+ require 'objectpool'
57
+ require 'benchmark'
58
+
59
+ class BenchObject
60
+ pool_methods :test
61
+
62
+ def test(arg)
63
+ sleep 0.01
64
+ end
65
+ end
66
+
67
+ p5 = BenchObject.to_pool(5)
68
+ p10 = BenchObject.to_pool(10)
69
+ p20 = BenchObject.to_pool(20)
70
+ p35 = BenchObject.to_pool(35)
71
+
72
+ op5, op10, op20, op35 = nil, nil, nil, nil
73
+
74
+ puts "-- Preparation time -------------------------------------"
75
+ Benchmark.bm(7) do |x|
76
+ x.report("5 workers ") { op5 = p5.new }
77
+ x.report("10 workers") { op10 = p10.new }
78
+ x.report("20 workers") { op20 = p20.new }
79
+ x.report("35 workers") { op35 = p35.new }
80
+ end
81
+
82
+ n = 5000
83
+
84
+ puts "-- Execution time (5000 calls) --------------------------"
85
+ Benchmark.bm(7) do |x|
86
+ x.report("5 workers ") { n.times {|n| op5.test(n).async };}
87
+ x.report("10 workers") { n.times {|n| op10.test(n).async };}
88
+ x.report("20 workers") { n.times {|n| op20.test(n).async };}
89
+ x.report("35 workers") { n.times {|n| op35.test(n).async };}
90
+ end
91
+ n = 50000
92
+ end
@@ -0,0 +1,250 @@
1
+ require 'logger'
2
+ require 'timeout'
3
+
4
+ begin
5
+ require 'fastthread'
6
+ rescue LoadError
7
+ require 'thread'
8
+ $stderr.puts("The fastthread gem not found. Using standard ruby threads.")
9
+ end
10
+
11
+ module ObjectPool
12
+ class Error < StandardError; end
13
+ class StopWorker < Error; end
14
+
15
+ def self.create(klass, size, opts={})
16
+ (pool_klass = Class.new(klass)).class_eval do
17
+ @__op__size = size
18
+ @__op__orig_class = klass
19
+ @__op__queue_limit = opts[:limit].to_i
20
+ extend ClassMethods
21
+ include InstanceMethods
22
+ __op__redefine_pool_methods
23
+ end
24
+ pool_klass
25
+ end
26
+
27
+ module InstanceMethods
28
+ def initialize(*args, &block)
29
+ @__op__mx = Mutex.new
30
+ @__op__cv = ConditionVariable.new
31
+ @__op__queue = Queue.new
32
+ @__op__workers = ThreadGroup.new
33
+
34
+ self.class.pool_size.times do
35
+ __op__create_worker(*args, &block)
36
+ end
37
+ end
38
+
39
+ def join
40
+ sleep 0.01 until @__op__workers.list.all? {|w| !w.alive?}
41
+ end
42
+
43
+ def wait
44
+ sleep 0.01 until @__op__mx.synchronize { @__op__queue.empty? && @__op__workers.list.all? {|w| !w.busy?} }
45
+ end
46
+
47
+ def size
48
+ @__op__workers.list.size
49
+ end
50
+
51
+ def busy?
52
+ @__op__mx.synchronize { @__op__queue.size < @__op__workers.list.size }
53
+ end
54
+
55
+ def close
56
+ @__op__workers.list.each {|worker| worker.raise(StopWorker)}
57
+ @__op__workers = ThreadGroup.new
58
+ end
59
+
60
+ private
61
+
62
+ def __op__create_worker(*args, &block)
63
+ @__op__workers.add(Thread.new do
64
+ executor = self.class.pool_orig_class.new(*args, &block)
65
+ action = nil
66
+ loop do
67
+ begin
68
+ @__op__mx.synchronize do
69
+ @__op__cv.wait(@__op__mx)
70
+ if action = @__op__queue.shift
71
+ Thread.current.lock_at_pool!
72
+ @__op__mx.unlock
73
+ result = executor.send(action.method.to_sym, action.args, &action.block)
74
+ action.complete!(result)
75
+ @__op__mx.lock
76
+ Thread.current.release_at_pool!
77
+ end
78
+ end
79
+ rescue StopWorker
80
+ break
81
+ end
82
+ end
83
+ end)
84
+ end
85
+
86
+ def __op__call_at_pool(meth, *args, &block)
87
+ if self.class.pool_queue_limit > 0
88
+ sleep 0.01 until @__op__mx.synchronize { self.class.pool_queue_limit > @__op__queue.size }
89
+ end
90
+ Method.new(meth, args, block, @__op__mx, @__op__cv, @__op__queue)
91
+ end
92
+ end
93
+
94
+ class Method
95
+ class Error < StandardError; end
96
+ class TerminatedError < Error; end
97
+
98
+ attr_reader :method, :args, :block, :callback
99
+
100
+ def initialize(method, args, block, mx, cv, queue)
101
+ @method, @args, @block = method, args, block
102
+ @mx, @cv, @queue = mx, cv, queue
103
+ @complete = false
104
+ @result = nil
105
+ end
106
+
107
+ def complete?
108
+ @complete
109
+ end
110
+
111
+ def complete!(*opts)
112
+ @callback.call(*opts)
113
+ @complete = true
114
+ end
115
+
116
+ def async(&block)
117
+ call_at_pool(false, &block)
118
+ end
119
+ alias_method :asynchronous, :async
120
+
121
+ def sync
122
+ call_at_pool(true)
123
+ end
124
+ alias_method :synchronous, :sync
125
+
126
+ private
127
+
128
+ def terminated?
129
+ !!@terminated
130
+ end
131
+
132
+ def call_at_pool(synchronous=false, &block)
133
+ unless terminated?
134
+ @callback = block_given? && !synchronous ? block : proc {|r| @result = r}
135
+ @mx.synchronize do
136
+ @queue << self
137
+ @cv.signal
138
+ @terminated = true
139
+ end
140
+ if synchronous
141
+ sleep 0.01 until complete?
142
+ return @result
143
+ end
144
+ return self
145
+ end
146
+ raise TerminatedError, "Can't run terminated method"
147
+ end
148
+ end
149
+
150
+ class Thread < ::Thread
151
+ def busy?
152
+ !!self[:busy]
153
+ end
154
+
155
+ def lock_at_pool!
156
+ self[:busy] = true
157
+ end
158
+
159
+ def release_at_pool!
160
+ self[:busy] = false
161
+ end
162
+ end
163
+
164
+ module ClassMethods
165
+ def __op__redefine_pool_methods
166
+ pool_methods.each do |method|
167
+ class_eval <<-EVAL
168
+ def #{method.to_s}(*args, &block)
169
+ __op__call_at_pool(:#{method.to_s}.to_sym, args, block)
170
+ end
171
+ EVAL
172
+ end
173
+ end
174
+
175
+ def pool?
176
+ true
177
+ end
178
+
179
+ def pool_size
180
+ @__op__size
181
+ end
182
+
183
+ def pool_orig_class
184
+ @__op__orig_class
185
+ end
186
+
187
+ def pool_queue_limit
188
+ @__op__queue_limit
189
+ end
190
+ end
191
+ end
192
+
193
+ class Class
194
+ def to_pool(size, opts={})
195
+ ObjectPool.create(self, size, opts)
196
+ end
197
+
198
+ def pool?
199
+ false
200
+ end
201
+
202
+ def pool_methods(*methods)
203
+ if methods.size > 0
204
+ __op__add_pool_methods(methods)
205
+ else
206
+ unless @pool_methods
207
+ @pool_methods ||= []
208
+ self.ancestors.each do |ancestor|
209
+ @pool_methods.concat(ancestor.pool_methods) if ancestor.is_a?(Class)
210
+ end
211
+ end
212
+ end
213
+ @pool_methods
214
+ end
215
+
216
+ protected
217
+
218
+ def __op__add_pool_methods(*methods)
219
+ if pool_methods && methods.size > 0
220
+ case m = methods.first
221
+ when Array
222
+ m.uniq.each { |method| __op__add_pool_methods(method) }
223
+ when Symbol, String
224
+ @pool_methods << m unless pool_methods.include?(m)
225
+ else
226
+ raise ArgumentError, "Invalid method name"
227
+ end
228
+ end
229
+ end
230
+
231
+ def __op__remove_pool_methods(*methods)
232
+ if pool_methods && methods.size > 0
233
+ case m = methods.first
234
+ when Array
235
+ m.uniq.each { |method| __op__remove_pool_methods(method) }
236
+ when Symbol, String
237
+ @pool_methods.delete(m)
238
+ else
239
+ raise ArgumentError, "Invalid method name"
240
+ end
241
+ end
242
+ end
243
+ end
244
+
245
+ class Object
246
+ def pool?
247
+ false
248
+ end
249
+ end
250
+
@@ -0,0 +1,96 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Core extensions" do
4
+ it "should add #to_pool instance method to Class" do
5
+ Class.new.should respond_to :to_pool
6
+ end
7
+
8
+ it "should add #pool? instance method to Class and Object" do
9
+ Class.new.should respond_to :pool?
10
+ Object.new.should respond_to :pool?
11
+ Class.new.pool?.should == false
12
+ Object.new.pool?.should == false
13
+ end
14
+ end
15
+
16
+ describe "An Class" do
17
+ context "on #to_pool call" do
18
+ before do
19
+ @test_class = Class.new(Object)
20
+ @pool = @test_class.to_pool(10)
21
+ end
22
+
23
+ it "should return itself ObjectPool powered when #to_pool method is called" do
24
+ @pool.pool?.should == true
25
+ @pool.should include ObjectPool::InstanceMethods
26
+ @pool.ancestors.should include @test_class
27
+ end
28
+
29
+ it "should save pool size in returned object" do
30
+ @pool.should respond_to :pool_size
31
+ @pool.pool_size.should == 10
32
+ end
33
+
34
+ it "should save original class in returned object" do
35
+ @pool.should respond_to :pool_orig_class
36
+ @pool.pool_orig_class.should == @test_class
37
+ end
38
+ end
39
+ end
40
+
41
+ describe "An ObjectPool powered class" do
42
+ before do
43
+ unless defined?(TestClass1)
44
+ class TestClass1
45
+ pool_methods :hello
46
+ def hello(args)
47
+ "Hello #{args}!"
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ context "on create" do
54
+ it "should start all workers in separated threads" do
55
+ @pool = TestClass1.to_pool(10)
56
+ @obj = @pool.new
57
+ @obj.size.should == @obj.class.pool_size
58
+ end
59
+ end
60
+
61
+ it "should allow to synchronously execute method in pool" do
62
+ @pool = TestClass1.to_pool(10)
63
+ @obj = @pool.new
64
+ result1 = @obj.hello("world").sync
65
+ result2 = @obj.hello("again").sync
66
+ result1.should == "Hello world!"
67
+ result2.should == "Hello again!"
68
+ end
69
+
70
+ it "should allow to asynchronously execute method in pool" do
71
+ @pool = TestClass1.to_pool(10)
72
+ @obj = @pool.new
73
+ results = []
74
+ 20.times do |n|
75
+ @obj.hello(n).async {|result| results << result; }
76
+ end
77
+ @obj.wait
78
+ results.size.should == 20
79
+ results.first.should match /^Hello \d+\!$/
80
+ end
81
+
82
+ it "should respect queue limit on methods execution" do
83
+ @pool = TestClass1.to_pool(20, :limit => 1)
84
+ @obj = @pool.new
85
+ @num = 0
86
+ lambda do
87
+ Timeout::timeout(0.3) do
88
+ 100.times do |n|
89
+ @obj.hello(n).async { sleep 2 }
90
+ @num += 1
91
+ end
92
+ end
93
+ end.should raise_error(Timeout::Error)
94
+ @num.should == @pool.pool_size + @pool.pool_queue_limit
95
+ end
96
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rubygems'
4
+ require 'objectpool'
5
+ require 'spec'
6
+ require 'spec/autorun'
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: objectpool
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Kriss Kowalik
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-28 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
31
+ version: 1.2.9
32
+ type: :development
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: yard
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ description: Little bit different and magical implementation of thread pool pattern in ruby.
47
+ email: kriss.kowalik@gmail.com
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ extra_rdoc_files:
53
+ - LICENSE
54
+ - README.md
55
+ files:
56
+ - .document
57
+ - .gitignore
58
+ - LICENSE
59
+ - README.md
60
+ - Rakefile
61
+ - lib/objectpool.rb
62
+ - spec/objectpool_spec.rb
63
+ - spec/spec.opts
64
+ - spec/spec_helper.rb
65
+ has_rdoc: true
66
+ homepage: http://github.com/kriss/objectpool
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options:
71
+ - --charset=UTF-8
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.6
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Magical thread pool implementation
95
+ test_files:
96
+ - spec/objectpool_spec.rb
97
+ - spec/spec_helper.rb