threadz 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -32,7 +32,17 @@ end
32
32
 
33
33
  desc "Run all test cases"
34
34
  task 'spec' do |task|
35
- exec 'spec -c spec/*.rb'
35
+ exec 'spec -c -f n spec/*.rb spec/basic/*.rb'
36
+ end
37
+
38
+ desc "Run all performance-oriented test cases"
39
+ task 'spec:performance' do |task|
40
+ exec 'spec -c -f n -t 30.0 spec/spec_helper.rb spec/performance/*.rb'
41
+ end
42
+
43
+ desc "Run *all* specs"
44
+ task 'spec:all' do |task|
45
+ exec 'spec -c -f n -t 30.0 spec/spec_helper.rb spec/**/*.rb'
36
46
  end
37
47
 
38
48
  desc "Run all test cases 10 times (or n times)"
@@ -114,8 +124,8 @@ begin
114
124
  require 'jeweler'
115
125
  Jeweler::Tasks.new do |gemspec|
116
126
  gemspec.name = "threadz"
117
- gemspec.summary = "A Ruby threadpool library to handle threadpools and make batch jobs easier."
118
- #gemspec.description = "Longer description?"
127
+ gemspec.summary = "An easy Ruby threadpool library."
128
+ gemspec.description = "A Ruby threadpool library to handle threadpools and make batch jobs easier."
119
129
  gemspec.email = "nanodeath@gmail.com"
120
130
  gemspec.homepage = "http://github.com/nanodeath/threadz"
121
131
  gemspec.authors = ["Max Aller"]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.3
@@ -1,6 +1,10 @@
1
1
  require 'thread'
2
2
 
3
3
  module Threadz
4
+ # Related to, or at least named after, Java's AtomicInteger.
5
+ # Provides a thread-safe integer counter thing.
6
+ # The code used in this file, while slightly verbose, is to optimize
7
+ # performance. Avoiding additional method calls and blocks is preferred.
4
8
  class AtomicInteger
5
9
  def initialize(value)
6
10
  @value = value
@@ -20,9 +24,17 @@ module Threadz
20
24
  end
21
25
 
22
26
  def decrement(amount=1)
27
+ # We could refactor and just call set here, but it's faster just to write
28
+ # the extra two lines.
23
29
  @mutex.lock
24
30
  @value -= amount
25
31
  @mutex.unlock
26
32
  end
33
+
34
+ def set(value)
35
+ @mutex.lock
36
+ @value = value
37
+ @mutex.unlock
38
+ end
27
39
  end
28
40
  end
@@ -55,24 +55,20 @@ module Threadz
55
55
  # +:timeout+:: If specified, will only wait for at least this many seconds
56
56
  # for the batch to finish. Typically used with #completed?
57
57
  def wait_until_done(opts={})
58
- return if completed?
59
-
60
58
  raise "Threadz: thread deadlocked because batch job was never started" if @latent && !@started
61
59
 
62
60
  timeout = opts.key?(:timeout) ? opts[:timeout] : 0
63
- #raise "Timeout not supported at the moment" if timeout
64
-
65
- @sleeper.wait(timeout)
61
+ @sleeper.wait(timeout) unless completed?
66
62
  end
67
63
 
68
- # Returns true iff there are no unfinished jobs in the queue.
64
+ # Returns true iff there are no jobs outstanding.
69
65
  def completed?
70
66
  return @jobs_count.value == 0
71
67
  end
72
68
 
73
69
  # If this is a latent batch, start processing all of the jobs in the queue.
74
70
  def start
75
- Thread.exclusive do # in case another thread tries to push new jobs onto the queue while we're starting
71
+ @job_lock.synchronize { # in case another thread tries to push new jobs onto the queue while we're starting
76
72
  if @latent
77
73
  @started = true
78
74
  until @job_queue.empty?
@@ -82,19 +78,13 @@ module Threadz
82
78
  else
83
79
  return false
84
80
  end
85
- end
81
+ }
86
82
  end
87
83
 
88
84
  # Execute a given block when the batch has finished processing. If the batch
89
85
  # has already finished executing, execute immediately.
90
86
  def when_done(&block)
91
- @job_lock.lock
92
- if completed?
93
- block.call
94
- else
95
- @when_done_blocks << block
96
- end
97
- @job_lock.unlock
87
+ @job_lock.synchronize { completed? ? block.call : @when_done_blocks << block }
98
88
  end
99
89
 
100
90
  private
@@ -110,11 +100,13 @@ module Threadz
110
100
  @threadpool.process do
111
101
  job.call
112
102
  # Lock in case we get two threads at the "fork in the road" at the same time
113
- @job_lock.lock
103
+ # Note: locking here actually creates undesirable behavior. Still investigating why,
104
+ # seems like it should be useful.
105
+ #@job_lock.lock
114
106
  @jobs_count.decrement
115
107
  # fork in the road
116
108
  handle_done if completed?
117
- @job_lock.unlock
109
+ #@job_lock.unlock
118
110
  end
119
111
  end
120
112
  end
@@ -80,7 +80,8 @@ module Threadz
80
80
 
81
81
  # Kill a thread after it completes its current job
82
82
  def kill_thread
83
- @queue.unshift(Directive::SUICIDE_PILL)
83
+ # TODO: ideally this would be unshift, but Queues don't have that. Come up with an alternative.
84
+ @queue << Directive::SUICIDE_PILL
84
85
  end
85
86
 
86
87
  # This thread watches over the pool and allocated and deallocates threads
@@ -0,0 +1,41 @@
1
+ require 'net/http'
2
+
3
+ describe Threadz::ThreadPool do
4
+ before(:each) do
5
+ @T = Threadz::ThreadPool.new
6
+ end
7
+
8
+ it "should perform well for IO jobs" do
9
+ urls = []
10
+ urls << "http://www.google.com/" << "http://www.yahoo.com/" << 'http://www.microsoft.com/'
11
+ urls << "http://www.cnn.com/" << "http://slashdot.org/" << "http://www.mozilla.org/"
12
+ urls << "http://www.ubuntu.com/" << "http://github.com/"
13
+ time_single_threaded = Time.now
14
+
15
+ begin
16
+ (urls * 2).each do |url|
17
+ response = Net::HTTP.get_response(URI.parse(url))
18
+ body = response.body
19
+ end
20
+
21
+ time_single_threaded = Time.now - time_single_threaded
22
+
23
+ time_multi_threaded = Time.now
24
+ b = @T.new_batch
25
+ (urls * 2).each do |url|
26
+ b << Proc.new do
27
+ response = Net::HTTP.get_response(URI.parse(url))
28
+ body = response.body
29
+ end
30
+ end
31
+
32
+ b.wait_until_done
33
+ time_multi_threaded = Time.now - time_multi_threaded
34
+
35
+ time_multi_threaded.should < time_single_threaded
36
+
37
+ rescue SocketError
38
+ pending "pending working internet connection"
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'timeout'
2
+
3
+ describe Threadz::Batch do
4
+ before(:each) do
5
+ @T = Threadz::ThreadPool.new(:initial_size => 25)
6
+ end
7
+
8
+ it "shouldn't fail under load" do
9
+ jobs = 1000
10
+ times_per_job = 1000
11
+ i = ::Threadz::AtomicInteger.new(0)
12
+
13
+ b1 = @T.new_batch(:latent => true)
14
+ b2 = @T.new_batch(:latent => true)
15
+
16
+ jobs.times do
17
+ b1 << lambda { times_per_job.times { i.increment } }
18
+ b2 << lambda { times_per_job.times { i.decrement } }
19
+ end
20
+
21
+ b1.start
22
+ b2.start
23
+
24
+ b1.wait_until_done
25
+ b2.wait_until_done
26
+
27
+ i.value.should == 0
28
+ end
29
+ end
@@ -1,8 +1,6 @@
1
1
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))
2
2
  require 'spec_helper'
3
3
 
4
- require 'net/http'
5
-
6
4
  describe Threadz do
7
5
  describe Threadz::ThreadPool do
8
6
  before(:each) do
@@ -26,40 +24,6 @@ describe Threadz do
26
24
  lambda { @T.new_batch(:latent => true) }.should_not raise_error
27
25
  end
28
26
 
29
- it "should perform well for IO jobs" do
30
- urls = []
31
- urls << "http://www.google.com/" << "http://www.yahoo.com/" << 'http://www.microsoft.com/'
32
- urls << "http://www.cnn.com/" << "http://slashdot.org/" << "http://www.mozilla.org/"
33
- urls << "http://www.ubuntu.com/" << "http://github.com/"
34
- time_single_threaded = Time.now
35
-
36
- begin
37
- (urls * 3).each do |url|
38
- response = Net::HTTP.get_response(URI.parse(url))
39
- body = response.body
40
- end
41
-
42
- time_single_threaded = Time.now - time_single_threaded
43
-
44
- time_multi_threaded = Time.now
45
- b = @T.new_batch
46
- (urls * 3).each do |url|
47
- b << Proc.new do
48
- response = Net::HTTP.get_response(URI.parse(url))
49
- body = response.body
50
- end
51
- end
52
-
53
- b.wait_until_done
54
- time_multi_threaded = Time.now - time_multi_threaded
55
-
56
- time_multi_threaded.should < time_single_threaded
57
-
58
- rescue SocketError
59
- pending "pending working internet connection"
60
- end
61
- end
62
-
63
27
  describe Threadz::Batch do
64
28
  it "should support jobs" do
65
29
  i = 0
@@ -108,7 +72,7 @@ describe Threadz do
108
72
  @i.should == 3
109
73
  end
110
74
 
111
- it "should support latent option correctly" do
75
+ it "should support latent option" do
112
76
  i = 0
113
77
  b = @T.new_batch(:latent => true)
114
78
  b << lambda { i += 1 }
@@ -172,11 +136,11 @@ describe Threadz do
172
136
  end
173
137
 
174
138
  it "should support 'when_done'" do
175
- i = 0
139
+ i = ::Threadz::AtomicInteger.new(0)
176
140
  when_done_executed = false
177
141
  b = @T.new_batch(:latent => true)
178
142
 
179
- 100.times { b << lambda { i += 1 } }
143
+ 100.times { b << lambda { i.increment } }
180
144
 
181
145
  b.when_done { when_done_executed = true }
182
146
 
@@ -184,8 +148,12 @@ describe Threadz do
184
148
 
185
149
  b.start
186
150
 
187
- sleep(0.1)
151
+ Timeout::timeout(10) do
152
+ sleep(0.1) until i.value == 100
153
+ end
188
154
 
155
+ i.value.should == 100
156
+
189
157
  b.completed?.should be_true
190
158
  when_done_executed.should be_true
191
159
  end
@@ -193,11 +161,11 @@ describe Threadz do
193
161
  it "should call 'when_done' immediately when batch is already done" do
194
162
  i = 0
195
163
  when_done_executed = false
196
- b = @T.new_batch
164
+ b = @T.new_batch :latent => true
197
165
 
198
- Thread.exclusive do
199
- 100.times { b << lambda { i += 1 } }
200
- end
166
+ 100.times { b << lambda { i += 1 } }
167
+
168
+ b.start
201
169
 
202
170
  b.wait_until_done
203
171
 
@@ -211,42 +179,19 @@ describe Threadz do
211
179
  it "should support multiple 'when_done' blocks" do
212
180
  i = 0
213
181
  when_done_executed = 0
214
- b = @T.new_batch
182
+ b = @T.new_batch :latent => true
215
183
 
216
- # We're not testing what happens when 'when_done' is called and
217
- # the batch is already finished, so wrapping in Thread#exclusive
218
- Thread.exclusive do
219
- 100.times { b << lambda { i += 1 } }
220
- end
184
+ 100.times { b << lambda { i += 1 } }
221
185
 
222
- 3.times { b.when_done { when_done_executed += 1 } }
186
+ 10.times { b.when_done { when_done_executed += 1 } }
223
187
 
224
- sleep(0.1)
225
188
 
226
- b.completed?.should be_true
227
- when_done_executed.should == 3
228
- end
189
+ b.start
229
190
 
230
- it "shouldn't fail under load" do
231
- jobs = 1000
232
- times_per_job = 100
233
- i = ::Threadz::AtomicInteger.new(0)
234
-
235
- b1 = @T.new_batch(:latent => true)
236
- b2 = @T.new_batch(:latent => true)
237
-
238
- jobs.times do
239
- b1 << lambda { times_per_job.times { i.increment } }
240
- b2 << lambda { times_per_job.times { i.decrement } }
241
- end
242
-
243
- b1.start
244
- b2.start
245
-
246
- b1.wait_until_done
247
- b2.wait_until_done
191
+ b.wait_until_done
248
192
 
249
- i.value.should == 0
193
+ b.completed?.should be_true
194
+ when_done_executed.should == 10
250
195
  end
251
196
  end
252
197
  end
@@ -5,11 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{threadz}
8
- s.version = "0.1.1"
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 = ["Max Aller"]
12
- s.date = %q{2009-12-19}
12
+ s.date = %q{2010-02-01}
13
+ s.description = %q{A Ruby threadpool library to handle threadpools and make batch jobs easier.}
13
14
  s.email = %q{nanodeath@gmail.com}
14
15
  s.extra_rdoc_files = [
15
16
  "README.rdoc"
@@ -28,6 +29,8 @@ Gem::Specification.new do |s|
28
29
  "lib/threadz/sleeper.rb",
29
30
  "lib/threadz/thread_pool.rb",
30
31
  "spec/atomic_integer_spec.rb",
32
+ "spec/basic/thread_pool_spec.rb",
33
+ "spec/performance/batch_spec.rb",
31
34
  "spec/spec_helper.rb",
32
35
  "spec/threadz_spec.rb",
33
36
  "threadz.gemspec"
@@ -36,11 +39,13 @@ Gem::Specification.new do |s|
36
39
  s.rdoc_options = ["--charset=UTF-8"]
37
40
  s.require_paths = ["lib"]
38
41
  s.rubygems_version = %q{1.3.5}
39
- s.summary = %q{A Ruby threadpool library to handle threadpools and make batch jobs easier.}
42
+ s.summary = %q{An easy Ruby threadpool library.}
40
43
  s.test_files = [
41
44
  "spec/atomic_integer_spec.rb",
42
45
  "spec/threadz_spec.rb",
43
- "spec/spec_helper.rb"
46
+ "spec/performance/batch_spec.rb",
47
+ "spec/spec_helper.rb",
48
+ "spec/basic/thread_pool_spec.rb"
44
49
  ]
45
50
 
46
51
  if s.respond_to? :specification_version then
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: threadz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Aller
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-12-19 00:00:00 -08:00
12
+ date: 2010-02-01 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description:
16
+ description: A Ruby threadpool library to handle threadpools and make batch jobs easier.
17
17
  email: nanodeath@gmail.com
18
18
  executables: []
19
19
 
@@ -35,6 +35,8 @@ files:
35
35
  - lib/threadz/sleeper.rb
36
36
  - lib/threadz/thread_pool.rb
37
37
  - spec/atomic_integer_spec.rb
38
+ - spec/basic/thread_pool_spec.rb
39
+ - spec/performance/batch_spec.rb
38
40
  - spec/spec_helper.rb
39
41
  - spec/threadz_spec.rb
40
42
  - threadz.gemspec
@@ -65,8 +67,10 @@ rubyforge_project:
65
67
  rubygems_version: 1.3.5
66
68
  signing_key:
67
69
  specification_version: 3
68
- summary: A Ruby threadpool library to handle threadpools and make batch jobs easier.
70
+ summary: An easy Ruby threadpool library.
69
71
  test_files:
70
72
  - spec/atomic_integer_spec.rb
71
73
  - spec/threadz_spec.rb
74
+ - spec/performance/batch_spec.rb
72
75
  - spec/spec_helper.rb
76
+ - spec/basic/thread_pool_spec.rb