pipa-threadpool 0.2.1

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/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