concurrent-ruby 0.6.0.pre.1 → 0.6.0.pre.2

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -0
  3. data/lib/concurrent.rb +9 -29
  4. data/lib/concurrent/{actor.rb → actor/actor.rb} +3 -3
  5. data/lib/concurrent/actor/actor_context.rb +77 -0
  6. data/lib/concurrent/actor/actor_ref.rb +67 -0
  7. data/lib/concurrent/{postable.rb → actor/postable.rb} +1 -1
  8. data/lib/concurrent/actor/simple_actor_ref.rb +94 -0
  9. data/lib/concurrent/actors.rb +5 -0
  10. data/lib/concurrent/agent.rb +81 -47
  11. data/lib/concurrent/async.rb +35 -35
  12. data/lib/concurrent/atomic/atomic_boolean.rb +157 -0
  13. data/lib/concurrent/atomic/atomic_fixnum.rb +170 -0
  14. data/lib/concurrent/{condition.rb → atomic/condition.rb} +0 -0
  15. data/lib/concurrent/{copy_on_notify_observer_set.rb → atomic/copy_on_notify_observer_set.rb} +48 -13
  16. data/lib/concurrent/{copy_on_write_observer_set.rb → atomic/copy_on_write_observer_set.rb} +41 -20
  17. data/lib/concurrent/atomic/count_down_latch.rb +116 -0
  18. data/lib/concurrent/atomic/cyclic_barrier.rb +106 -0
  19. data/lib/concurrent/atomic/event.rb +103 -0
  20. data/lib/concurrent/{thread_local_var.rb → atomic/thread_local_var.rb} +0 -0
  21. data/lib/concurrent/atomics.rb +9 -0
  22. data/lib/concurrent/channel/buffered_channel.rb +6 -4
  23. data/lib/concurrent/channel/channel.rb +30 -2
  24. data/lib/concurrent/channel/unbuffered_channel.rb +2 -2
  25. data/lib/concurrent/channel/waitable_list.rb +3 -1
  26. data/lib/concurrent/channels.rb +5 -0
  27. data/lib/concurrent/{channel → collection}/blocking_ring_buffer.rb +16 -5
  28. data/lib/concurrent/collection/priority_queue.rb +305 -0
  29. data/lib/concurrent/{channel → collection}/ring_buffer.rb +6 -1
  30. data/lib/concurrent/collections.rb +3 -0
  31. data/lib/concurrent/configuration.rb +68 -19
  32. data/lib/concurrent/dataflow.rb +9 -9
  33. data/lib/concurrent/delay.rb +21 -13
  34. data/lib/concurrent/dereferenceable.rb +40 -33
  35. data/lib/concurrent/exchanger.rb +3 -0
  36. data/lib/concurrent/{cached_thread_pool.rb → executor/cached_thread_pool.rb} +8 -9
  37. data/lib/concurrent/executor/executor.rb +222 -0
  38. data/lib/concurrent/{fixed_thread_pool.rb → executor/fixed_thread_pool.rb} +6 -7
  39. data/lib/concurrent/{immediate_executor.rb → executor/immediate_executor.rb} +5 -5
  40. data/lib/concurrent/executor/java_cached_thread_pool.rb +31 -0
  41. data/lib/concurrent/{java_fixed_thread_pool.rb → executor/java_fixed_thread_pool.rb} +7 -11
  42. data/lib/concurrent/executor/java_single_thread_executor.rb +21 -0
  43. data/lib/concurrent/{java_thread_pool_executor.rb → executor/java_thread_pool_executor.rb} +66 -77
  44. data/lib/concurrent/executor/one_by_one.rb +65 -0
  45. data/lib/concurrent/{per_thread_executor.rb → executor/per_thread_executor.rb} +4 -4
  46. data/lib/concurrent/executor/ruby_cached_thread_pool.rb +29 -0
  47. data/lib/concurrent/{ruby_fixed_thread_pool.rb → executor/ruby_fixed_thread_pool.rb} +5 -4
  48. data/lib/concurrent/executor/ruby_single_thread_executor.rb +72 -0
  49. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +282 -0
  50. data/lib/concurrent/{ruby_thread_pool_worker.rb → executor/ruby_thread_pool_worker.rb} +6 -6
  51. data/lib/concurrent/{safe_task_executor.rb → executor/safe_task_executor.rb} +20 -13
  52. data/lib/concurrent/executor/single_thread_executor.rb +35 -0
  53. data/lib/concurrent/executor/thread_pool_executor.rb +68 -0
  54. data/lib/concurrent/executor/timer_set.rb +138 -0
  55. data/lib/concurrent/executors.rb +9 -0
  56. data/lib/concurrent/future.rb +39 -40
  57. data/lib/concurrent/ivar.rb +22 -15
  58. data/lib/concurrent/mvar.rb +2 -1
  59. data/lib/concurrent/obligation.rb +9 -3
  60. data/lib/concurrent/observable.rb +33 -0
  61. data/lib/concurrent/options_parser.rb +46 -0
  62. data/lib/concurrent/promise.rb +23 -24
  63. data/lib/concurrent/scheduled_task.rb +21 -45
  64. data/lib/concurrent/timer_task.rb +204 -126
  65. data/lib/concurrent/tvar.rb +1 -1
  66. data/lib/concurrent/utilities.rb +3 -36
  67. data/lib/concurrent/{processor_count.rb → utility/processor_count.rb} +1 -1
  68. data/lib/concurrent/utility/timeout.rb +36 -0
  69. data/lib/concurrent/utility/timer.rb +21 -0
  70. data/lib/concurrent/version.rb +1 -1
  71. data/lib/concurrent_ruby_ext.bundle +0 -0
  72. data/spec/concurrent/{actor_context_spec.rb → actor/actor_context_spec.rb} +0 -8
  73. data/spec/concurrent/{actor_ref_shared.rb → actor/actor_ref_shared.rb} +9 -59
  74. data/spec/concurrent/{actor_spec.rb → actor/actor_spec.rb} +43 -41
  75. data/spec/concurrent/{postable_shared.rb → actor/postable_shared.rb} +0 -0
  76. data/spec/concurrent/actor/simple_actor_ref_spec.rb +135 -0
  77. data/spec/concurrent/agent_spec.rb +160 -71
  78. data/spec/concurrent/atomic/atomic_boolean_spec.rb +172 -0
  79. data/spec/concurrent/atomic/atomic_fixnum_spec.rb +186 -0
  80. data/spec/concurrent/{condition_spec.rb → atomic/condition_spec.rb} +2 -2
  81. data/spec/concurrent/{copy_on_notify_observer_set_spec.rb → atomic/copy_on_notify_observer_set_spec.rb} +0 -0
  82. data/spec/concurrent/{copy_on_write_observer_set_spec.rb → atomic/copy_on_write_observer_set_spec.rb} +0 -0
  83. data/spec/concurrent/atomic/count_down_latch_spec.rb +151 -0
  84. data/spec/concurrent/atomic/cyclic_barrier_spec.rb +248 -0
  85. data/spec/concurrent/{event_spec.rb → atomic/event_spec.rb} +18 -3
  86. data/spec/concurrent/{observer_set_shared.rb → atomic/observer_set_shared.rb} +15 -6
  87. data/spec/concurrent/{thread_local_var_spec.rb → atomic/thread_local_var_spec.rb} +0 -0
  88. data/spec/concurrent/channel/buffered_channel_spec.rb +1 -1
  89. data/spec/concurrent/channel/channel_spec.rb +6 -4
  90. data/spec/concurrent/channel/probe_spec.rb +37 -9
  91. data/spec/concurrent/channel/unbuffered_channel_spec.rb +2 -2
  92. data/spec/concurrent/{channel → collection}/blocking_ring_buffer_spec.rb +0 -0
  93. data/spec/concurrent/collection/priority_queue_spec.rb +317 -0
  94. data/spec/concurrent/{channel → collection}/ring_buffer_spec.rb +0 -0
  95. data/spec/concurrent/configuration_spec.rb +4 -70
  96. data/spec/concurrent/dereferenceable_shared.rb +5 -4
  97. data/spec/concurrent/exchanger_spec.rb +10 -5
  98. data/spec/concurrent/{cached_thread_pool_shared.rb → executor/cached_thread_pool_shared.rb} +15 -37
  99. data/spec/concurrent/{fixed_thread_pool_shared.rb → executor/fixed_thread_pool_shared.rb} +0 -0
  100. data/spec/concurrent/{global_thread_pool_shared.rb → executor/global_thread_pool_shared.rb} +10 -8
  101. data/spec/concurrent/{immediate_executor_spec.rb → executor/immediate_executor_spec.rb} +0 -0
  102. data/spec/concurrent/{java_cached_thread_pool_spec.rb → executor/java_cached_thread_pool_spec.rb} +1 -21
  103. data/spec/concurrent/{java_fixed_thread_pool_spec.rb → executor/java_fixed_thread_pool_spec.rb} +0 -0
  104. data/spec/concurrent/executor/java_single_thread_executor_spec.rb +21 -0
  105. data/spec/concurrent/{java_thread_pool_executor_spec.rb → executor/java_thread_pool_executor_spec.rb} +0 -0
  106. data/spec/concurrent/{per_thread_executor_spec.rb → executor/per_thread_executor_spec.rb} +0 -4
  107. data/spec/concurrent/{ruby_cached_thread_pool_spec.rb → executor/ruby_cached_thread_pool_spec.rb} +1 -1
  108. data/spec/concurrent/{ruby_fixed_thread_pool_spec.rb → executor/ruby_fixed_thread_pool_spec.rb} +0 -0
  109. data/spec/concurrent/executor/ruby_single_thread_executor_spec.rb +18 -0
  110. data/spec/concurrent/{ruby_thread_pool_executor_spec.rb → executor/ruby_thread_pool_executor_spec.rb} +12 -24
  111. data/spec/concurrent/executor/safe_task_executor_spec.rb +103 -0
  112. data/spec/concurrent/{thread_pool_class_cast_spec.rb → executor/thread_pool_class_cast_spec.rb} +12 -0
  113. data/spec/concurrent/{thread_pool_executor_shared.rb → executor/thread_pool_executor_shared.rb} +0 -0
  114. data/spec/concurrent/{thread_pool_shared.rb → executor/thread_pool_shared.rb} +84 -119
  115. data/spec/concurrent/executor/timer_set_spec.rb +183 -0
  116. data/spec/concurrent/future_spec.rb +12 -0
  117. data/spec/concurrent/ivar_spec.rb +11 -1
  118. data/spec/concurrent/observable_shared.rb +173 -0
  119. data/spec/concurrent/observable_spec.rb +51 -0
  120. data/spec/concurrent/options_parser_spec.rb +71 -0
  121. data/spec/concurrent/runnable_shared.rb +6 -0
  122. data/spec/concurrent/scheduled_task_spec.rb +60 -40
  123. data/spec/concurrent/timer_task_spec.rb +130 -144
  124. data/spec/concurrent/{processor_count_spec.rb → utility/processor_count_spec.rb} +0 -0
  125. data/spec/concurrent/{utilities_spec.rb → utility/timeout_spec.rb} +0 -0
  126. data/spec/concurrent/utility/timer_spec.rb +52 -0
  127. metadata +147 -108
  128. data/lib/concurrent/actor_context.rb +0 -31
  129. data/lib/concurrent/actor_ref.rb +0 -39
  130. data/lib/concurrent/atomic.rb +0 -121
  131. data/lib/concurrent/channel/probe.rb +0 -19
  132. data/lib/concurrent/count_down_latch.rb +0 -60
  133. data/lib/concurrent/event.rb +0 -80
  134. data/lib/concurrent/java_cached_thread_pool.rb +0 -45
  135. data/lib/concurrent/ruby_cached_thread_pool.rb +0 -37
  136. data/lib/concurrent/ruby_thread_pool_executor.rb +0 -268
  137. data/lib/concurrent/simple_actor_ref.rb +0 -124
  138. data/lib/concurrent/thread_pool_executor.rb +0 -30
  139. data/spec/concurrent/atomic_spec.rb +0 -201
  140. data/spec/concurrent/count_down_latch_spec.rb +0 -125
  141. data/spec/concurrent/safe_task_executor_spec.rb +0 -58
  142. data/spec/concurrent/simple_actor_ref_spec.rb +0 -219
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e4a4e756232abaffd4ca55c37e049af25f9884a
4
- data.tar.gz: 9345ebbf4f1a05e1c5b0eccd8df0d111407c293e
3
+ metadata.gz: 873e9e671b06398c80e8c918e5e2ba7c0d75564e
4
+ data.tar.gz: 69963957bf5e06a7a5b7ca21a678039496874c35
5
5
  SHA512:
6
- metadata.gz: 1e5b34f7f631e3cb76248674f8cae5b1bee56c8485c71f32cff53d2625fb5475d84e7ff0b1f07272efbfe860652f391ea5f1078652e0c47edc2117a5099152e1
7
- data.tar.gz: 5c736c6a669fd952268275f9b1e802f32eac4dc78386df90b871f1bb6dc66985b9b5e7e2046ce5bf81235608c1bc3340a1dcbd49573fb65d8cacf8b6b53f3d06
6
+ metadata.gz: 92bb87ece26412064c4d9697b1d92ce1cc4963d74b6f9c32b32db091d216ff8655bbd1961322db8618666a571f4f9559cf7823aa9db1fa33687b2c45db1bb57a
7
+ data.tar.gz: 0d2645e119b50b88748b6131bcc984e127399bb9f5988fdfaa095fe915149ad03e60ab530523736006d3eb71a1b86821b6a889c032e9bd3c5d6a04a821d8ca2d
data/README.md CHANGED
@@ -33,6 +33,20 @@ The design goals of this gem are:
33
33
  </tr>
34
34
  </table>
35
35
 
36
+ ### Install
37
+
38
+ ```shell
39
+ gem install concurrent-ruby
40
+ ```
41
+ or add the following line to Gemfile:
42
+
43
+ ```ruby
44
+ gem 'concurrent-ruby'
45
+ ```
46
+ and run `bundle install` from your shell.
47
+
48
+ *NOTE: There is an old gem from 2007 called "concurrent" that does not appear to be under active development. That isn't us. Please do not run* `gem install concurrent`*. It is not the droid you are looking for.*
49
+
36
50
  ## Features & Documentation
37
51
 
38
52
  Please see the [Concurrent Ruby Wiki](https://github.com/jdantonio/concurrent-ruby/wiki)
@@ -128,6 +142,8 @@ ibm.value #=> 187.57
128
142
  * [Michele Della Torre](https://github.com/mighe)
129
143
  * [Chris Seaton](https://github.com/chrisseaton)
130
144
  * [Lucas Allan](https://github.com/lucasallan)
145
+ * [Petr Chalupa](https://github.com/pitr-ch)
146
+ * [Ravil Bayramgalin](https://github.com/brainopia)
131
147
  * [Giuseppe Capizzi](https://github.com/gcapizzi)
132
148
  * [Brian Shirai](https://github.com/brixen)
133
149
  * [Chip Miller](https://github.com/chip-miller)
@@ -1,52 +1,32 @@
1
1
  require 'concurrent/version'
2
2
  require 'concurrent/configuration'
3
3
 
4
- require 'concurrent/atomic'
5
- require 'concurrent/count_down_latch'
6
- require 'concurrent/condition'
7
- require 'concurrent/copy_on_notify_observer_set'
8
- require 'concurrent/copy_on_write_observer_set'
9
- require 'concurrent/safe_task_executor'
10
- require 'concurrent/ivar'
4
+ require 'concurrent/atomics'
5
+ require 'concurrent/actors'
6
+ require 'concurrent/channels'
7
+ require 'concurrent/collections'
8
+ require 'concurrent/executors'
9
+ require 'concurrent/utilities'
11
10
 
12
- require 'concurrent/actor'
13
11
  require 'concurrent/agent'
14
12
  require 'concurrent/async'
15
13
  require 'concurrent/dataflow'
16
14
  require 'concurrent/delay'
17
15
  require 'concurrent/dereferenceable'
18
- require 'concurrent/event'
19
16
  require 'concurrent/exchanger'
20
17
  require 'concurrent/future'
18
+ require 'concurrent/ivar'
21
19
  require 'concurrent/mvar'
22
20
  require 'concurrent/obligation'
23
- require 'concurrent/postable'
24
- require 'concurrent/processor_count'
21
+ require 'concurrent/observable'
22
+ require 'concurrent/options_parser'
25
23
  require 'concurrent/promise'
26
24
  require 'concurrent/runnable'
27
25
  require 'concurrent/scheduled_task'
28
26
  require 'concurrent/stoppable'
29
27
  require 'concurrent/supervisor'
30
- require 'concurrent/thread_local_var'
31
28
  require 'concurrent/timer_task'
32
29
  require 'concurrent/tvar'
33
- require 'concurrent/utilities'
34
-
35
- require 'concurrent/channel/probe'
36
- require 'concurrent/channel/channel'
37
- require 'concurrent/channel/unbuffered_channel'
38
- require 'concurrent/channel/buffered_channel'
39
- require 'concurrent/channel/ring_buffer'
40
- require 'concurrent/channel/blocking_ring_buffer'
41
-
42
- require 'concurrent/actor_context'
43
- require 'concurrent/simple_actor_ref'
44
-
45
- require 'concurrent/cached_thread_pool'
46
- require 'concurrent/fixed_thread_pool'
47
- require 'concurrent/immediate_executor'
48
- require 'concurrent/per_thread_executor'
49
- require 'concurrent/thread_pool_executor'
50
30
 
51
31
  # Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell,
52
32
  # F#, C#, Java, and classic concurrency patterns.
@@ -1,9 +1,9 @@
1
1
  require 'thread'
2
2
  require 'observer'
3
3
 
4
- require 'concurrent/event'
4
+ require 'concurrent/actor/postable'
5
+ require 'concurrent/atomic/event'
5
6
  require 'concurrent/obligation'
6
- require 'concurrent/postable'
7
7
  require 'concurrent/runnable'
8
8
 
9
9
  module Concurrent
@@ -123,7 +123,7 @@ module Concurrent
123
123
  #
124
124
  # @see http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
125
125
  class Actor
126
- include Observable
126
+ include ::Observable
127
127
  include Postable
128
128
  include Runnable
129
129
 
@@ -0,0 +1,77 @@
1
+ require 'concurrent/actor/simple_actor_ref'
2
+
3
+ module Concurrent
4
+
5
+ # Actor-based concurrency is all the rage in some circles. Originally described in
6
+ # 1973, the actor model is a paradigm for creating asynchronous, concurrent objects
7
+ # that is becoming increasingly popular. Much has changed since actors were first
8
+ # written about four decades ago, which has led to a serious fragmentation within
9
+ # the actor community. There is *no* universally accepted, strict definition of
10
+ # "actor" and actor implementations differ widely between languages and libraries.
11
+ #
12
+ # A good definition of "actor" is:
13
+ #
14
+ # An independent, concurrent, single-purpose, computational entity that communicates exclusively via message passing.
15
+ #
16
+ # The actor framework in this library is heavily influenced by the Akka toolkit,
17
+ # with additional inspiration from Erlang and Scala. Unlike many of the abstractions
18
+ # in this library, `ActorContext` takes an *object-oriented* approach to asynchronous
19
+ # concurrency, rather than a *functional programming* approach.
20
+ #
21
+ # Creating an actor class is achieved by including the `ActorContext` module
22
+ # within a standard Ruby class. One `ActorContext` is mixed in, however, everything
23
+ # changes. Objects of the class can no longer be instanced with the `#new` method.
24
+ # Instead, the various factor methods, such as `#spawn`, must be used. These factory
25
+ # methods do not return direct references to the actor object. Instead they return
26
+ # objects of one of the `ActorRef` subclasses. The `ActorRef` objects encapsulate
27
+ # actor objects. This encapsulation is necessary to prevent synchronous method calls
28
+ # froom being made against the actors. It also allows the messaging and lifecycle
29
+ # behavior to be implemented within the `ActorRef` subclasses, allowing for better
30
+ # separation of responsibility.
31
+ #
32
+ # @see Concurrent::ActorRef
33
+ #
34
+ # @see http://akka.io/
35
+ # @see http://www.erlang.org/doc/getting_started/conc_prog.html
36
+ # @see http://www.scala-lang.org/api/current/index.html#scala.actors.Actor
37
+ #
38
+ # @see http://doc.akka.io/docs/akka/snapshot/general/supervision.html#What_Restarting_Means What Restarting Means
39
+ # @see http://doc.akka.io/docs/akka/snapshot/general/supervision.html#What_Lifecycle_Monitoring_Means What Lifecycle Monitoring Means
40
+ module ActorContext
41
+
42
+ # Callback method called by the `ActorRef` which encapsulates the actor instance.
43
+ def on_start
44
+ end
45
+
46
+ # Callback method called by the `ActorRef` which encapsulates the actor instance.
47
+ def on_shutdown
48
+ end
49
+
50
+ # Callback method called by the `ActorRef` which encapsulates the actor instance.
51
+ #
52
+ # @param [Time] time the date/time at which the error occurred
53
+ # @param [Array] message the message that caused the error
54
+ # @param [Exception] exception the exception object that was raised
55
+ def on_error(time, message, exception)
56
+ end
57
+
58
+ def self.included(base)
59
+
60
+ class << base
61
+
62
+ # Create a single, unregistered actor. The actor will run on its own, dedicated
63
+ # thread. The thread will be started the first time a message is post to the actor.
64
+ # Should the thread ever die it will be restarted the next time a message is post.
65
+ #
66
+ # @param [Hash] opts the options defining actor behavior
67
+ # @option opts [Array] :args (`nil`) arguments to be passed to the actor constructor
68
+ #
69
+ # @return [SimpleActorRef] the `ActorRef` encapsulating the actor
70
+ def spawn(opts = {})
71
+ args = opts.fetch(:args, [])
72
+ Concurrent::SimpleActorRef.new(self.new(*args), opts)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,67 @@
1
+ module Concurrent
2
+
3
+ # Base class for classes that encapsulate `ActorContext` objects.
4
+ #
5
+ # @see Concurrent::ActorContext
6
+ module ActorRef
7
+
8
+ # @!method post(*msg, &block)
9
+ #
10
+ # Send a message and return a future which will eventually be updated
11
+ # with the result of the operation. An optional callback block can be
12
+ # given which will be called once the operation is complete. Although
13
+ # it is possible to use a callback block and also interrogate the
14
+ # returned future it is a good practice to do one or the other.
15
+ #
16
+ # @param [Array] msg One or more elements of the message
17
+ #
18
+ # @yield a callback operation to be performed when the operation is complete.
19
+ # @yieldparam [Time] time the date/time at which the error occurred
20
+ # @yieldparam [Object] result the result of message processing or `nil` on error
21
+ # @yieldparam [Exception] exception the exception object that was raised or `nil` on success
22
+ #
23
+ # @return [IVar] a future that will eventually contain the result of message processing
24
+
25
+ # @!method post!(timeout, *msg)
26
+ # Send a message synchronously and block awaiting the response.
27
+ #
28
+ # @param [Integer] timeout the maximum number of seconds to block waiting
29
+ # for a response
30
+ # @param [Array] msg One or more elements of the message
31
+ #
32
+ # @return [Object] the result of successful message processing
33
+ #
34
+ # @raise [Concurrent::TimeoutError] if a timeout occurs
35
+ # @raise [Exception] an exception which occurred during message processing
36
+
37
+ # @!method running?()
38
+ # Is the actor running and processing messages?
39
+ # @return [Boolean] `true` if running else `false`
40
+
41
+ # @!method shutdown?()
42
+ # Is the actor shutdown and no longer processing messages?
43
+ # @return [Boolean] `true` if shutdown else `false`
44
+
45
+ # @!method shutdown()
46
+ # Shutdown the actor, gracefully exit all threads, and stop processing messages.
47
+ # @return [Boolean] `true` if shutdown is successful else `false`
48
+
49
+ # @!method join(limit = nil)
50
+ # Suspend the current thread until the actor has been shutdown
51
+ # @param [Integer] limit the maximum number of seconds to block waiting for the
52
+ # actor to shutdown. Block indefinitely when `nil` or not given
53
+ # @return [Boolean] `true` if the actor shutdown before the limit expired else `false`
54
+ # @see http://www.ruby-doc.org/core-2.1.1/Thread.html#method-i-join
55
+
56
+ # Send a message and return. It's a fire-and-forget interaction.
57
+ #
58
+ # @param [Object] message a single value representing the message to be sent
59
+ # to the actor or an array of multiple message components
60
+ #
61
+ # @return [ActorRef] self
62
+ def <<(message)
63
+ post(*message)
64
+ self
65
+ end
66
+ end
67
+ end
@@ -1,4 +1,4 @@
1
- require 'concurrent/event'
1
+ require 'concurrent/atomic/event'
2
2
 
3
3
  module Concurrent
4
4
 
@@ -0,0 +1,94 @@
1
+ require 'concurrent/actor/actor_ref'
2
+ require 'concurrent/atomic/event'
3
+ require 'concurrent/executor/single_thread_executor'
4
+ require 'concurrent/ivar'
5
+
6
+ module Concurrent
7
+
8
+ class SimpleActorRef
9
+ include ActorRef
10
+
11
+ def initialize(actor, opts = {})
12
+ @actor = actor
13
+ @mutex = Mutex.new
14
+ @one_by_one = OneByOne.new
15
+ @executor = OptionsParser::get_executor_from(opts)
16
+ @stop_event = Event.new
17
+ @reset_on_error = opts.fetch(:reset_on_error, true)
18
+ @exception_class = opts.fetch(:rescue_exception, false) ? Exception : StandardError
19
+ @args = opts.fetch(:args, []) if @reset_on_error
20
+
21
+ @actor.define_singleton_method(:shutdown, &method(:set_stop_event))
22
+ @actor.on_start
23
+ end
24
+
25
+ def running?
26
+ not @stop_event.set?
27
+ end
28
+
29
+ def shutdown?
30
+ @stop_event.set?
31
+ end
32
+
33
+ def post(*msg, &block)
34
+ raise ArgumentError.new('message cannot be empty') if msg.empty?
35
+ ivar = IVar.new
36
+ @one_by_one.post(@executor, Message.new(msg, ivar, block), &method(:process_message))
37
+ ivar
38
+ end
39
+
40
+ def post!(timeout, *msg)
41
+ raise Concurrent::TimeoutError unless timeout.nil? || timeout >= 0
42
+ ivar = self.post(*msg)
43
+ ivar.value(timeout)
44
+ if ivar.incomplete?
45
+ raise Concurrent::TimeoutError
46
+ elsif ivar.reason
47
+ raise ivar.reason
48
+ end
49
+ ivar.value
50
+ end
51
+
52
+ def shutdown
53
+ @mutex.synchronize do
54
+ return if shutdown?
55
+ @actor.on_shutdown
56
+ @stop_event.set
57
+ end
58
+ end
59
+
60
+ def join(limit = nil)
61
+ @stop_event.wait(limit)
62
+ end
63
+
64
+ private
65
+
66
+ Message = Struct.new(:payload, :ivar, :callback)
67
+
68
+ def set_stop_event
69
+ @stop_event.set
70
+ end
71
+
72
+ def process_message(message)
73
+ result = ex = nil
74
+
75
+ begin
76
+ result = @actor.receive(*message.payload)
77
+ rescue @exception_class => ex
78
+ @actor.on_error(Time.now, message.payload, ex)
79
+ if @reset_on_error
80
+ @mutex.synchronize{ @actor = @actor.class.new(*@args) }
81
+ end
82
+ ensure
83
+ now = Time.now
84
+ message.ivar.complete(ex.nil?, result, ex)
85
+
86
+ begin
87
+ message.callback.call(now, result, ex) if message.callback
88
+ rescue @exception_class => ex
89
+ # suppress
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,5 @@
1
+ require 'concurrent/actor/actor'
2
+ require 'concurrent/actor/actor_context'
3
+ require 'concurrent/actor/actor_ref'
4
+ require 'concurrent/actor/postable'
5
+ require 'concurrent/actor/simple_actor_ref'
@@ -1,15 +1,15 @@
1
1
  require 'thread'
2
- require 'observer'
3
2
 
4
- require 'concurrent/configuration'
5
3
  require 'concurrent/dereferenceable'
6
- require 'concurrent/utilities'
4
+ require 'concurrent/observable'
5
+ require 'concurrent/options_parser'
6
+ require 'concurrent/utility/timeout'
7
7
 
8
8
  module Concurrent
9
9
 
10
10
  # An agent is a single atomic value that represents an identity. The current value
11
- # of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
12
- # the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
11
+ # of the agent can be requested at any time (`#deref`). Each agent has a work queue and operates on
12
+ # the global thread pool. Consumers can `#post` code blocks to the agent. The code block (function)
13
13
  # will receive the current value of the agent as its sole parameter. The return value of the block
14
14
  # will become the new value of the agent. Agents support two error handling modes: fail and continue.
15
15
  # A good example of an agent is a shared incrementing counter, such as the score in a video game.
@@ -34,13 +34,13 @@ module Concurrent
34
34
  # @return [Fixnum] the maximum number of seconds before an update is cancelled
35
35
  class Agent
36
36
  include Dereferenceable
37
- include OptionsParser
37
+ include Concurrent::Observable
38
38
 
39
39
  # The default timeout value (in seconds); used when no timeout option
40
40
  # is given at initialization
41
41
  TIMEOUT = 5
42
42
 
43
- attr_reader :timeout
43
+ attr_reader :timeout, :task_executor, :operation_executor
44
44
 
45
45
  # Initialize a new Agent with the given initial value and provided options.
46
46
  #
@@ -49,32 +49,34 @@ module Concurrent
49
49
  #
50
50
  # @option opts [Fixnum] :timeout (TIMEOUT) maximum number of seconds before an update is cancelled
51
51
  #
52
- # @option opts [Boolean] :operation (false) when +true+ will execute the future on the global
53
- # operation pool (for long-running operations), when +false+ will execute the future on the
52
+ # @option opts [Boolean] :operation (false) when `true` will execute the future on the global
53
+ # operation pool (for long-running operations), when `false` will execute the future on the
54
54
  # global task pool (for short-running tasks)
55
55
  # @option opts [object] :executor when provided will run all operations on
56
56
  # this executor rather than the global thread pool (overrides :operation)
57
57
  #
58
- # @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
59
- # @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
60
- # @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
58
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
59
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
60
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
61
61
  # returning the value returned from the proc
62
62
  def initialize(initial, opts = {})
63
- @value = initial
64
- @rescuers = []
65
- @validator = Proc.new { |result| true }
66
- @timeout = opts.fetch(:timeout, TIMEOUT).freeze
67
- @observers = CopyOnWriteObserverSet.new
68
- @executor = get_executor_from(opts)
63
+ @value = initial
64
+ @rescuers = []
65
+ @validator = Proc.new { |result| true }
66
+ @timeout = opts.fetch(:timeout, TIMEOUT).freeze
67
+ self.observers = CopyOnWriteObserverSet.new
68
+ @one_by_one = OneByOne.new
69
+ @task_executor = OptionsParser.get_task_executor_from(opts)
70
+ @operation_executor = OptionsParser.get_operation_executor_from(opts)
69
71
  init_mutex
70
72
  set_deref_options(opts)
71
73
  end
72
74
 
73
75
  # Specifies a block operation to be performed when an update operation raises
74
76
  # an exception. Rescue blocks will be checked in order they were added. The first
75
- # block for which the raised exception "is-a" subclass of the given +clazz+ will
76
- # be called. If no +clazz+ is given the block will match any caught exception.
77
- # This behavior is intended to be identical to Ruby's +begin/rescue/end+ behavior.
77
+ # block for which the raised exception "is-a" subclass of the given `clazz` will
78
+ # be called. If no `clazz` is given the block will match any caught exception.
79
+ # This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
78
80
  # Any number of rescue handlers can be added. If no rescue handlers are added then
79
81
  # caught exceptions will be suppressed.
80
82
  #
@@ -111,53 +113,78 @@ module Concurrent
111
113
  # @yieldparam [Object] value the result of the last update operation
112
114
  # @yieldreturn [Boolean] true if the value is valid else false
113
115
  def validate(&block)
114
- @validator = block unless block.nil?
116
+ unless block.nil?
117
+ mutex.lock
118
+ @validator = block
119
+ mutex.unlock
120
+ end
115
121
  self
116
122
  end
117
123
  alias_method :validates, :validate
118
124
  alias_method :validate_with, :validate
119
125
  alias_method :validates_with, :validate
120
126
 
121
- # Update the current value with the result of the given block operation
127
+ # Update the current value with the result of the given block operation,
128
+ # block should not do blocking calls, use #post_off for blocking calls
122
129
  #
123
130
  # @yield the operation to be performed with the current value in order to calculate
124
131
  # the new value
125
132
  # @yieldparam [Object] value the current value
126
133
  # @yieldreturn [Object] the new value
134
+ # @return [true, nil] nil when no block is given
127
135
  def post(&block)
128
- @executor.post{ work(&block) } unless block.nil?
136
+ post_on(@task_executor, &block)
129
137
  end
130
138
 
131
- # Update the current value with the result of the given block operation
139
+ # Update the current value with the result of the given block operation,
140
+ # block can do blocking calls
141
+ #
142
+ # @yield the operation to be performed with the current value in order to calculate
143
+ # the new value
144
+ # @yieldparam [Object] value the current value
145
+ # @yieldreturn [Object] the new value
146
+ # @return [true, nil] nil when no block is given
147
+ def post_off(&block)
148
+ post_on(@operation_executor, &block)
149
+ end
150
+
151
+ # Update the current value with the result of the given block operation,
152
+ # block should not do blocking calls, use #post_off for blocking calls
132
153
  #
133
154
  # @yield the operation to be performed with the current value in order to calculate
134
155
  # the new value
135
156
  # @yieldparam [Object] value the current value
136
157
  # @yieldreturn [Object] the new value
137
158
  def <<(block)
138
- self.post(&block)
159
+ post(&block)
139
160
  self
140
161
  end
141
162
 
142
- def add_observer(observer, func=:update)
143
- @observers.add_observer(observer, func)
163
+ # Waits/blocks until all the updates sent before this call are done.
164
+ #
165
+ # @param [Numeric] timeout the maximum time in second to wait.
166
+ # @return [Boolean] false on timeout, true otherwise
167
+ def await(timeout = nil)
168
+ done = Event.new
169
+ post { |val| done.set; val }
170
+ done.wait timeout
144
171
  end
145
172
 
146
- alias_method :add_watch, :add_observer
173
+ private
147
174
 
148
- def delete_observer(observer)
149
- @observers.delete_observer(observer)
175
+ def post_on(executor, &block)
176
+ return nil if block.nil?
177
+ @one_by_one.post(executor) { work(&block) }
178
+ true
150
179
  end
151
180
 
152
- private
153
-
154
181
  # @!visibility private
155
182
  Rescuer = Struct.new(:clazz, :block) # :nodoc:
156
183
 
157
184
  # @!visibility private
158
185
  def try_rescue(ex) # :nodoc:
159
186
  rescuer = mutex.synchronize do
160
- @rescuers.find{|r| ex.is_a?(r.clazz) }
187
+ @rescuers.find { |r| ex.is_a?(r.clazz) }
161
188
  end
162
189
  rescuer.block.call(ex) if rescuer
163
190
  rescue Exception => ex
@@ -166,24 +193,31 @@ module Concurrent
166
193
 
167
194
  # @!visibility private
168
195
  def work(&handler) # :nodoc:
196
+ validator, value = mutex.synchronize { [@validator, @value] }
197
+
169
198
  begin
199
+ # FIXME creates second thread
200
+ result, valid = Concurrent::timeout(@timeout) do
201
+ result = handler.call(value)
202
+ [result, validator.call(result)]
203
+ end
204
+ rescue Exception => ex
205
+ exception = ex
206
+ end
170
207
 
171
- should_notify = false
208
+ mutex.lock
209
+ should_notify = if !exception && valid
210
+ @value = result
211
+ true
212
+ end
213
+ mutex.unlock
172
214
 
173
- mutex.synchronize do
174
- result = Concurrent::timeout(@timeout) do
175
- handler.call(@value)
176
- end
177
- if @validator.call(result)
178
- @value = result
179
- should_notify = true
180
- end
181
- end
215
+ if should_notify
182
216
  time = Time.now
183
- @observers.notify_observers{ [time, self.value] } if should_notify
184
- rescue Exception => ex
185
- try_rescue(ex)
217
+ observers.notify_observers { [time, self.value] }
186
218
  end
219
+
220
+ try_rescue(exception)
187
221
  end
188
222
  end
189
223
  end