contender 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4277ce364374a3fc397165e20f7775c84828225c
4
+ data.tar.gz: f8c915d431b9d64ae31a9a9470e78ee83ba8df7d
5
+ SHA512:
6
+ metadata.gz: 6f252c5ef6200b03e51f3a35edddc150d03ddd01f20cf77f7a7b9d102114f499df03a07db0a5c8833fa3af55a0ad27e0a39ce9aa616ca0edc229853e7741f56c
7
+ data.tar.gz: 9c65d1d8d32808f2afcb0a66ffa41d995b7af142faeda839fd588d2c833582d63f3421c981bb759f46842e74987950d2f1aed48bb9e3bf892c961f43f26a0a25
data/lib/contender.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'forwardable'
2
+ require 'thread'
3
+
4
+ require 'contender/version'
5
+
6
+ require 'contender/errors'
7
+ require 'contender/countdown_latch'
8
+ require 'contender/executor'
9
+ require 'contender/direct_executor'
10
+ require 'contender/pool'
@@ -0,0 +1,45 @@
1
+ module Contender
2
+ # Synchronization aid that allows one or more threads to wait until a set of operations being
3
+ # performed in other threads complete
4
+ class CountdownLatch
5
+ # @param [Integer] initial
6
+ # @return [undefined]
7
+ def initialize(initial)
8
+ @count = initial
9
+
10
+ @mutex = Mutex.new
11
+ @condition = ConditionVariable.new
12
+ end
13
+
14
+ # Performs an ordered read of the count for this latch
15
+ # @return [Integer]
16
+ def count
17
+ @mutex.synchronize do
18
+ @count
19
+ end
20
+ end
21
+
22
+ # Performs an ordered decrement operation for this latch
23
+ #
24
+ # @api public
25
+ # @return [undefined]
26
+ def countdown
27
+ @mutex.synchronize do
28
+ @count = @count.pred if @count > 0
29
+ @condition.broadcast
30
+ end
31
+ end
32
+
33
+ # Waits until either the latch reaches zero or the timeout is reached
34
+ #
35
+ # @api public
36
+ # @param [Float] timeout
37
+ # @return [undefined]
38
+ def await(timeout = nil)
39
+ @mutex.synchronize do
40
+ return if @count == 0
41
+ @condition.wait @mutex, timeout
42
+ end
43
+ end
44
+ end # CountdownLatch
45
+ end
@@ -0,0 +1,8 @@
1
+ module Contender
2
+ # Simple implementation of an executor that executes blocks in the calling thread
3
+ class DirectExecutor < Executor
4
+ def execute(*arguments, &block)
5
+ block.call *arguments
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Contender
2
+ # Raised when an operation times out
3
+ class OperationTimedOut < Exception; end
4
+
5
+ # Raised when an operation was interrupted
6
+ class OperationInterrupted < Exception; end
7
+ end
@@ -0,0 +1,20 @@
1
+ module Contender
2
+ # Represents a mechanism for executing submitted blocks
3
+ #
4
+ # This interface decouples task submission from the way that tasks will be run, including details
5
+ # of thread use, scheduling, etc.
6
+ #
7
+ # @abstract
8
+ class Executor
9
+ # Executes the given block at some time in the future
10
+ #
11
+ # The block may execute in a new thread, in a pooled thread, or in the calling thread, at the
12
+ # discretion of the implementation.
13
+ #
14
+ # @abstract
15
+ # @param [Object...] arguments
16
+ # @param [Proc] block
17
+ # @return [undefined]
18
+ def execute(*arguments, &block); end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ require 'contender/pool/pool_worker'
2
+ require 'contender/pool/task'
3
+ require 'contender/pool/task_queue'
4
+ require 'contender/pool/thread_pool_executor'
@@ -0,0 +1,79 @@
1
+ module Contender
2
+ module Pool
3
+ # Encapsulates a single worker thread in a thread pool
4
+ class PoolWorker
5
+ # @param [ThreadPoolExecutor] pool
6
+ # @param [TaskQueue] queue
7
+ # @param [Boolean] non_block
8
+ # @return [undefined]
9
+ def initialize(pool, queue, non_block = false)
10
+ @pool = pool
11
+ @queue = queue
12
+ @non_block = non_block
13
+
14
+ # Only changed in the worker thread
15
+ @state = :inactive
16
+ end
17
+
18
+ # @return [undefined]
19
+ def start
20
+ return unless inactive?
21
+
22
+ @thread = Thread.new do
23
+ start_worker_loop
24
+ end
25
+ end
26
+
27
+ # @return [undefined]
28
+ def join
29
+ return unless @thread
30
+ @thread.join
31
+ end
32
+
33
+ # @return [undefined]
34
+ def interrupt
35
+ return unless @thread
36
+ @thread.raise OperationInterrupted
37
+ end
38
+
39
+ # Returns true if the pool worker is inactive
40
+ # @return [Boolean]
41
+ def inactive?
42
+ :inactive == @state
43
+ end
44
+
45
+ # Returns true if the pool worker is waiting for tasks
46
+ # @return [Boolean]
47
+ def waiting?
48
+ :waiting == @state
49
+ end
50
+
51
+ # Returns true if the pool worker is executing a task
52
+ # @return [Boolean]
53
+ def executing?
54
+ :executing == @state
55
+ end
56
+
57
+ protected
58
+
59
+ # Loop that is executed in another thread
60
+ # @return [undefined]
61
+ def start_worker_loop
62
+ @state = :waiting
63
+
64
+ loop do
65
+ break if @pool.shutdown_now?
66
+
67
+ task = @queue.dequeue @non_block
68
+ next unless task
69
+
70
+ @state = :executing
71
+ task.execute
72
+ @state = :waiting
73
+ end
74
+ ensure
75
+ @state = :inactive
76
+ end
77
+ end # PoolWorker
78
+ end # Pool
79
+ end
@@ -0,0 +1,52 @@
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
@@ -0,0 +1,80 @@
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
@@ -0,0 +1,162 @@
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
+ return unless inactive?
76
+
77
+ start_workers @options.fetch(:size)
78
+ @state = :active
79
+ end
80
+
81
+ # Blocks until all queued tasks have been executed, then shuts down the thread pool
82
+ #
83
+ # @api public
84
+ # @return [undefined]
85
+ def shutdown
86
+ return unless active?
87
+
88
+ @state = :shutdown
89
+
90
+ # TODO This is not ideal
91
+ until @queue.empty?
92
+ # Wait until the task queue is empty
93
+ sleep 0.1
94
+ end
95
+
96
+ @state = :shutdown_now
97
+
98
+ @queue.wakeup
99
+ @workers.each do |worker|
100
+ worker.join
101
+ end
102
+
103
+ cleanup
104
+ end
105
+
106
+ # Shuts down the thread pool instantly, interrupting any executing tasks
107
+ #
108
+ # @api public
109
+ # @return [undefined]
110
+ def shutdown!
111
+ return unless active?
112
+
113
+ @state = :shutdown_now
114
+
115
+ @workers.each do |worker|
116
+ worker.interrupt
117
+ end
118
+
119
+ cleanup
120
+ end
121
+
122
+ # @!method backlog
123
+ # Performs a volatile read on the number of tasks in the queue
124
+ # @return [Integer]
125
+ # @!method empty?
126
+ # Returns true if the task queue is empty
127
+ # @return [Boolean]
128
+ # @!method purge
129
+ # Removes any tasks that are queued for execution
130
+ # @return [undefined]
131
+ def_delegators :@queue, :backlog, :empty?, :purge
132
+
133
+ protected
134
+
135
+ # Cleans up pool workers and changes the executor state to inactive
136
+ # @return [undefined]
137
+ def cleanup
138
+ @workers.clear
139
+ @state = :inactive
140
+ end
141
+
142
+ # Starts the given number of worker threads
143
+ # @return [undefined]
144
+ def start_workers(count)
145
+ @mutex.synchronize do
146
+ count.times do
147
+ start_worker
148
+ end
149
+ end
150
+ end
151
+
152
+ # Starts a new worker and adds it to pool
153
+ # @return [undefined]
154
+ def start_worker
155
+ worker = PoolWorker.new self, @queue, @options.fetch(:non_block)
156
+ @workers.push worker
157
+
158
+ worker.start
159
+ end
160
+ end # ThreadPoolExecutor
161
+ end # Pool
162
+ end
@@ -0,0 +1,3 @@
1
+ module Contender
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: contender
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ian Unruh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple framework for managing in-process execution
14
+ email: ianunruh@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/contender/countdown_latch.rb
20
+ - lib/contender/direct_executor.rb
21
+ - lib/contender/errors.rb
22
+ - lib/contender/executor.rb
23
+ - lib/contender/pool/pool_worker.rb
24
+ - lib/contender/pool/task.rb
25
+ - lib/contender/pool/task_queue.rb
26
+ - lib/contender/pool/thread_pool_executor.rb
27
+ - lib/contender/pool.rb
28
+ - lib/contender/version.rb
29
+ - lib/contender.rb
30
+ homepage: https://github.com/ianunruh/contender
31
+ licenses:
32
+ - Apache 2.0
33
+ metadata: {}
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - '>='
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 2.0.3
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Simple framework for managing in-process execution
54
+ test_files: []
55
+ has_rdoc: