concurrent-ruby 0.7.0.rc1 → 0.7.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/lib/concurrent.rb +2 -1
  4. data/lib/concurrent/actor.rb +104 -0
  5. data/lib/concurrent/{actress → actor}/ad_hoc.rb +2 -3
  6. data/lib/concurrent/actor/behaviour.rb +70 -0
  7. data/lib/concurrent/actor/behaviour/abstract.rb +48 -0
  8. data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
  9. data/lib/concurrent/actor/behaviour/buffer.rb +54 -0
  10. data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
  11. data/lib/concurrent/actor/behaviour/executes_context.rb +18 -0
  12. data/lib/concurrent/actor/behaviour/linking.rb +42 -0
  13. data/lib/concurrent/actor/behaviour/pausing.rb +77 -0
  14. data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
  15. data/lib/concurrent/actor/behaviour/sets_results.rb +36 -0
  16. data/lib/concurrent/actor/behaviour/supervised.rb +58 -0
  17. data/lib/concurrent/actor/behaviour/supervising.rb +34 -0
  18. data/lib/concurrent/actor/behaviour/terminates_children.rb +13 -0
  19. data/lib/concurrent/actor/behaviour/termination.rb +54 -0
  20. data/lib/concurrent/actor/context.rb +153 -0
  21. data/lib/concurrent/actor/core.rb +213 -0
  22. data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
  23. data/lib/concurrent/{actress → actor}/envelope.rb +1 -1
  24. data/lib/concurrent/actor/errors.rb +27 -0
  25. data/lib/concurrent/actor/internal_delegations.rb +49 -0
  26. data/lib/concurrent/{actress/core_delegations.rb → actor/public_delegations.rb} +11 -13
  27. data/lib/concurrent/{actress → actor}/reference.rb +25 -8
  28. data/lib/concurrent/actor/root.rb +37 -0
  29. data/lib/concurrent/{actress → actor}/type_check.rb +1 -1
  30. data/lib/concurrent/actor/utills.rb +7 -0
  31. data/lib/concurrent/actor/utils/broadcast.rb +36 -0
  32. data/lib/concurrent/actress.rb +2 -224
  33. data/lib/concurrent/agent.rb +10 -12
  34. data/lib/concurrent/atomic.rb +32 -1
  35. data/lib/concurrent/atomic/atomic_boolean.rb +55 -13
  36. data/lib/concurrent/atomic/atomic_fixnum.rb +54 -16
  37. data/lib/concurrent/atomic/synchronization.rb +51 -0
  38. data/lib/concurrent/atomic/thread_local_var.rb +15 -50
  39. data/lib/concurrent/atomic_reference/mutex_atomic.rb +1 -1
  40. data/lib/concurrent/atomic_reference/ruby.rb +15 -0
  41. data/lib/concurrent/atomics.rb +1 -0
  42. data/lib/concurrent/channel/unbuffered_channel.rb +2 -1
  43. data/lib/concurrent/configuration.rb +6 -3
  44. data/lib/concurrent/dataflow.rb +20 -3
  45. data/lib/concurrent/delay.rb +23 -31
  46. data/lib/concurrent/executor/executor.rb +7 -2
  47. data/lib/concurrent/executor/timer_set.rb +1 -1
  48. data/lib/concurrent/future.rb +2 -1
  49. data/lib/concurrent/lazy_register.rb +58 -0
  50. data/lib/concurrent/options_parser.rb +4 -2
  51. data/lib/concurrent/promise.rb +2 -1
  52. data/lib/concurrent/scheduled_task.rb +6 -5
  53. data/lib/concurrent/tvar.rb +6 -10
  54. data/lib/concurrent/utility/processor_count.rb +4 -2
  55. data/lib/concurrent/version.rb +1 -1
  56. data/lib/concurrent_ruby_ext.so +0 -0
  57. metadata +32 -10
  58. data/lib/concurrent/actress/context.rb +0 -98
  59. data/lib/concurrent/actress/core.rb +0 -228
  60. data/lib/concurrent/actress/errors.rb +0 -14
@@ -9,7 +9,7 @@ module Concurrent
9
9
  include Concurrent::AtomicDirectUpdate
10
10
  include Concurrent::AtomicNumericCompareAndSetWrapper
11
11
 
12
- # @!macro atomic_reference_method_initialize
12
+ # @!macro [attach] atomic_reference_method_initialize
13
13
  def initialize(value = nil)
14
14
  @mutex = Mutex.new
15
15
  @value = value
@@ -14,5 +14,20 @@ module Concurrent
14
14
  class CAtomic
15
15
  include Concurrent::AtomicDirectUpdate
16
16
  include Concurrent::AtomicNumericCompareAndSetWrapper
17
+
18
+ # @!method initialize
19
+ # @!macro atomic_reference_method_initialize
20
+
21
+ # @!method get
22
+ # @!macro atomic_reference_method_get
23
+
24
+ # @!method set
25
+ # @!macro atomic_reference_method_set
26
+
27
+ # @!method get_and_set
28
+ # @!macro atomic_reference_method_get_and_set
29
+
30
+ # @!method _compare_and_set
31
+ # @!macro atomic_reference_method_compare_and_set
17
32
  end
18
33
  end
@@ -8,3 +8,4 @@ require 'concurrent/atomic/cyclic_barrier'
8
8
  require 'concurrent/atomic/count_down_latch'
9
9
  require 'concurrent/atomic/event'
10
10
  require 'concurrent/atomic/thread_local_var'
11
+ require 'concurrent/atomic/synchronization'
@@ -12,6 +12,7 @@ module Concurrent
12
12
  end
13
13
 
14
14
  def push(value)
15
+ # TODO set_unless_assigned define on IVar as #set_state? or #try_set_state
15
16
  until @probe_set.take.set_unless_assigned(value, self)
16
17
  end
17
18
  end
@@ -31,4 +32,4 @@ module Concurrent
31
32
  end
32
33
 
33
34
  end
34
- end
35
+ end
@@ -2,6 +2,7 @@ require 'thread'
2
2
  require 'concurrent/delay'
3
3
  require 'concurrent/errors'
4
4
  require 'concurrent/atomic'
5
+ require 'concurrent/executor/immediate_executor'
5
6
  require 'concurrent/executor/thread_pool_executor'
6
7
  require 'concurrent/executor/timer_set'
7
8
  require 'concurrent/utility/processor_count'
@@ -18,9 +19,10 @@ module Concurrent
18
19
 
19
20
  # Create a new configuration object.
20
21
  def initialize
21
- @global_task_pool = Delay.new { new_task_pool }
22
- @global_operation_pool = Delay.new { new_operation_pool }
23
- @global_timer_set = Delay.new { Concurrent::TimerSet.new }
22
+ immediate_executor = ImmediateExecutor.new
23
+ @global_task_pool = Delay.new(executor: immediate_executor) { new_task_pool }
24
+ @global_operation_pool = Delay.new(executor: immediate_executor) { new_operation_pool }
25
+ @global_timer_set = Delay.new(executor: immediate_executor) { Concurrent::TimerSet.new }
24
26
  @logger = no_logger
25
27
  end
26
28
 
@@ -154,5 +156,6 @@ module Concurrent
154
156
  self.finalize_executor(self.configuration.global_timer_set)
155
157
  self.finalize_executor(self.configuration.global_task_pool)
156
158
  self.finalize_executor(self.configuration.global_operation_pool)
159
+ # TODO may break other test suites using concurrent-ruby, terminates before test is run
157
160
  end
158
161
  end
@@ -61,17 +61,34 @@ module Concurrent
61
61
  # @raise [ArgumentError] if no block is given
62
62
  # @raise [ArgumentError] if any of the inputs are not `IVar`s
63
63
  def dataflow(*inputs, &block)
64
- dataflow_with(Concurrent.configuration.global_task_pool, *inputs, &block)
64
+ dataflow_with(Concurrent.configuration.global_operation_pool, *inputs, &block)
65
65
  end
66
66
  module_function :dataflow
67
67
 
68
68
  def dataflow_with(executor, *inputs, &block)
69
+ call_dataflow(:value, executor, *inputs, &block)
70
+ end
71
+ module_function :dataflow_with
72
+
73
+ def dataflow!(*inputs, &block)
74
+ dataflow_with!(Concurrent.configuration.global_task_pool, *inputs, &block)
75
+ end
76
+ module_function :dataflow!
77
+
78
+ def dataflow_with!(executor, *inputs, &block)
79
+ call_dataflow(:value!, executor, *inputs, &block)
80
+ end
81
+ module_function :dataflow_with!
82
+
83
+ private
84
+
85
+ def call_dataflow(method, executor, *inputs, &block)
69
86
  raise ArgumentError.new('an executor must be provided') if executor.nil?
70
87
  raise ArgumentError.new('no block given') unless block_given?
71
88
  raise ArgumentError.new('not all dependencies are IVars') unless inputs.all? { |input| input.is_a? IVar }
72
89
 
73
90
  result = Future.new(executor: executor) do
74
- values = inputs.map { |input| input.value }
91
+ values = inputs.map { |input| input.send(method) }
75
92
  block.call(*values)
76
93
  end
77
94
 
@@ -87,5 +104,5 @@ module Concurrent
87
104
 
88
105
  result
89
106
  end
90
- module_function :dataflow_with
107
+ module_function :call_dataflow
91
108
  end
@@ -1,5 +1,6 @@
1
1
  require 'thread'
2
2
  require 'concurrent/obligation'
3
+ require 'concurrent/options_parser'
3
4
 
4
5
  module Concurrent
5
6
 
@@ -51,32 +52,13 @@ module Concurrent
51
52
  @state = :pending
52
53
  @task = block
53
54
  set_deref_options(opts)
55
+ @task_executor = OptionsParser.get_task_executor_from(opts)
56
+ @computing = false
54
57
  end
55
58
 
56
- # Return the (possibly memoized) value of the delayed operation.
57
- #
58
- # If the state is `:pending` then the calling thread will block while the
59
- # operation is performed. All other threads simultaneously calling `#value`
60
- # will block as well. Once the operation is complete (either `:fulfilled` or
61
- # `:rejected`) all waiting threads will unblock and the new value will be
62
- # returned.
63
- #
64
- # If the state is not `:pending` when `#value` is called the (possibly memoized)
65
- # value will be returned without blocking and without performing the operation
66
- # again.
67
- #
68
- # Regardless of the final disposition all `Dereferenceable` options set during
69
- # object construction will be honored.
70
- #
71
- # @return [Object] the (possibly memoized) result of the block operation
72
- #
73
- # @see Concurrent::Dereferenceable
74
- def value
75
- mutex.lock
59
+ def wait(timeout)
76
60
  execute_task_once
77
- apply_deref_options(@value)
78
- ensure
79
- mutex.unlock
61
+ super timeout
80
62
  end
81
63
 
82
64
  # reconfigures the block returning the value if still #incomplete?
@@ -85,7 +67,7 @@ module Concurrent
85
67
  def reconfigure(&block)
86
68
  mutex.lock
87
69
  raise ArgumentError.new('no block given') unless block_given?
88
- if @state == :pending
70
+ unless @computing
89
71
  @task = block
90
72
  true
91
73
  else
@@ -98,13 +80,23 @@ module Concurrent
98
80
  private
99
81
 
100
82
  def execute_task_once
101
- if @state == :pending
102
- begin
103
- @value = @task.call
104
- @state = :fulfilled
105
- rescue => ex
106
- @reason = ex
107
- @state = :rejected
83
+ mutex.lock
84
+ execute = @computing = true unless @computing
85
+ task = @task
86
+ mutex.unlock
87
+
88
+ if execute
89
+ @task_executor.post do
90
+ begin
91
+ result = task.call
92
+ success = true
93
+ rescue => ex
94
+ reason = ex
95
+ end
96
+ mutex.lock
97
+ set_state success, result, reason
98
+ event.set
99
+ mutex.unlock
108
100
  end
109
101
  end
110
102
  end
@@ -250,8 +250,13 @@ module Concurrent
250
250
  end
251
251
 
252
252
  # @!macro executor_method_wait_for_termination
253
- def wait_for_termination(timeout)
254
- @executor.awaitTermination(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
253
+ def wait_for_termination(timeout = nil)
254
+ if timeout.nil?
255
+ ok = @executor.awaitTermination(60, java.util.concurrent.TimeUnit::SECONDS) until ok
256
+ true
257
+ else
258
+ @executor.awaitTermination(1000 * timeout, java.util.concurrent.TimeUnit::MILLISECONDS)
259
+ end
255
260
  end
256
261
 
257
262
  # @!macro executor_method_shutdown
@@ -23,7 +23,7 @@ module Concurrent
23
23
  # this executor rather than the global thread pool (overrides :operation)
24
24
  def initialize(opts = {})
25
25
  @queue = PriorityQueue.new(order: :min)
26
- @task_executor = OptionsParser::get_executor_from(opts)
26
+ @task_executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_task_pool
27
27
  @timer_executor = SingleThreadExecutor.new
28
28
  @condition = Condition.new
29
29
  init_executor
@@ -1,6 +1,7 @@
1
1
  require 'thread'
2
2
 
3
3
  require 'concurrent/options_parser'
4
+ require 'concurrent/ivar'
4
5
  require 'concurrent/executor/safe_task_executor'
5
6
 
6
7
  module Concurrent
@@ -61,7 +62,7 @@ module Concurrent
61
62
  super(IVar::NO_VALUE, opts)
62
63
  @state = :unscheduled
63
64
  @task = block
64
- @executor = OptionsParser::get_executor_from(opts)
65
+ @executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
65
66
  end
66
67
 
67
68
  # Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and
@@ -0,0 +1,58 @@
1
+ require 'concurrent/atomic'
2
+ require 'concurrent/delay'
3
+
4
+
5
+ module Concurrent
6
+ # Allows to store lazy evaluated values under keys. Uses `Delay`s.
7
+ # @example
8
+ # register = Concurrent::LazyRegister.new
9
+ # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::Atomic:0x007fd7ecd5e1e0>>
10
+ # register[:key]
11
+ # #=> nil
12
+ # register.add(:key) { Concurrent::Actor.spawn!(Actor::AdHoc, :ping) { -> message { message } } }
13
+ # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::Atomic:0x007fd7ecd5e1e0>>
14
+ # register[:key]
15
+ # #=> #<Concurrent::Actor::Reference /ping (Concurrent::Actor::AdHoc)>
16
+ class LazyRegister
17
+ def initialize
18
+ @data = Atomic.new Hash.new
19
+ end
20
+
21
+ # @param [Object] key
22
+ # @return value stored under the key
23
+ # @raise Exception when the initialization block fails
24
+ def [](key)
25
+ delay = @data.get[key]
26
+ delay.value! if delay
27
+ end
28
+
29
+ # @param [Object] key
30
+ # @return [true, false] if the key is registered
31
+ def registered?(key)
32
+ @data.get.key? key
33
+ end
34
+
35
+ alias_method :key?, :registered?
36
+
37
+ # @param [Object] key
38
+ # @yield the object to store under the key
39
+ # @return self
40
+ def register(key, &block)
41
+ delay = Delay.new(&block)
42
+ @data.update { |h| h.merge key => delay }
43
+ self
44
+ end
45
+
46
+ alias_method :add, :register
47
+
48
+ # Un-registers the object under key, realized or not
49
+ # @return self
50
+ # @param [Object] key
51
+ def unregister(key)
52
+ @data.update { |h| h.dup.tap { |h| h.delete(key) } }
53
+ self
54
+ end
55
+
56
+ alias_method :remove, :unregister
57
+ end
58
+ end
@@ -10,14 +10,16 @@ module Concurrent
10
10
  # @option opts [Boolean] :operation (`false`) when true use the global operation pool
11
11
  # @option opts [Boolean] :task (`true`) when true use the global task pool
12
12
  #
13
- # @return [Executor] the requested thread pool (default: global task pool)
13
+ # @return [Executor, nil] the requested thread pool, or nil when no option specified
14
14
  def get_executor_from(opts = {})
15
15
  if opts[:executor]
16
16
  opts[:executor]
17
17
  elsif opts[:operation] == true || opts[:task] == false
18
18
  Concurrent.configuration.global_operation_pool
19
- else
19
+ elsif opts[:operation] == false || opts[:task] == true
20
20
  Concurrent.configuration.global_task_pool
21
+ else
22
+ nil
21
23
  end
22
24
  end
23
25
 
@@ -5,6 +5,7 @@ require 'concurrent/options_parser'
5
5
 
6
6
  module Concurrent
7
7
 
8
+ # TODO unify promise and future to single class, with dataflow
8
9
  class Promise
9
10
  include Obligation
10
11
 
@@ -32,7 +33,7 @@ module Concurrent
32
33
  def initialize(opts = {}, &block)
33
34
  opts.delete_if { |k, v| v.nil? }
34
35
 
35
- @executor = OptionsParser::get_executor_from(opts)
36
+ @executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
36
37
  @parent = opts.fetch(:parent) { nil }
37
38
  @on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
38
39
  @on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
@@ -15,16 +15,17 @@ module Concurrent
15
15
  super(NO_VALUE, opts)
16
16
 
17
17
  self.observers = CopyOnNotifyObserverSet.new
18
- @intended_time = intended_time
19
- @state = :unscheduled
20
- @task = block
18
+ @intended_time = intended_time
19
+ @state = :unscheduled
20
+ @task = block
21
+ @executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
21
22
  end
22
23
 
23
24
  # @since 0.5.0
24
25
  def execute
25
26
  if compare_and_set_state(:pending, :unscheduled)
26
27
  @schedule_time = TimerSet.calculate_schedule_time(@intended_time)
27
- Concurrent::timer(@schedule_time.to_f - Time.now.to_f, &method(:process_task))
28
+ Concurrent::timer(@schedule_time.to_f - Time.now.to_f) { @executor.post &method(:process_task) }
28
29
  self
29
30
  end
30
31
  end
@@ -71,7 +72,7 @@ module Concurrent
71
72
  end
72
73
 
73
74
  time = Time.now
74
- observers.notify_and_delete_observers{ [time, self.value, reason] }
75
+ observers.notify_and_delete_observers { [time, self.value, reason] }
75
76
  end
76
77
  end
77
78
  end
@@ -1,7 +1,5 @@
1
1
  require 'set'
2
2
 
3
- require 'concurrent/atomic/thread_local_var'
4
-
5
3
  module Concurrent
6
4
 
7
5
  # A `TVar` is a transactional variable - a single-element container that
@@ -97,7 +95,7 @@ module Concurrent
97
95
 
98
96
  begin
99
97
  # Retry loop
100
-
98
+
101
99
  loop do
102
100
 
103
101
  # Create a new transaction
@@ -149,8 +147,6 @@ module Concurrent
149
147
 
150
148
  ABORTED = Object.new
151
149
 
152
- CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
153
-
154
150
  ReadLogEntry = Struct.new(:tvar, :version)
155
151
  UndoLogEntry = Struct.new(:tvar, :value)
156
152
 
@@ -158,8 +154,8 @@ module Concurrent
158
154
 
159
155
  def initialize
160
156
  @write_set = Set.new
161
- @read_log = []
162
- @undo_log = []
157
+ @read_log = []
158
+ @undo_log = []
163
159
  end
164
160
 
165
161
  def read(tvar)
@@ -217,7 +213,7 @@ module Concurrent
217
213
  end
218
214
 
219
215
  unlock
220
-
216
+
221
217
  true
222
218
  end
223
219
 
@@ -240,11 +236,11 @@ module Concurrent
240
236
  end
241
237
 
242
238
  def self.current
243
- CURRENT_TRANSACTION.value
239
+ Thread.current[:current_tvar_transaction]
244
240
  end
245
241
 
246
242
  def self.current=(transaction)
247
- CURRENT_TRANSACTION.value = transaction
243
+ Thread.current[:current_tvar_transaction] = transaction
248
244
  end
249
245
 
250
246
  end
@@ -1,12 +1,14 @@
1
1
  require 'rbconfig'
2
2
  require 'concurrent/delay'
3
+ require 'concurrent/executor/immediate_executor'
3
4
 
4
5
  module Concurrent
5
6
 
6
7
  class ProcessorCounter
7
8
  def initialize
8
- @processor_count = Delay.new { compute_processor_count }
9
- @physical_processor_count = Delay.new { compute_physical_processor_count }
9
+ immediate_executor = ImmediateExecutor.new
10
+ @processor_count = Delay.new(executor: immediate_executor) { compute_processor_count }
11
+ @physical_processor_count = Delay.new(executor: immediate_executor) { compute_physical_processor_count }
10
12
  end
11
13
 
12
14
  # Number of processors seen by the OS and used for process scheduling. For performance