contender 0.1.1 → 0.2.0

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