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

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