zeevex_concurrency 0.0.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/.gitignore +17 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/zeevex_concurrency/delay.rb +50 -0
- data/lib/zeevex_concurrency/delayed.rb +233 -0
- data/lib/zeevex_concurrency/event_loop.rb +154 -0
- data/lib/zeevex_concurrency/future.rb +60 -0
- data/lib/zeevex_concurrency/logging.rb +7 -0
- data/lib/zeevex_concurrency/nil_logger.rb +7 -0
- data/lib/zeevex_concurrency/promise.rb +32 -0
- data/lib/zeevex_concurrency/synchronized.rb +46 -0
- data/lib/zeevex_concurrency/thread_pool.rb +346 -0
- data/lib/zeevex_concurrency/version.rb +3 -0
- data/lib/zeevex_concurrency.rb +29 -0
- data/script/repl +10 -0
- data/script/testall +2 -0
- data/spec/delay_spec.rb +172 -0
- data/spec/delayed_spec.rb +104 -0
- data/spec/event_loop_spec.rb +161 -0
- data/spec/future_spec.rb +316 -0
- data/spec/promise_spec.rb +172 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/thread_pool_spec.rb +281 -0
- data/zeevex_concurrency.gemspec +30 -0
- metadata +187 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
require 'zeevex_concurrency/thread_pool.rb'
|
3
|
+
require 'zeevex_concurrency/event_loop.rb'
|
4
|
+
require 'timeout'
|
5
|
+
require 'thread'
|
6
|
+
require 'atomic'
|
7
|
+
require 'countdownlatch'
|
8
|
+
|
9
|
+
describe ZeevexConcurrency::ThreadPool do
|
10
|
+
let :mutex do
|
11
|
+
Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
let :latch do
|
15
|
+
CountDownLatch.new(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
let :latch_wait_task do
|
19
|
+
Proc.new { latch.wait }
|
20
|
+
end
|
21
|
+
|
22
|
+
let :queue do
|
23
|
+
Queue.new
|
24
|
+
end
|
25
|
+
|
26
|
+
let :pop_task do
|
27
|
+
Proc.new { queue.pop }
|
28
|
+
end
|
29
|
+
|
30
|
+
let :atom do
|
31
|
+
Atomic.new(0)
|
32
|
+
end
|
33
|
+
|
34
|
+
around :each do |ex|
|
35
|
+
Timeout::timeout(30) do
|
36
|
+
ex.run
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
before do
|
41
|
+
queue
|
42
|
+
pop_task
|
43
|
+
atom
|
44
|
+
latch_wait_task
|
45
|
+
latch
|
46
|
+
end
|
47
|
+
|
48
|
+
def wait_until(timeout = 5, sleep_sec = 0.1)
|
49
|
+
t_start = Time.now
|
50
|
+
|
51
|
+
# go ahead and give up our timeslice as we might as well
|
52
|
+
# let somebody else make the condition true
|
53
|
+
Thread.pass unless yield
|
54
|
+
until yield || (Time.now-t_start) >= timeout
|
55
|
+
sleep sleep_sec
|
56
|
+
end
|
57
|
+
yield
|
58
|
+
end
|
59
|
+
|
60
|
+
shared_examples_for 'thread pool initialization' do
|
61
|
+
context 'basic usage' do
|
62
|
+
it 'should allow enqueue of a proc' do
|
63
|
+
expect { pool.enqueue(Proc.new { true }) }.
|
64
|
+
not_to raise_error
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should allow enqueue of a block' do
|
68
|
+
expect {
|
69
|
+
pool.enqueue do
|
70
|
+
true
|
71
|
+
end
|
72
|
+
}.not_to raise_error
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should allow enqueue of a Promise, and return same promise' do
|
76
|
+
promise = ZeevexConcurrency::Promise.new(Proc.new {true})
|
77
|
+
expect { pool.enqueue(promise) }.not_to raise_error
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should NOT allow both a callable and a block' do
|
81
|
+
expect {
|
82
|
+
pool.enqueue(Proc.new{}) do
|
83
|
+
true
|
84
|
+
end
|
85
|
+
}.to raise_error(ArgumentError)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
shared_examples_for 'thread pool running tasks' do
|
91
|
+
it 'should execute the task on a different thread' do
|
92
|
+
pool.enqueue { queue << Thread.current.__id__ }
|
93
|
+
queue.pop.should_not == Thread.current.__id__
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should allow enqueueing from an executed task, and execute both' do
|
97
|
+
pool.enqueue do
|
98
|
+
pool.enqueue { queue << "val2" }
|
99
|
+
queue << "val1"
|
100
|
+
end
|
101
|
+
[queue.pop, queue.pop].sort.should == ["val1", "val2"]
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should execute a large number of tasks' do
|
105
|
+
atom = Atomic.new(0)
|
106
|
+
300.times do
|
107
|
+
pool.enqueue do
|
108
|
+
atom.update { |x| x+1 }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
Timeout::timeout(20) do
|
112
|
+
while atom.value != 300
|
113
|
+
sleep 0.5
|
114
|
+
end
|
115
|
+
end
|
116
|
+
atom.value.should == 300
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
shared_examples_for 'thread pool with parallel execution' do
|
121
|
+
after do
|
122
|
+
latch.countdown!
|
123
|
+
end
|
124
|
+
|
125
|
+
# must be an even number
|
126
|
+
let :count do
|
127
|
+
parallelism == -1 ? 32 : parallelism
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should increase busy_count when tasks start' do
|
131
|
+
count.times { pool.enqueue { queue.pop } }
|
132
|
+
wait_until { pool.busy_count == count }
|
133
|
+
pool.busy_count.should == count
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should decrease busy_count when tasks finish' do
|
137
|
+
count.times { pool.enqueue { queue.pop } }
|
138
|
+
(count / 2).times { queue << "foo" }
|
139
|
+
pool.enqueue { latch.countdown!; queue.pop }
|
140
|
+
latch.wait
|
141
|
+
# should we need the following?
|
142
|
+
wait_until { pool.busy_count == (count / 2) + 1}
|
143
|
+
pool.busy_count.should == (count / 2) + 1
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# TODO: this is pretty iffy - it doesn't really prove the assertion
|
148
|
+
#
|
149
|
+
it 'should return from join only when currently executing tasks finish' do
|
150
|
+
(count / 2).times { pool.enqueue { sleep 1; atom.update {|x| x + 1} } }
|
151
|
+
pool.join
|
152
|
+
atom.value.should == count/2
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
shared_examples_for 'thread pool with task queue' do
|
157
|
+
it 'should give a total count of backlog in queue' do
|
158
|
+
(parallelism + 1).times { pool.enqueue { queue.pop } }
|
159
|
+
wait_until { pool.backlog == 1 }
|
160
|
+
pool.backlog.should == 1
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should allow flushing jobs from the queue' do
|
164
|
+
(parallelism + 1).times { pool.enqueue { queue.pop } }
|
165
|
+
wait_until { pool.backlog == 1 }
|
166
|
+
pool.flush
|
167
|
+
pool.backlog.should == 0
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should not return from join if backlogged tasks have not run' do
|
171
|
+
count = parallelism + 2
|
172
|
+
count.times { pool.enqueue { queue.pop } }
|
173
|
+
expect {
|
174
|
+
Timeout::timeout(2) { pool.join }
|
175
|
+
}.to raise_error(TimeoutError)
|
176
|
+
end
|
177
|
+
|
178
|
+
# TODO: this is another iffy one - how do we accurately meausure
|
179
|
+
# when join returns and how many tasks are waiting?
|
180
|
+
it 'should return from join when backlogged tasks have' do
|
181
|
+
count = parallelism * 2
|
182
|
+
t_start = Time.now
|
183
|
+
count.times { pool.enqueue { sleep 1; atom.update {|x| x + 1} } }
|
184
|
+
pool.join
|
185
|
+
t_end = Time.now
|
186
|
+
atom.value.should == count
|
187
|
+
# we expect roughly 2 seconds of wall clock time - each thread doing 2 tasks
|
188
|
+
# which sleep for 1 second each
|
189
|
+
(t_end - t_start).round.should == 2
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
shared_examples_for 'thread pool control' do
|
194
|
+
it 'should allow enqueueing after a stop/start' do
|
195
|
+
pending 'broken on jruby, and really in general'
|
196
|
+
pool.stop
|
197
|
+
pool.start
|
198
|
+
pool.enqueue do
|
199
|
+
queue << "ran"
|
200
|
+
end
|
201
|
+
Timeout::timeout(5) do
|
202
|
+
queue.pop.should == "ran"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'FixedPool' do
|
208
|
+
let :parallelism do
|
209
|
+
32
|
210
|
+
end
|
211
|
+
let :pool do
|
212
|
+
ZeevexConcurrency::ThreadPool::FixedPool.new(parallelism)
|
213
|
+
end
|
214
|
+
|
215
|
+
it_should_behave_like 'thread pool initialization'
|
216
|
+
it_should_behave_like 'thread pool running tasks'
|
217
|
+
it_should_behave_like 'thread pool control'
|
218
|
+
it_should_behave_like 'thread pool with parallel execution'
|
219
|
+
it_should_behave_like 'thread pool with task queue'
|
220
|
+
|
221
|
+
it 'should indicate that the pool is busy when there are tasks in the queue' do
|
222
|
+
(parallelism + 1).times { pool.enqueue { sleep 30 } }
|
223
|
+
wait_until { pool.backlog == 1 }
|
224
|
+
pool.should be_busy
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should indicate that there are no free workers when there are tasks in the queue' do
|
228
|
+
(parallelism + 1).times { pool.enqueue { sleep 30 } }
|
229
|
+
wait_until { pool.free_count == 0 }
|
230
|
+
pool.free_count.should == 0
|
231
|
+
pool.busy_count.should == parallelism
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
context 'InlineThreadPool' do
|
237
|
+
let :pool do
|
238
|
+
ZeevexConcurrency::ThreadPool::InlineThreadPool.new
|
239
|
+
end
|
240
|
+
let :parallelism do
|
241
|
+
1
|
242
|
+
end
|
243
|
+
|
244
|
+
it_should_behave_like 'thread pool initialization'
|
245
|
+
it_should_behave_like 'thread pool running tasks'
|
246
|
+
it_should_behave_like 'thread pool control'
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'ThreadPerJobPool' do
|
250
|
+
let :pool do
|
251
|
+
ZeevexConcurrency::ThreadPool::ThreadPerJobPool.new
|
252
|
+
end
|
253
|
+
let :parallelism do
|
254
|
+
-1
|
255
|
+
end
|
256
|
+
|
257
|
+
it_should_behave_like 'thread pool initialization'
|
258
|
+
it_should_behave_like 'thread pool running tasks'
|
259
|
+
it_should_behave_like 'thread pool control'
|
260
|
+
it_should_behave_like 'thread pool with parallel execution'
|
261
|
+
end
|
262
|
+
|
263
|
+
context 'EventLoopAdapter' do
|
264
|
+
let :loop do
|
265
|
+
ZeevexConcurrency::EventLoop.new
|
266
|
+
end
|
267
|
+
let :pool do
|
268
|
+
ZeevexConcurrency::ThreadPool::EventLoopAdapter.new loop
|
269
|
+
end
|
270
|
+
let :parallelism do
|
271
|
+
1
|
272
|
+
end
|
273
|
+
|
274
|
+
it_should_behave_like 'thread pool initialization'
|
275
|
+
it_should_behave_like 'thread pool running tasks'
|
276
|
+
it_should_behave_like 'thread pool control'
|
277
|
+
it_should_behave_like 'thread pool with task queue'
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
281
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'zeevex_concurrency/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "zeevex_concurrency"
|
8
|
+
gem.version = ZeevexConcurrency::VERSION
|
9
|
+
gem.authors = ["Robert Sanders"]
|
10
|
+
gem.email = ["robert@zeevex.com"]
|
11
|
+
gem.description = %q{Concurrency utilities including Delays, Promises, Futures, Event Loops, Thread Pools, and Synchronizing wrappers}
|
12
|
+
gem.summary = %q{Some concurrency utilities for Ruby}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'zeevex_proxy'
|
21
|
+
gem.add_dependency 'countdownlatch', '~> 1.0.0'
|
22
|
+
gem.add_dependency 'atomic', '~> 1.0.0'
|
23
|
+
|
24
|
+
## other headius utils
|
25
|
+
# s.add_dependency 'thread_safe'
|
26
|
+
|
27
|
+
gem.add_development_dependency 'rspec', '~> 2.9.0'
|
28
|
+
gem.add_development_dependency 'rake'
|
29
|
+
gem.add_development_dependency 'pry'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zeevex_concurrency
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Robert Sanders
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-06 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 3
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
prerelease: false
|
31
|
+
type: :runtime
|
32
|
+
name: zeevex_proxy
|
33
|
+
requirement: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
hash: 23
|
41
|
+
segments:
|
42
|
+
- 1
|
43
|
+
- 0
|
44
|
+
- 0
|
45
|
+
version: 1.0.0
|
46
|
+
prerelease: false
|
47
|
+
type: :runtime
|
48
|
+
name: countdownlatch
|
49
|
+
requirement: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 23
|
57
|
+
segments:
|
58
|
+
- 1
|
59
|
+
- 0
|
60
|
+
- 0
|
61
|
+
version: 1.0.0
|
62
|
+
prerelease: false
|
63
|
+
type: :runtime
|
64
|
+
name: atomic
|
65
|
+
requirement: *id003
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ~>
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 43
|
73
|
+
segments:
|
74
|
+
- 2
|
75
|
+
- 9
|
76
|
+
- 0
|
77
|
+
version: 2.9.0
|
78
|
+
prerelease: false
|
79
|
+
type: :development
|
80
|
+
name: rspec
|
81
|
+
requirement: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
prerelease: false
|
93
|
+
type: :development
|
94
|
+
name: rake
|
95
|
+
requirement: *id005
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
prerelease: false
|
107
|
+
type: :development
|
108
|
+
name: pry
|
109
|
+
requirement: *id006
|
110
|
+
description: Concurrency utilities including Delays, Promises, Futures, Event Loops, Thread Pools, and Synchronizing wrappers
|
111
|
+
email:
|
112
|
+
- robert@zeevex.com
|
113
|
+
executables: []
|
114
|
+
|
115
|
+
extensions: []
|
116
|
+
|
117
|
+
extra_rdoc_files: []
|
118
|
+
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- Gemfile
|
122
|
+
- LICENSE.txt
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- lib/zeevex_concurrency.rb
|
126
|
+
- lib/zeevex_concurrency/delay.rb
|
127
|
+
- lib/zeevex_concurrency/delayed.rb
|
128
|
+
- lib/zeevex_concurrency/event_loop.rb
|
129
|
+
- lib/zeevex_concurrency/future.rb
|
130
|
+
- lib/zeevex_concurrency/logging.rb
|
131
|
+
- lib/zeevex_concurrency/nil_logger.rb
|
132
|
+
- lib/zeevex_concurrency/promise.rb
|
133
|
+
- lib/zeevex_concurrency/synchronized.rb
|
134
|
+
- lib/zeevex_concurrency/thread_pool.rb
|
135
|
+
- lib/zeevex_concurrency/version.rb
|
136
|
+
- script/repl
|
137
|
+
- script/testall
|
138
|
+
- spec/delay_spec.rb
|
139
|
+
- spec/delayed_spec.rb
|
140
|
+
- spec/event_loop_spec.rb
|
141
|
+
- spec/future_spec.rb
|
142
|
+
- spec/promise_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
144
|
+
- spec/thread_pool_spec.rb
|
145
|
+
- zeevex_concurrency.gemspec
|
146
|
+
homepage: ""
|
147
|
+
licenses: []
|
148
|
+
|
149
|
+
post_install_message:
|
150
|
+
rdoc_options: []
|
151
|
+
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
hash: 3
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
requirements: []
|
173
|
+
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 1.8.24
|
176
|
+
signing_key:
|
177
|
+
specification_version: 3
|
178
|
+
summary: Some concurrency utilities for Ruby
|
179
|
+
test_files:
|
180
|
+
- spec/delay_spec.rb
|
181
|
+
- spec/delayed_spec.rb
|
182
|
+
- spec/event_loop_spec.rb
|
183
|
+
- spec/future_spec.rb
|
184
|
+
- spec/promise_spec.rb
|
185
|
+
- spec/spec_helper.rb
|
186
|
+
- spec/thread_pool_spec.rb
|
187
|
+
has_rdoc:
|