pipa-threadpool 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Igor Gunko
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,29 @@
1
+ === Introduction
2
+ O HAI! My name is Pool, Thread Pool. Now in Ruby.
3
+
4
+ === Installation
5
+ gem install pipa-threadpool -s gems.github.com
6
+
7
+ === Usage
8
+ pool = ThreadPool.new
9
+ ...
10
+ pool.run(...) do |...|
11
+ ...
12
+ end
13
+ ...
14
+ pool.close
15
+
16
+ === More info in docs
17
+ http://rdoc.info/projects/pipa/threadpool
18
+
19
+ === Bugs & such
20
+ Please report via Github issue tracking.
21
+
22
+ === See also
23
+ http://github.com/pipa/monkeyjob -- Background job runner
24
+ http://github.com/pipa/xmlnuts -- Ruby <-> XML mapping
25
+ http://github.com/pipa/statelogic -- A simple state machine for ActiveRecord
26
+
27
+
28
+ Free hint: If you liek mudkipz^W^Wfeel like generous today you can tip me at http://tipjoy.com/u/pisuka
29
+
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ $KCODE = 'u'
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/testtask'
9
+
10
+ Rake::GemPackageTask.new(Gem::Specification.load('threadpool.gemspec')) do |p|
11
+ p.need_tar = true
12
+ p.need_zip = true
13
+ end
14
+
15
+ Rake::RDocTask.new do |rdoc|
16
+ files =['README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb']
17
+ rdoc.rdoc_files.add(files)
18
+ rdoc.main = "README.rdoc" # page to start on
19
+ rdoc.title = "XmlNuts Documentation"
20
+ rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
21
+ rdoc.options << '--line-numbers' << '--inline-source'
22
+ end
23
+
24
+ Rake::TestTask.new do |t|
25
+ t.test_files = FileList['test/**/*.rb']
26
+ end
27
+
@@ -0,0 +1 @@
1
+ require 'threadpool'
data/lib/threadpool.rb ADDED
@@ -0,0 +1,189 @@
1
+ require 'thread'
2
+ require 'thwait'
3
+ require 'monitor'
4
+
5
+ # This is unsurprisingly a thread pool.
6
+ # It can run your jobs asynchronously.
7
+ # It can grow and shrink depending on the load.
8
+ # Like any good pool it can be... closed!
9
+ class ThreadPool
10
+ DEFAULT_CORE_WORKERS = 4
11
+ DEFAULT_KEEP_ALIVE_TIME = 5
12
+
13
+ class Job #:nodoc:
14
+ def initialize(*args, &handler)
15
+ @args, @handler = args, handler
16
+ end
17
+
18
+ def run
19
+ @handler.call(*@args)
20
+ end
21
+ end
22
+
23
+ @@controllers = ThreadGroup.new
24
+
25
+ # new([[core_workers[, max_workers[, keep_alive_time]],] options]) [{|pool| ... }]
26
+ #
27
+ # === Arguments
28
+ # [+core_workers+] Number of core worker threads. The pool will never shrink below this point.
29
+ # [+max_workers+] Maximum number of worker threads allowed per this pool.
30
+ # The pool will never expand over this limit.
31
+ # Default is +core_workers * 2+
32
+ # [+keep_alive_time+] Time to keep non-core workers alive. Default is 5 sec.
33
+ # [+options+] +:core =>+ _core_workers_,
34
+ # +:max =>+ _max_workers_,
35
+ # +:keep_alive =>+ _keep_alive_time_,
36
+ # +:init_core => false+ to defer initial setup of core workers.
37
+ #
38
+ # When called with a block the pool will be closed upon exit from the block.
39
+ # Graceful +close+ will be used, a non-bang version.
40
+ #
41
+ # === Example:
42
+ # ThreadPool.new 10, 25, 6.7, :init_core => false do |pool|
43
+ # ...
44
+ # end
45
+ def initialize(*args)
46
+ extend MonitorMixin
47
+
48
+ options = args.last.is_a?(Hash) ? args.pop : {}
49
+
50
+ @core_workers = (args[0] || options[:core] || DEFAULT_CORE_WORKERS).to_i
51
+ raise ArgumentError, "core_workers must be a positive integer" if @core_workers <= 0
52
+
53
+ @max_workers = (args[1] || options[:max] || @core_workers * 2).to_i
54
+ raise ArgumentError, "max_workers must be >= core_workers" if @max_workers < @core_workers
55
+
56
+ @keep_alive_time = (args[2] || options[:keep_alive] || DEFAULT_KEEP_ALIVE_TIME).to_f
57
+ raise ArgumentError, "keep_alive_time must be a non-negative real number" if @keep_alive_time < 0
58
+
59
+ @workers, @jobs = ThreadGroup.new, Queue.new
60
+
61
+ @worker_routine = proc do
62
+ while job = @jobs.pop
63
+ job.run rescue nil
64
+ end
65
+ end
66
+
67
+ @controller = Thread.new do
68
+ loop do
69
+ sleep(@keep_alive_time)
70
+ break if @dead
71
+ synchronize do
72
+ n = @jobs.num_waiting - @core_workers
73
+ stop_workers([n / 2, 1].max) if n >= 0
74
+ end
75
+ end
76
+ end
77
+ @@controllers.add(@controller)
78
+
79
+ create_workers(@core_workers) if options.fetch(:init_core, true)
80
+
81
+ begin
82
+ yield self
83
+ ensure
84
+ shutdown
85
+ end if block_given?
86
+ end
87
+
88
+ # live? => boolean
89
+ #
90
+ # Pool is live when it's not dead.
91
+ # Pool is dead when it's closed.
92
+ def live?
93
+ synchronize { !@dead }
94
+ end
95
+
96
+ # run([arg1[, arg2[, ...]]]) {|[arg1[, arg2[, ...]]]| ... } -> pool
97
+ #
98
+ # Schedule the block to run asynchronously on a worker thread. Return immediately.
99
+ # Any arguments passed to this method will be passed to the block.
100
+ #
101
+ # When there are no idle workers the pool will grow.
102
+ # When max pool size is reached the job will be queued up until better times.
103
+ #
104
+ # === Example:
105
+ # pool.run('go to hell') do |greeting|
106
+ # puts greeting
107
+ # end
108
+ def run(*args, &block)
109
+ run_core(true, *args, &block)
110
+ end
111
+
112
+ # try_run([arg1[, arg2[, ...]]]) {|[arg1[, arg2[, ...]]]| ... } -> pool or nil
113
+ #
114
+ # Try to run the block asynchronously on a worker thread (see +run+).
115
+ # If there are no idle workers immediately available and the pool reached its maximum size,
116
+ # then do not enqueue the job and return +nil+.
117
+ #
118
+ # === Example:
119
+ # puts 'zomg' unless pool.try_run('go to hell') {|greeting| puts greeting }
120
+ def try_run(*args, &block)
121
+ run_core(false, *args, &block)
122
+ end
123
+
124
+ # close
125
+ #
126
+ # Rape me gently. Waits until all the jobs are done and destroys the pool.
127
+ def close
128
+ _sync do
129
+ @dead = true
130
+ @controller.run
131
+ stop_workers(@workers.list.size)
132
+ end
133
+ ThreadsWait.all_waits(@controller, *@workers.list)
134
+ self
135
+ end
136
+
137
+ # close!
138
+ #
139
+ # Rape me hard. Instantly kills the workers. Ensure blocks will be called though (last prayer on).
140
+ def close!
141
+ _sync do
142
+ @dead = true
143
+ @controller.run
144
+ @workers.list.each {|w| w.kill }
145
+ end
146
+ self
147
+ end
148
+
149
+ private
150
+
151
+ def run_core(enqueue, *args, &block) #:nodoc:
152
+ raise ArgumentError, 'block must be provided' unless block_given?
153
+ _sync do
154
+ if @jobs.num_waiting == 0
155
+ if @workers.list.size < @max_workers
156
+ create_worker
157
+ else
158
+ return nil unless enqueue
159
+ end
160
+ end
161
+ @jobs.push(Job.new(*args, &block))
162
+ end
163
+ self
164
+ end
165
+
166
+ def _sync #:nodoc:
167
+ synchronize do
168
+ check_state
169
+ yield
170
+ end
171
+ end
172
+
173
+ def check_state #:nodoc:
174
+ raise "pool's closed" if @dead
175
+ end
176
+
177
+ def create_worker #:nodoc:
178
+ @workers.add(Thread.new(&@worker_routine))
179
+ end
180
+
181
+ def create_workers(n) #:nodoc:
182
+ n.times { create_worker }
183
+ end
184
+
185
+ def stop_workers(n) #:nodoc:
186
+ n.times { @jobs << nil }
187
+ end
188
+ end
189
+
@@ -0,0 +1,18 @@
1
+ require 'test/unit'
2
+ require 'lib/threadpool'
3
+
4
+
5
+ class ThreadPoolTest < Test::Unit::TestCase
6
+ def setup
7
+ end
8
+
9
+ def test_me
10
+ @pool = ThreadPool.new(2, 15, 1)
11
+ n = 0
12
+ p = proc {|x| n += x }
13
+ 100.times {|i| @pool.run(i, &p) }
14
+ @pool.close
15
+ assert_equal 4950, n
16
+ end
17
+ end
18
+
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pipa-threadpool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Igor Gunko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-06 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: tekmon@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - MIT-LICENSE
25
+ files:
26
+ - README.rdoc
27
+ - MIT-LICENSE
28
+ - Rakefile
29
+ - lib/threadpool.rb
30
+ - lib/pipa-threadpool.rb
31
+ has_rdoc: true
32
+ homepage: http://github.com/pipa/threadpool
33
+ post_install_message:
34
+ rdoc_options:
35
+ - --line-numbers
36
+ - --main
37
+ - README.rdoc
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 1.2.0
56
+ signing_key:
57
+ specification_version: 2
58
+ summary: Thread pool for Ruby
59
+ test_files:
60
+ - test/threadpool_test.rb