concurrent-ruby 0.6.0.pre.2 → 0.6.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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -8
  3. data/lib/concurrent.rb +2 -0
  4. data/lib/concurrent/actor/actor.rb +3 -3
  5. data/lib/concurrent/actor/postable.rb +1 -1
  6. data/lib/concurrent/actors.rb +0 -3
  7. data/lib/concurrent/actress.rb +75 -0
  8. data/lib/concurrent/actress/ad_hoc.rb +14 -0
  9. data/lib/concurrent/actress/context.rb +96 -0
  10. data/lib/concurrent/actress/core.rb +204 -0
  11. data/lib/concurrent/actress/core_delegations.rb +37 -0
  12. data/lib/concurrent/actress/doc.md +53 -0
  13. data/lib/concurrent/actress/envelope.rb +25 -0
  14. data/lib/concurrent/actress/errors.rb +14 -0
  15. data/lib/concurrent/actress/reference.rb +64 -0
  16. data/lib/concurrent/actress/type_check.rb +48 -0
  17. data/lib/concurrent/agent.rb +20 -11
  18. data/lib/concurrent/async.rb +54 -25
  19. data/lib/concurrent/atomic/atomic.rb +48 -0
  20. data/lib/concurrent/atomic/atomic_boolean.rb +13 -13
  21. data/lib/concurrent/atomic/atomic_fixnum.rb +9 -17
  22. data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +7 -4
  23. data/lib/concurrent/atomic/copy_on_write_observer_set.rb +16 -14
  24. data/lib/concurrent/atomic/event.rb +11 -16
  25. data/lib/concurrent/atomics.rb +1 -0
  26. data/lib/concurrent/channel/channel.rb +4 -2
  27. data/lib/concurrent/collection/blocking_ring_buffer.rb +1 -1
  28. data/lib/concurrent/configuration.rb +59 -47
  29. data/lib/concurrent/delay.rb +28 -12
  30. data/lib/concurrent/dereferenceable.rb +6 -6
  31. data/lib/concurrent/errors.rb +30 -0
  32. data/lib/concurrent/executor/executor.rb +11 -4
  33. data/lib/concurrent/executor/immediate_executor.rb +1 -0
  34. data/lib/concurrent/executor/java_thread_pool_executor.rb +4 -0
  35. data/lib/concurrent/executor/one_by_one.rb +24 -12
  36. data/lib/concurrent/executor/per_thread_executor.rb +1 -0
  37. data/lib/concurrent/executor/ruby_single_thread_executor.rb +2 -1
  38. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +7 -2
  39. data/lib/concurrent/executor/ruby_thread_pool_worker.rb +3 -0
  40. data/lib/concurrent/executor/timer_set.rb +1 -1
  41. data/lib/concurrent/future.rb +0 -2
  42. data/lib/concurrent/ivar.rb +31 -6
  43. data/lib/concurrent/logging.rb +17 -0
  44. data/lib/concurrent/mvar.rb +45 -0
  45. data/lib/concurrent/obligation.rb +61 -20
  46. data/lib/concurrent/observable.rb +7 -0
  47. data/lib/concurrent/promise.rb +1 -0
  48. data/lib/concurrent/runnable.rb +2 -2
  49. data/lib/concurrent/supervisor.rb +1 -2
  50. data/lib/concurrent/timer_task.rb +17 -13
  51. data/lib/concurrent/tvar.rb +113 -73
  52. data/lib/concurrent/utility/processor_count.rb +141 -116
  53. data/lib/concurrent/utility/timeout.rb +4 -5
  54. data/lib/concurrent/version.rb +1 -1
  55. data/spec/concurrent/actor/postable_shared.rb +1 -1
  56. data/spec/concurrent/actress_spec.rb +191 -0
  57. data/spec/concurrent/async_spec.rb +35 -3
  58. data/spec/concurrent/atomic/atomic_boolean_spec.rb +1 -1
  59. data/spec/concurrent/atomic/atomic_fixnum_spec.rb +1 -1
  60. data/spec/concurrent/atomic/atomic_spec.rb +133 -0
  61. data/spec/concurrent/atomic/count_down_latch_spec.rb +1 -1
  62. data/spec/concurrent/collection/priority_queue_spec.rb +1 -1
  63. data/spec/concurrent/configuration_spec.rb +5 -2
  64. data/spec/concurrent/delay_spec.rb +14 -0
  65. data/spec/concurrent/exchanger_spec.rb +4 -9
  66. data/spec/concurrent/executor/java_cached_thread_pool_spec.rb +1 -1
  67. data/spec/concurrent/executor/java_fixed_thread_pool_spec.rb +1 -1
  68. data/spec/concurrent/executor/java_single_thread_executor_spec.rb +1 -1
  69. data/spec/concurrent/executor/java_thread_pool_executor_spec.rb +1 -1
  70. data/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb +4 -4
  71. data/spec/concurrent/ivar_spec.rb +2 -2
  72. data/spec/concurrent/obligation_spec.rb +113 -24
  73. data/spec/concurrent/observable_shared.rb +4 -0
  74. data/spec/concurrent/observable_spec.rb +8 -3
  75. data/spec/concurrent/runnable_spec.rb +2 -2
  76. data/spec/concurrent/scheduled_task_spec.rb +1 -0
  77. data/spec/concurrent/supervisor_spec.rb +26 -11
  78. data/spec/concurrent/timer_task_spec.rb +36 -35
  79. data/spec/concurrent/tvar_spec.rb +1 -1
  80. data/spec/concurrent/utility/timer_spec.rb +8 -8
  81. data/spec/spec_helper.rb +8 -18
  82. data/spec/support/example_group_extensions.rb +48 -0
  83. metadata +23 -16
  84. data/lib/concurrent/actor/actor_context.rb +0 -77
  85. data/lib/concurrent/actor/actor_ref.rb +0 -67
  86. data/lib/concurrent/actor/simple_actor_ref.rb +0 -94
  87. data/lib/concurrent_ruby_ext.bundle +0 -0
  88. data/spec/concurrent/actor/actor_context_spec.rb +0 -29
  89. data/spec/concurrent/actor/actor_ref_shared.rb +0 -263
  90. data/spec/concurrent/actor/simple_actor_ref_spec.rb +0 -135
  91. data/spec/support/functions.rb +0 -25
@@ -42,23 +42,59 @@ module Concurrent
42
42
  [:unscheduled, :pending].include? state
43
43
  end
44
44
 
45
+ # @return [Object] see Dereferenceable#deref
45
46
  def value(timeout = nil)
47
+ wait timeout
48
+ deref
49
+ end
50
+
51
+ # wait until Obligation is #complete?
52
+ # @param [Numeric] timeout the maximum time in second to wait.
53
+ # @return [Obligation] self
54
+ def wait(timeout = nil)
46
55
  event.wait(timeout) if timeout != 0 && incomplete?
47
- super()
56
+ self
57
+ end
58
+
59
+ # wait until Obligation is #complete?
60
+ # @param [Numeric] timeout the maximum time in second to wait.
61
+ # @return [Obligation] self
62
+ # @raise [Exception] when #rejected? it raises #reason
63
+ def no_error!(timeout = nil)
64
+ wait(timeout).tap { raise self if rejected? }
65
+ end
66
+
67
+ # @raise [Exception] when #rejected? it raises #reason
68
+ # @return [Object] see Dereferenceable#deref
69
+ def value!(timeout = nil)
70
+ wait(timeout)
71
+ if rejected?
72
+ raise self
73
+ else
74
+ deref
75
+ end
48
76
  end
49
77
 
50
78
  def state
51
79
  mutex.lock
52
- result = @state
80
+ @state
81
+ ensure
53
82
  mutex.unlock
54
- result
55
83
  end
56
84
 
57
85
  def reason
58
86
  mutex.lock
59
- result = @reason
87
+ @reason
88
+ ensure
60
89
  mutex.unlock
61
- result
90
+ end
91
+
92
+ # @example allows Obligation to be risen
93
+ # rejected_ivar = Ivar.new.fail
94
+ # raise rejected_ivar
95
+ def exception(*args)
96
+ raise 'obligation is not rejected' unless rejected?
97
+ reason.exception(*args)
62
98
  end
63
99
 
64
100
  protected
@@ -81,13 +117,16 @@ module Concurrent
81
117
  @state = :fulfilled
82
118
  else
83
119
  @reason = reason
84
- @state = :rejected
120
+ @state = :rejected
85
121
  end
86
122
  end
87
123
 
88
124
  # @!visibility private
89
125
  def state=(value) # :nodoc:
90
- mutex.synchronize { @state = value }
126
+ mutex.lock
127
+ @state = value
128
+ ensure
129
+ mutex.unlock
91
130
  end
92
131
 
93
132
  # atomic compare and set operation
@@ -100,14 +139,15 @@ module Concurrent
100
139
  #
101
140
  # @!visibility private
102
141
  def compare_and_set_state(next_state, expected_current) # :nodoc:
103
- mutex.synchronize do
104
- if @state == expected_current
105
- @state = next_state
106
- true
107
- else
108
- false
109
- end
142
+ mutex.lock
143
+ if @state == expected_current
144
+ @state = next_state
145
+ true
146
+ else
147
+ false
110
148
  end
149
+ ensure
150
+ mutex.unlock
111
151
  end
112
152
 
113
153
  # executes the block within mutex if current state is included in expected_states
@@ -116,15 +156,16 @@ module Concurrent
116
156
  #
117
157
  # @!visibility private
118
158
  def if_state(*expected_states) # :nodoc:
159
+ mutex.lock
119
160
  raise ArgumentError.new('no block given') unless block_given?
120
161
 
121
- mutex.synchronize do
122
- if expected_states.include? @state
123
- yield
124
- else
125
- false
126
- end
162
+ if expected_states.include? @state
163
+ yield
164
+ else
165
+ false
127
166
  end
167
+ ensure
168
+ mutex.unlock
128
169
  end
129
170
  end
130
171
  end
@@ -10,6 +10,13 @@ module Concurrent
10
10
  observers.add_observer(*args, &block)
11
11
  end
12
12
 
13
+ # as #add_observer but it can be used for chaning
14
+ # @return [Observable] self
15
+ def with_observer(*args, &block)
16
+ add_observer *args, &block
17
+ self
18
+ end
19
+
13
20
  # @return [Object] the deleted observer
14
21
  def delete_observer(*args)
15
22
  observers.delete_observer(*args)
@@ -162,6 +162,7 @@ module Concurrent
162
162
  def synchronized_set_state!(success, value, reason)
163
163
  mutex.lock
164
164
  set_state!(success, value, reason)
165
+ ensure
165
166
  mutex.unlock
166
167
  end
167
168
  end
@@ -1,11 +1,11 @@
1
1
  require 'thread'
2
2
 
3
+ require 'concurrent/errors'
4
+
3
5
  module Concurrent
4
6
 
5
7
  module Runnable
6
8
 
7
- LifecycleError = Class.new(StandardError)
8
-
9
9
  class Context
10
10
  attr_reader :runner, :thread
11
11
  def initialize(runner)
@@ -1,5 +1,6 @@
1
1
  require 'thread'
2
2
 
3
+ require 'concurrent/errors'
3
4
  require 'concurrent/runnable'
4
5
 
5
6
  module Concurrent
@@ -15,8 +16,6 @@ module Concurrent
15
16
  CHILD_TYPES = [:worker, :supervisor]
16
17
  CHILD_RESTART_OPTIONS = [:permanent, :transient, :temporary]
17
18
 
18
- MaxRestartFrequencyError = Class.new(StandardError)
19
-
20
19
  WorkerContext = Struct.new(:worker, :type, :restart) do
21
20
  attr_accessor :thread
22
21
  attr_accessor :terminated
@@ -146,7 +146,7 @@ module Concurrent
146
146
  # @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
147
147
  class TimerTask
148
148
  include Dereferenceable
149
- include Executor
149
+ include RubyExecutor
150
150
  include Concurrent::Observable
151
151
 
152
152
  # Default `:execution_interval` in seconds.
@@ -248,9 +248,9 @@ module Concurrent
248
248
  # task is performed again.
249
249
  def execution_interval
250
250
  mutex.lock
251
- result = @execution_interval
251
+ @execution_interval
252
+ ensure
252
253
  mutex.unlock
253
- result
254
254
  end
255
255
 
256
256
  # @!attribute [rw] execution_interval
@@ -260,10 +260,12 @@ module Concurrent
260
260
  if (value = value.to_f) <= 0.0
261
261
  raise ArgumentError.new('must be greater than zero')
262
262
  else
263
- mutex.lock
264
- result = @execution_interval = value
265
- mutex.unlock
266
- result
263
+ begin
264
+ mutex.lock
265
+ @execution_interval = value
266
+ ensure
267
+ mutex.unlock
268
+ end
267
269
  end
268
270
  end
269
271
 
@@ -272,9 +274,9 @@ module Concurrent
272
274
  # considered to have failed.
273
275
  def timeout_interval
274
276
  mutex.lock
275
- result = @timeout_interval
277
+ @timeout_interval
278
+ ensure
276
279
  mutex.unlock
277
- result
278
280
  end
279
281
 
280
282
  # @!attribute [rw] timeout_interval
@@ -284,10 +286,12 @@ module Concurrent
284
286
  if (value = value.to_f) <= 0.0
285
287
  raise ArgumentError.new('must be greater than zero')
286
288
  else
287
- mutex.lock
288
- result = @timeout_interval = value
289
- mutex.unlock
290
- result
289
+ begin
290
+ mutex.lock
291
+ @timeout_interval = value
292
+ ensure
293
+ mutex.unlock
294
+ end
291
295
  end
292
296
  end
293
297
 
@@ -4,57 +4,158 @@ require 'concurrent/atomic/thread_local_var'
4
4
 
5
5
  module Concurrent
6
6
 
7
- ABORTED = Object.new
8
-
9
- CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
10
-
11
- ReadLogEntry = Struct.new(:tvar, :version)
12
- UndoLogEntry = Struct.new(:tvar, :value)
13
-
7
+ # A `TVar` is a transactional variable - a single-element container that
8
+ # is used as part of a transaction - see `Concurrent::atomically`.
14
9
  class TVar
15
10
 
11
+ # Create a new `TVar` with an initial value.
16
12
  def initialize(value)
17
13
  @value = value
18
14
  @version = 0
19
15
  @lock = Mutex.new
20
16
  end
21
17
 
18
+ # Get the value of a `TVar`.
22
19
  def value
23
20
  Concurrent::atomically do
24
21
  Transaction::current.read(self)
25
22
  end
26
23
  end
27
24
 
25
+ # Set the value of a `TVar`.
28
26
  def value=(value)
29
27
  Concurrent::atomically do
30
28
  Transaction::current.write(self, value)
31
29
  end
32
30
  end
33
31
 
34
- def unsafe_value
32
+ # @!visibility private
33
+ def unsafe_value # :nodoc:
35
34
  @value
36
35
  end
37
36
 
38
- def unsafe_value=(value)
37
+ # @!visibility private
38
+ def unsafe_value=(value) # :nodoc:
39
39
  @value = value
40
40
  end
41
41
 
42
- def unsafe_version
42
+ # @!visibility private
43
+ def unsafe_version # :nodoc:
43
44
  @version
44
45
  end
45
46
 
46
- def unsafe_increment_version
47
+ # @!visibility private
48
+ def unsafe_increment_version # :nodoc:
47
49
  @version += 1
48
50
  end
49
51
 
50
- def unsafe_lock
52
+ # @!visibility private
53
+ def unsafe_lock # :nodoc:
51
54
  @lock
52
55
  end
53
56
 
54
57
  end
55
58
 
59
+ # Run a block that reads and writes `TVar`s as a single atomic transaction.
60
+ # With respect to the value of `TVar` objects, the transaction is atomic,
61
+ # in that it either happens or it does not, consistent, in that the `TVar`
62
+ # objects involved will never enter an illegal state, and isolated, in that
63
+ # transactions never interfere with each other. You may recognise these
64
+ # properties from database transactions.
65
+ #
66
+ # There are some very important and unusual semantics that you must be aware of:
67
+ #
68
+ # * Most importantly, the block that you pass to atomically may be executed more than once. In most cases your code should be free of side-effects, except for via TVar.
69
+ #
70
+ # * If an exception escapes an atomically block it will abort the transaction.
71
+ #
72
+ # * It is undefined behaviour to use callcc or Fiber with atomically.
73
+ #
74
+ # * If you create a new thread within an atomically, it will not be part of the transaction. Creating a thread counts as a side-effect.
75
+ #
76
+ # Transactions within transactions are flattened to a single transaction.
77
+ #
78
+ # @example
79
+ # a = new TVar(100_000)
80
+ # b = new TVar(100)
81
+ #
82
+ # Concurrent::atomically do
83
+ # a.value -= 10
84
+ # b.value += 10
85
+ # end
86
+ def atomically
87
+ raise ArgumentError.new('no block given') unless block_given?
88
+
89
+ # Get the current transaction
90
+
91
+ transaction = Transaction::current
92
+
93
+ # Are we not already in a transaction (not nested)?
94
+
95
+ if transaction.nil?
96
+ # New transaction
97
+
98
+ begin
99
+ # Retry loop
100
+
101
+ loop do
102
+
103
+ # Create a new transaction
104
+
105
+ transaction = Transaction.new
106
+ Transaction::current = transaction
107
+
108
+ # Run the block, aborting on exceptions
109
+
110
+ begin
111
+ result = yield
112
+ rescue Transaction::AbortError => e
113
+ transaction.abort
114
+ result = Transaction::ABORTED
115
+ rescue => e
116
+ transaction.abort
117
+ throw e
118
+ end
119
+ # If we can commit, break out of the loop
120
+
121
+ if result != Transaction::ABORTED
122
+ if transaction.commit
123
+ break result
124
+ end
125
+ end
126
+ end
127
+ ensure
128
+ # Clear the current transaction
129
+
130
+ Transaction::current = nil
131
+ end
132
+ else
133
+ # Nested transaction - flatten it and just run the block
134
+
135
+ yield
136
+ end
137
+ end
138
+
139
+ # Abort a currently running transaction - see `Concurrent::atomically`.
140
+ def abort_transaction
141
+ raise Transaction::AbortError.new
142
+ end
143
+
144
+ module_function :atomically, :abort_transaction
145
+
146
+ private
147
+
56
148
  class Transaction
57
149
 
150
+ ABORTED = Object.new
151
+
152
+ CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
153
+
154
+ ReadLogEntry = Struct.new(:tvar, :version)
155
+ UndoLogEntry = Struct.new(:tvar, :value)
156
+
157
+ AbortError = Class.new(StandardError)
158
+
58
159
  def initialize
59
160
  @write_set = Set.new
60
161
  @read_log = []
@@ -148,65 +249,4 @@ module Concurrent
148
249
 
149
250
  end
150
251
 
151
- AbortError = Class.new(StandardError)
152
-
153
- def atomically
154
- raise ArgumentError.new('no block given') unless block_given?
155
-
156
- # Get the current transaction
157
-
158
- transaction = Transaction::current
159
-
160
- # Are we not already in a transaction (not nested)?
161
-
162
- if transaction.nil?
163
- # New transaction
164
-
165
- begin
166
- # Retry loop
167
-
168
- loop do
169
-
170
- # Create a new transaction
171
-
172
- transaction = Transaction.new
173
- Transaction::current = transaction
174
-
175
- # Run the block, aborting on exceptions
176
-
177
- begin
178
- result = yield
179
- rescue AbortError => e
180
- transaction.abort
181
- result = ABORTED
182
- rescue => e
183
- transaction.abort
184
- throw e
185
- end
186
- # If we can commit, break out of the loop
187
-
188
- if result != ABORTED
189
- if transaction.commit
190
- break result
191
- end
192
- end
193
- end
194
- ensure
195
- # Clear the current transaction
196
-
197
- Transaction::current = nil
198
- end
199
- else
200
- # Nested transaction - flatten it and just run the block
201
-
202
- yield
203
- end
204
- end
205
-
206
- def abort_transaction
207
- raise AbortError.new
208
- end
209
-
210
- module_function :atomically, :abort_transaction
211
-
212
252
  end