contender 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: