contender 0.1.1 → 0.2.0
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/lib/contender.rb +45 -1
- data/lib/contender/copy_on_write_array.rb +19 -0
- data/lib/contender/copy_on_write_set.rb +17 -0
- data/lib/contender/copy_on_write_structure.rb +91 -0
- data/lib/contender/countdown_latch.rb +7 -10
- data/lib/contender/counter.rb +30 -0
- data/lib/contender/direct_executor.rb +5 -2
- data/lib/contender/errors.rb +23 -4
- data/lib/contender/executor.rb +5 -4
- data/lib/contender/executor_service.rb +94 -0
- data/lib/contender/future.rb +37 -0
- data/lib/contender/future_task.rb +99 -0
- data/lib/contender/linked_queue.rb +338 -0
- data/lib/contender/pool.rb +3 -3
- data/lib/contender/pool/pool_executor.rb +697 -0
- data/lib/contender/pool/pool_worker.rb +19 -60
- data/lib/contender/pool/rejection_policy.rb +61 -0
- data/lib/contender/queue.rb +87 -0
- data/lib/contender/thread_factory.rb +37 -0
- data/lib/contender/version.rb +1 -1
- data/spec/copy_on_write_array_spec.rb +20 -0
- data/spec/copy_on_write_set_spec.rb +25 -0
- data/spec/countdown_latch_spec.rb +136 -0
- data/spec/counter_spec.rb +26 -0
- data/spec/direct_executor_spec.rb +21 -0
- data/spec/future_task_spec.rb +170 -0
- data/spec/linked_queue_spec.rb +25 -0
- data/spec/pool/executor_spec.rb +436 -0
- data/spec/pool/executor_stress_spec.rb +31 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/wait_helper.rb +14 -0
- metadata +149 -22
- data/lib/contender/pool/task.rb +0 -52
- data/lib/contender/pool/task_queue.rb +0 -80
- data/lib/contender/pool/thread_pool_executor.rb +0 -167
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Contender
|
4
|
+
module Pool
|
5
|
+
|
6
|
+
describe PoolExecutor, stress: true do
|
7
|
+
it 'can handle many submissions' do
|
8
|
+
executor = Pool.fixed 4
|
9
|
+
|
10
|
+
x = 100_000
|
11
|
+
|
12
|
+
latch = CountdownLatch.new x
|
13
|
+
x.times do
|
14
|
+
executor.execute do
|
15
|
+
latch.countdown
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
latch.await
|
20
|
+
|
21
|
+
puts executor
|
22
|
+
|
23
|
+
executor.shutdown
|
24
|
+
executor.await_termination 5
|
25
|
+
|
26
|
+
executor.completed_tasks.should == x
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/wait_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module WaitHelper
|
2
|
+
TimeoutError = Class.new RuntimeError
|
3
|
+
|
4
|
+
def wait_until(timeout = 5, interval = 0.1, &block)
|
5
|
+
start = Time.now
|
6
|
+
|
7
|
+
until block.call
|
8
|
+
elapsed = Time.now - start
|
9
|
+
raise TimeoutError unless elapsed < timeout
|
10
|
+
|
11
|
+
sleep interval
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,57 +1,184 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: contender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ian Unruh
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
13
|
-
dependencies:
|
14
|
-
|
12
|
+
date: 2013-07-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: atomic
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rr
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: simplecov
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Collection of utilities for managing concurrency
|
15
111
|
email: ianunruh@gmail.com
|
16
112
|
executables: []
|
17
113
|
extensions: []
|
18
114
|
extra_rdoc_files: []
|
19
115
|
files:
|
20
|
-
- lib/contender.rb
|
116
|
+
- lib/contender/copy_on_write_array.rb
|
117
|
+
- lib/contender/copy_on_write_set.rb
|
118
|
+
- lib/contender/copy_on_write_structure.rb
|
21
119
|
- lib/contender/countdown_latch.rb
|
120
|
+
- lib/contender/counter.rb
|
22
121
|
- lib/contender/direct_executor.rb
|
23
122
|
- lib/contender/errors.rb
|
24
123
|
- lib/contender/executor.rb
|
124
|
+
- lib/contender/executor_service.rb
|
125
|
+
- lib/contender/future.rb
|
126
|
+
- lib/contender/future_task.rb
|
127
|
+
- lib/contender/linked_queue.rb
|
128
|
+
- lib/contender/pool/pool_executor.rb
|
129
|
+
- lib/contender/pool/pool_worker.rb
|
130
|
+
- lib/contender/pool/rejection_policy.rb
|
25
131
|
- lib/contender/pool.rb
|
132
|
+
- lib/contender/queue.rb
|
133
|
+
- lib/contender/thread_factory.rb
|
26
134
|
- lib/contender/version.rb
|
27
|
-
- lib/contender
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
135
|
+
- lib/contender.rb
|
136
|
+
- spec/copy_on_write_array_spec.rb
|
137
|
+
- spec/copy_on_write_set_spec.rb
|
138
|
+
- spec/countdown_latch_spec.rb
|
139
|
+
- spec/counter_spec.rb
|
140
|
+
- spec/direct_executor_spec.rb
|
141
|
+
- spec/future_task_spec.rb
|
142
|
+
- spec/linked_queue_spec.rb
|
143
|
+
- spec/pool/executor_spec.rb
|
144
|
+
- spec/pool/executor_stress_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/wait_helper.rb
|
31
147
|
homepage: https://github.com/ianunruh/contender
|
32
148
|
licenses:
|
33
149
|
- Apache 2.0
|
34
|
-
post_install_message:
|
150
|
+
post_install_message:
|
35
151
|
rdoc_options: []
|
36
152
|
require_paths:
|
37
153
|
- lib
|
38
154
|
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
39
156
|
requirements:
|
40
|
-
- - '>='
|
157
|
+
- - ! '>='
|
41
158
|
- !ruby/object:Gem::Version
|
42
159
|
version: '0'
|
43
|
-
none: false
|
44
160
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
45
162
|
requirements:
|
46
|
-
- - '>='
|
163
|
+
- - ! '>='
|
47
164
|
- !ruby/object:Gem::Version
|
48
165
|
version: '0'
|
49
|
-
none: false
|
50
166
|
requirements: []
|
51
|
-
rubyforge_project:
|
52
|
-
rubygems_version: 1.8.
|
53
|
-
signing_key:
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 1.8.25
|
169
|
+
signing_key:
|
54
170
|
specification_version: 3
|
55
|
-
summary:
|
56
|
-
test_files:
|
57
|
-
|
171
|
+
summary: Collection of utilities for managing concurrency
|
172
|
+
test_files:
|
173
|
+
- spec/copy_on_write_array_spec.rb
|
174
|
+
- spec/copy_on_write_set_spec.rb
|
175
|
+
- spec/countdown_latch_spec.rb
|
176
|
+
- spec/counter_spec.rb
|
177
|
+
- spec/direct_executor_spec.rb
|
178
|
+
- spec/future_task_spec.rb
|
179
|
+
- spec/linked_queue_spec.rb
|
180
|
+
- spec/pool/executor_spec.rb
|
181
|
+
- spec/pool/executor_stress_spec.rb
|
182
|
+
- spec/spec_helper.rb
|
183
|
+
- spec/wait_helper.rb
|
184
|
+
has_rdoc:
|
data/lib/contender/pool/task.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
module Contender
|
2
|
-
module Pool
|
3
|
-
# Encapsulates a task that is either waiting for execution or has been executed
|
4
|
-
class Task
|
5
|
-
# @return [Array] The arguments that the block will be executed with
|
6
|
-
attr_reader :arguments
|
7
|
-
|
8
|
-
# @return [Proc] The block that will be executed by a pool worker
|
9
|
-
attr_reader :block
|
10
|
-
|
11
|
-
# @return [Exception] Exception that occured during task execution
|
12
|
-
attr_reader :exception
|
13
|
-
|
14
|
-
# @param [Proc] block
|
15
|
-
# @param [Array] arguments
|
16
|
-
# @return [undefined]
|
17
|
-
def initialize(block, arguments)
|
18
|
-
@block = block
|
19
|
-
@arguments = arguments
|
20
|
-
@executed = false
|
21
|
-
end
|
22
|
-
|
23
|
-
# Returns true if this task has been executed
|
24
|
-
#
|
25
|
-
# @api public
|
26
|
-
# @return [Boolean]
|
27
|
-
def executed?
|
28
|
-
@executed
|
29
|
-
end
|
30
|
-
|
31
|
-
# Returns true if the execution of this task resulted in an exception
|
32
|
-
#
|
33
|
-
# @api public
|
34
|
-
# @return [Boolean]
|
35
|
-
def failed?
|
36
|
-
!!@exception
|
37
|
-
end
|
38
|
-
|
39
|
-
# Executes the block with the provided arguments
|
40
|
-
# @return [undefined]
|
41
|
-
def execute
|
42
|
-
begin
|
43
|
-
@block.call *@arguments
|
44
|
-
rescue Exception => exception
|
45
|
-
@exception = exception
|
46
|
-
end
|
47
|
-
|
48
|
-
@executed = true
|
49
|
-
end
|
50
|
-
end # Task
|
51
|
-
end # Pool
|
52
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
module Contender
|
2
|
-
module Pool
|
3
|
-
# Simple thread-safe queue that holds tasks waiting for execution
|
4
|
-
class TaskQueue
|
5
|
-
# @return [undefined]
|
6
|
-
def initialize
|
7
|
-
@condition = ConditionVariable.new
|
8
|
-
@mutex = Mutex.new
|
9
|
-
@tasks = Array.new
|
10
|
-
end
|
11
|
-
|
12
|
-
# Performs a volatile read of the size of the task queue
|
13
|
-
#
|
14
|
-
# @api public
|
15
|
-
# @return [Integer]
|
16
|
-
def backlog
|
17
|
-
@tasks.size
|
18
|
-
end
|
19
|
-
|
20
|
-
# Returns true if the task queue is empty
|
21
|
-
#
|
22
|
-
# @api public
|
23
|
-
# @return [Boolean]
|
24
|
-
def empty?
|
25
|
-
@tasks.empty?
|
26
|
-
end
|
27
|
-
|
28
|
-
# Removes any tasks that are queued for execution
|
29
|
-
#
|
30
|
-
# @api public
|
31
|
-
# @return [undefined]
|
32
|
-
def purge
|
33
|
-
@mutex.synchronize do
|
34
|
-
@tasks.clear
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Enqueues the given task for execution
|
39
|
-
#
|
40
|
-
# @api public
|
41
|
-
# @param [Task] task
|
42
|
-
# @return [undefined]
|
43
|
-
def enqueue(task)
|
44
|
-
@mutex.synchronize do
|
45
|
-
@tasks.push task
|
46
|
-
@condition.signal
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Dequeues the first task in line
|
51
|
-
#
|
52
|
-
# If this operation is performed as non-blocking and the queue is empty, then the result
|
53
|
-
# of the dequeue will be +nil+.
|
54
|
-
#
|
55
|
-
# If this operation is performed as blocking, then it will wait until a task is added to the
|
56
|
-
# queue. In the case that the thread pool is shutdown during while a worker is waiting for
|
57
|
-
# the operation to complete, +nil+ will be returned immediately.
|
58
|
-
#
|
59
|
-
# @api public
|
60
|
-
# @param [Boolean] non_block
|
61
|
-
# @return [Task]
|
62
|
-
def dequeue(non_block = false)
|
63
|
-
@mutex.synchronize do
|
64
|
-
if @tasks.empty?
|
65
|
-
return if non_block
|
66
|
-
@condition.wait @mutex
|
67
|
-
end
|
68
|
-
|
69
|
-
@tasks.shift
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Wakes up any threads waiting for tasks to be queued
|
74
|
-
# @return [undefined]
|
75
|
-
def wakeup
|
76
|
-
@condition.broadcast
|
77
|
-
end
|
78
|
-
end # TaskQueue
|
79
|
-
end # Pool
|
80
|
-
end
|
@@ -1,167 +0,0 @@
|
|
1
|
-
module Contender
|
2
|
-
module Pool
|
3
|
-
# Executor that uses a cached thread pool to execute tasks in the background
|
4
|
-
class ThreadPoolExecutor < Executor
|
5
|
-
extend Forwardable
|
6
|
-
|
7
|
-
# @param [Hash] options
|
8
|
-
# @return [undefined]
|
9
|
-
def initialize(options = Hash.new)
|
10
|
-
@queue = TaskQueue.new
|
11
|
-
|
12
|
-
@options = {
|
13
|
-
size: 2,
|
14
|
-
non_block: false
|
15
|
-
}.merge options
|
16
|
-
|
17
|
-
@workers = Array.new
|
18
|
-
@mutex = Mutex.new
|
19
|
-
|
20
|
-
@state = :inactive
|
21
|
-
end
|
22
|
-
|
23
|
-
# Defers the execution of the given block until it can be processed by a worker in the
|
24
|
-
# thread pool
|
25
|
-
#
|
26
|
-
# @api public
|
27
|
-
# @param [Object...] arguments
|
28
|
-
# @param [Proc] block
|
29
|
-
# @return [undefined]
|
30
|
-
def execute(*arguments, &block)
|
31
|
-
unless active?
|
32
|
-
raise 'Thread pool has not been started or is shutting down'
|
33
|
-
end
|
34
|
-
|
35
|
-
@queue.enqueue(Task.new(block, arguments))
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns true if this thread pool is inactive
|
39
|
-
#
|
40
|
-
# @api public
|
41
|
-
# @return [Boolean]
|
42
|
-
def inactive?
|
43
|
-
:inactive == @state
|
44
|
-
end
|
45
|
-
|
46
|
-
# Returns true if this thread pool is active and accepting tasks
|
47
|
-
#
|
48
|
-
# @api public
|
49
|
-
# @return [Boolean]
|
50
|
-
def active?
|
51
|
-
:active == @state
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns true if this thread pool is waiting to shutdown
|
55
|
-
#
|
56
|
-
# @api public
|
57
|
-
# @return [Boolean]
|
58
|
-
def shutdown?
|
59
|
-
:shutdown == @state
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns true if this thread pool is shutting down now
|
63
|
-
#
|
64
|
-
# @api public
|
65
|
-
# @return [Boolean]
|
66
|
-
def shutdown_now?
|
67
|
-
:shutdown_now == @state
|
68
|
-
end
|
69
|
-
|
70
|
-
# Starts the thread pool
|
71
|
-
#
|
72
|
-
# @api public
|
73
|
-
# @return [undefined]
|
74
|
-
def start
|
75
|
-
@mutex.synchronize do
|
76
|
-
return unless inactive?
|
77
|
-
@state = :starting
|
78
|
-
end
|
79
|
-
|
80
|
-
start_workers @options.fetch(:size)
|
81
|
-
@state = :active
|
82
|
-
end
|
83
|
-
|
84
|
-
# Blocks until all queued tasks have been executed, then shuts down the thread pool
|
85
|
-
#
|
86
|
-
# @api public
|
87
|
-
# @return [undefined]
|
88
|
-
def shutdown
|
89
|
-
@mutex.synchronize do
|
90
|
-
return unless active?
|
91
|
-
@state = :shutdown
|
92
|
-
end
|
93
|
-
|
94
|
-
# TODO This is not ideal
|
95
|
-
until @queue.empty?
|
96
|
-
# Wait until the task queue is empty
|
97
|
-
sleep 0.1
|
98
|
-
end
|
99
|
-
|
100
|
-
@state = :shutdown_now
|
101
|
-
|
102
|
-
@queue.wakeup
|
103
|
-
@workers.each do |worker|
|
104
|
-
worker.join
|
105
|
-
end
|
106
|
-
|
107
|
-
cleanup
|
108
|
-
end
|
109
|
-
|
110
|
-
# Shuts down the thread pool instantly, interrupting any executing tasks
|
111
|
-
#
|
112
|
-
# @api public
|
113
|
-
# @return [undefined]
|
114
|
-
def shutdown!
|
115
|
-
@mutex.synchronize do
|
116
|
-
return unless active?
|
117
|
-
@state = :shutdown_now
|
118
|
-
end
|
119
|
-
|
120
|
-
@workers.each do |worker|
|
121
|
-
worker.interrupt
|
122
|
-
end
|
123
|
-
|
124
|
-
cleanup
|
125
|
-
end
|
126
|
-
|
127
|
-
# @!method backlog
|
128
|
-
# Performs a volatile read on the number of tasks in the queue
|
129
|
-
# @return [Integer]
|
130
|
-
# @!method empty?
|
131
|
-
# Returns true if the task queue is empty
|
132
|
-
# @return [Boolean]
|
133
|
-
# @!method purge
|
134
|
-
# Removes any tasks that are queued for execution
|
135
|
-
# @return [undefined]
|
136
|
-
def_delegators :@queue, :backlog, :empty?, :purge
|
137
|
-
|
138
|
-
protected
|
139
|
-
|
140
|
-
# Cleans up pool workers and changes the executor state to inactive
|
141
|
-
# @return [undefined]
|
142
|
-
def cleanup
|
143
|
-
@workers.clear
|
144
|
-
@state = :inactive
|
145
|
-
end
|
146
|
-
|
147
|
-
# Starts the given number of worker threads
|
148
|
-
# @return [undefined]
|
149
|
-
def start_workers(count)
|
150
|
-
@mutex.synchronize do
|
151
|
-
count.times do
|
152
|
-
start_worker
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# Starts a new worker and adds it to pool
|
158
|
-
# @return [undefined]
|
159
|
-
def start_worker
|
160
|
-
worker = PoolWorker.new self, @queue, @options.fetch(:non_block)
|
161
|
-
@workers.push worker
|
162
|
-
|
163
|
-
worker.start
|
164
|
-
end
|
165
|
-
end # ThreadPoolExecutor
|
166
|
-
end # Pool
|
167
|
-
end
|