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.
@@ -1,10 +1,54 @@
1
+ require 'atomic'
1
2
  require 'forwardable'
3
+ require 'monitor'
4
+ require 'set'
2
5
  require 'thread'
3
6
 
4
7
  require 'contender/version'
5
-
6
8
  require 'contender/errors'
9
+
10
+ require 'contender/copy_on_write_structure'
11
+ require 'contender/copy_on_write_array'
12
+ require 'contender/copy_on_write_set'
7
13
  require 'contender/countdown_latch'
14
+ require 'contender/counter'
8
15
  require 'contender/executor'
16
+ require 'contender/executor_service'
9
17
  require 'contender/direct_executor'
18
+ require 'contender/future'
19
+ require 'contender/future_task'
10
20
  require 'contender/pool'
21
+ require 'contender/thread_factory'
22
+
23
+ require 'contender/queue'
24
+ require 'contender/linked_queue'
25
+
26
+ module Contender
27
+ # http://stackoverflow.com/questions/535721/ruby-max-integer
28
+ FIXNUM_MAX = (2**(0.size * 8 - 2) - 1)
29
+ FIXNUM_MIN = -(2**(0.size * 8 - 2))
30
+
31
+ # @param [Integer] size
32
+ # @param [Boolean] allow_timeout
33
+ # @return [PoolExecutor]
34
+ def self.fixed_pool(size, allow_timeout = false)
35
+ executor = Pool::PoolExecutor.new size, size, 0, LinkedQueue.new, simple_thread_factory
36
+
37
+ if allow_timeout
38
+ executor.work_timeout = 60
39
+ executor.allow_core_timeout = true
40
+ end
41
+
42
+ executor
43
+ end
44
+
45
+ # @return [PoolExecutor]
46
+ def self.single_pool
47
+ fixed_pool 1
48
+ end
49
+
50
+ def self.simple_thread_factory
51
+ SimpleThreadFactory.new
52
+ end
53
+ end
54
+
@@ -0,0 +1,19 @@
1
+ module Contender
2
+ # Array that uses copy-on-write to guarantee thread safety
3
+ #
4
+ # Use this when read throughput is the primary requirement; write operations will always be
5
+ # slow due to the need to create a shallow copy of the array to perform operations on.
6
+ class CopyOnWriteArray
7
+ include CopyOnWriteStructure
8
+ include Comparable
9
+ include Enumerable
10
+
11
+ delegate_to Array
12
+
13
+ mutators :<<, :[]=, :clear, :collect!, :compact!, :concat, :delete, :delete_at, :delete_if,
14
+ :fill, :flatten!, :insert, :keep_if, :map!, :pop, :push, :reject!, :replace, :reverse!,
15
+ :rotate!, :select!, :shift, :shuffle!, :slice!, :sort!, :sort_by!, :uniq!, :unshift
16
+
17
+ accessors!
18
+ end # CopyOnWriteArray
19
+ end
@@ -0,0 +1,17 @@
1
+ module Contender
2
+ # Set that uses copy-on-write to guarantee thread safety
3
+ #
4
+ # Use this when read throughput is the primary requirement; write operations will always be
5
+ # slow due to the need to create a shallow copy of the set to perform operations on.
6
+ class CopyOnWriteSet
7
+ include CopyOnWriteStructure
8
+ include Enumerable
9
+
10
+ delegate_to Set
11
+
12
+ mutators :<<, :add, :add?, :clear, :collect!, :delete, :delete?, :delete_if, :flatten!,
13
+ :keep_if, :map!, :merge, :reject!, :replace, :select!, :subtract
14
+
15
+ accessors!
16
+ end # CopyOnWriteSet
17
+ end
@@ -0,0 +1,91 @@
1
+ module Contender
2
+ # @abstract
3
+ module CopyOnWriteStructure
4
+ extend Forwardable
5
+
6
+ def self.included(receiver)
7
+ receiver.send :extend, ClassMethods
8
+ receiver.send :extend, Forwardable
9
+ end
10
+
11
+ module ClassMethods
12
+ def accessors(*args)
13
+ @accessors ||= Set.new
14
+
15
+ args.each do |method|
16
+ @accessors.add method
17
+
18
+ def_delegator :current_reference, method
19
+ end
20
+ end
21
+
22
+ def accessors!
23
+ methods = Set.new(delegated_type.instance_methods(false))
24
+
25
+ methods = methods - @accessors if @accessors
26
+ methods = methods - @mutators if @mutators
27
+
28
+ methods.each do |method|
29
+ def_delegator :current_reference, method
30
+ end
31
+ end
32
+
33
+ def mutators(*args)
34
+ @mutators ||= Set.new
35
+
36
+ args.each do |method|
37
+ @mutators.add method
38
+
39
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
40
+ def #{method}(*args, &block)
41
+ update_reference do |current|
42
+ current.#{method}(*args, &block)
43
+ end
44
+ end
45
+ RUBY_EVAL
46
+ end
47
+ end
48
+
49
+ alias_method :accessor, :accessors
50
+ alias_method :mutator, :mutators
51
+
52
+ def delegate_to(type)
53
+ @delegated_type = type
54
+ end
55
+
56
+ attr_reader :delegated_type
57
+ end
58
+
59
+ def initialize(*args, &block)
60
+ @reference = Atomic.new(self.class.delegated_type.new(*args, &block))
61
+ end
62
+
63
+ protected
64
+
65
+ # @return [Object]
66
+ def current_reference
67
+ @reference.value
68
+ end
69
+
70
+ # @yield [Object]
71
+ # @return [Object]
72
+ def update_reference
73
+ loop do
74
+ current = current_reference
75
+ update = current.clone
76
+
77
+ result = yield update
78
+
79
+ if result.equal? update
80
+ # Most write operations return a reference to self
81
+ # Don't let this reference leak to the caller
82
+ result = self
83
+ end
84
+
85
+ return result if @reference.compare_and_swap current, update
86
+
87
+ # Concurrent modification, retry operation
88
+ end
89
+ end
90
+ end # CopyOnWriteStructure
91
+ end
@@ -2,30 +2,27 @@ module Contender
2
2
  # Synchronization aid that allows one or more threads to wait until a set of operations being
3
3
  # performed in other threads complete
4
4
  class CountdownLatch
5
+ # @return [Integer]
6
+ attr_reader :count
7
+
5
8
  # @param [Integer] initial
6
9
  # @return [undefined]
7
10
  def initialize(initial)
11
+ raise ArgumentError if initial < 0
12
+
8
13
  @count = initial
9
14
 
10
15
  @mutex = Mutex.new
11
16
  @condition = ConditionVariable.new
12
17
  end
13
18
 
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
19
  # Performs an ordered decrement operation for this latch
23
20
  #
24
21
  # @api public
25
22
  # @return [undefined]
26
23
  def countdown
27
24
  @mutex.synchronize do
28
- @count = @count.pred if @count > 0
25
+ @count -= 1 if @count > 0
29
26
  @condition.broadcast if @count == 0
30
27
  end
31
28
  end
@@ -37,7 +34,7 @@ module Contender
37
34
  # @return [Boolean] True if the latch reached zero, false if the timeout was reached
38
35
  def await(timeout = nil)
39
36
  @mutex.synchronize do
40
- return if @count == 0
37
+ return true if @count == 0
41
38
  @condition.wait @mutex, timeout
42
39
 
43
40
  @count == 0
@@ -0,0 +1,30 @@
1
+ module Contender
2
+ # Simplified interface to an atomic reference that acts as a counter
3
+ class Counter < Atomic
4
+ # @return [undefined]
5
+ def initialize(initial = 0)
6
+ super initial
7
+ end
8
+
9
+ # Increments the value of this counter by 1
10
+ # @return [Integer] The new value of this counter
11
+ def increment
12
+ update do |count|
13
+ count = count + 1
14
+ end
15
+ end
16
+
17
+ # Decrements the value of this counter by 1
18
+ # @return [Integer] The new value of this counter
19
+ def decrement
20
+ update do |count|
21
+ count = count - 1
22
+ end
23
+ end
24
+
25
+ # @return [String]
26
+ def inspect
27
+ "#<Contender::Counter = #{value}>"
28
+ end
29
+ end # Counter
30
+ end
@@ -1,8 +1,11 @@
1
1
  module Contender
2
2
  # Simple implementation of an executor that executes blocks in the calling thread
3
3
  class DirectExecutor < Executor
4
- def execute(*arguments, &block)
5
- block.call *arguments
4
+ # @api public
5
+ # @param [Object] task
6
+ # @return [undefined]
7
+ def execute(task = nil, &block)
8
+ (task || block).call
6
9
  end
7
10
  end
8
11
  end
@@ -1,7 +1,26 @@
1
1
  module Contender
2
- # Raised when an operation times out
3
- class OperationTimedOut < Exception; end
2
+ # Raised when attempting to retrieve the result of a task that was cancelled
3
+ class CancellationError < RuntimeError; end
4
4
 
5
- # Raised when an operation was interrupted
6
- class OperationInterrupted < Exception; end
5
+ # Raised when a blocking operation times out
6
+ class TimeoutError < RuntimeError; end
7
+
8
+ # Raised when a thread is interrupted during the execution of a task
9
+ class InterruptError < ThreadError; end
10
+
11
+ # Raised when an operation can not be completed because the object is not in the correct state
12
+ class InvalidStateError < RuntimeError; end
13
+
14
+ # Raised when attempting to retrieve the result of a task that aborted by raising an exception
15
+ class ExecutionError < RuntimeError
16
+ # @return [Exception]
17
+ attr_reader :cause
18
+
19
+ # @param [Exception] cause
20
+ # @return [undefined]
21
+ def initialize(cause)
22
+ @cause = cause
23
+ set_backtrace cause.backtrace
24
+ end
25
+ end
7
26
  end
@@ -12,9 +12,10 @@ module Contender
12
12
  # discretion of the implementation.
13
13
  #
14
14
  # @abstract
15
- # @param [Object...] arguments
16
- # @param [Proc] block
15
+ # @param [Object] task
17
16
  # @return [undefined]
18
- def execute(*arguments, &block); end
19
- end
17
+ def execute(task = nil, &block)
18
+ raise NotImplementedError
19
+ end
20
+ end # Executor
20
21
  end
@@ -0,0 +1,94 @@
1
+ module Contender
2
+ # @abstract
3
+ class ExecutorService < Executor
4
+ # @abstract
5
+ # @return [undefined]
6
+ def shutdown
7
+ raise NotImplementedError
8
+ end
9
+
10
+ # @abstract
11
+ # @return [Array]
12
+ def shutdown!
13
+ raise NotImplementedError
14
+ end
15
+
16
+ # @abstract
17
+ # @param [Float] timeout
18
+ # @return [Boolean]
19
+ def await_termination(timeout)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ # @abstract
24
+ # @return [Boolean]
25
+ def shutdown?
26
+ raise NotImplementedError
27
+ end
28
+
29
+ # @abstract
30
+ # @return [Boolean]
31
+ def terminated?
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # @api public
36
+ # @raise [ArgumentError] If neither a block nor callable were given
37
+ # @param [Object] callable
38
+ # @return [FutureTask]
39
+ def submit(callable = nil, &block)
40
+ callable ||= block
41
+
42
+ raise ArgumentError unless callable
43
+
44
+ future = future_for callable
45
+ execute future
46
+ future
47
+ end
48
+
49
+ # @api public
50
+ # @param [Array<Object>] tasks
51
+ # @return [Array<FutureTask>]
52
+ def invoke_all(tasks)
53
+ finished = false
54
+ futures = Array.new
55
+
56
+ begin
57
+ tasks.each do |task|
58
+ future = create_task_for task
59
+
60
+ futures.push future
61
+ execute future
62
+ end
63
+
64
+ futures.each do |future|
65
+ unless future.done?
66
+ begin
67
+ future.result
68
+ rescue CancellationError
69
+ rescue ExecutionError
70
+ # The caller can deal with these errors
71
+ end
72
+ end
73
+ end
74
+
75
+ finished = true
76
+ futures
77
+ ensure
78
+ unless finished
79
+ futures.each do |future|
80
+ future.cancel true
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # @api public
87
+ # @param [Object] task
88
+ # @return [FutureTask]
89
+ def future_for(task)
90
+ FutureTask.new task
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,37 @@
1
+ module Contender
2
+ # @abstract
3
+ class Future
4
+ # @abstract
5
+ # @param [Boolean] should_interrupt
6
+ # @return [Boolean] True if this future was cancelled
7
+ def cancel(should_interrupt)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ # Returns true if this future was cancelled before it could complete
12
+ #
13
+ # @abstract
14
+ # @return [Boolean]
15
+ def cancelled?
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Returns true if this future was either cancelled or completed
20
+ #
21
+ # @abstract
22
+ # @return [Boolean]
23
+ def done?
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # @abstract
28
+ # @raise [ExecutionError] If the result of the operation was an exception
29
+ # @raise [TimeoutError] If the timeout was reached before the operation completed
30
+ # @raise [CancellationError] If the operation was cancelled
31
+ # @param [Integer] timeout Time to wait for the result
32
+ # @return [Object] The result of the future
33
+ def result(timeout = nil)
34
+ raise NotImplementedError
35
+ end
36
+ end
37
+ end