o-concurrent-ruby 1.1.11

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 +7 -0
  2. data/CHANGELOG.md +542 -0
  3. data/Gemfile +37 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +404 -0
  6. data/Rakefile +307 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +189 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby/concurrent/agent.rb +587 -0
  23. data/lib/concurrent-ruby/concurrent/array.rb +66 -0
  24. data/lib/concurrent-ruby/concurrent/async.rb +449 -0
  25. data/lib/concurrent-ruby/concurrent/atom.rb +222 -0
  26. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  27. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +126 -0
  28. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +143 -0
  29. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +164 -0
  30. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +205 -0
  31. data/lib/concurrent-ruby/concurrent/atomic/count_down_latch.rb +100 -0
  32. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +128 -0
  33. data/lib/concurrent-ruby/concurrent/atomic/event.rb +109 -0
  34. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +42 -0
  35. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +37 -0
  36. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  37. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  38. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  39. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +131 -0
  40. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +254 -0
  41. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +377 -0
  42. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +181 -0
  43. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +166 -0
  44. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +104 -0
  45. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  46. data/lib/concurrent-ruby/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  47. data/lib/concurrent-ruby/concurrent/atomics.rb +10 -0
  48. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  49. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  50. data/lib/concurrent-ruby/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  51. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +158 -0
  52. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  53. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +66 -0
  54. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  55. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  56. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  57. data/lib/concurrent-ruby/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  58. data/lib/concurrent-ruby/concurrent/collection/ruby_non_concurrent_priority_queue.rb +160 -0
  59. data/lib/concurrent-ruby/concurrent/concern/deprecation.rb +34 -0
  60. data/lib/concurrent-ruby/concurrent/concern/dereferenceable.rb +73 -0
  61. data/lib/concurrent-ruby/concurrent/concern/logging.rb +32 -0
  62. data/lib/concurrent-ruby/concurrent/concern/obligation.rb +220 -0
  63. data/lib/concurrent-ruby/concurrent/concern/observable.rb +110 -0
  64. data/lib/concurrent-ruby/concurrent/configuration.rb +188 -0
  65. data/lib/concurrent-ruby/concurrent/constants.rb +8 -0
  66. data/lib/concurrent-ruby/concurrent/dataflow.rb +81 -0
  67. data/lib/concurrent-ruby/concurrent/delay.rb +199 -0
  68. data/lib/concurrent-ruby/concurrent/errors.rb +69 -0
  69. data/lib/concurrent-ruby/concurrent/exchanger.rb +352 -0
  70. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +131 -0
  71. data/lib/concurrent-ruby/concurrent/executor/cached_thread_pool.rb +62 -0
  72. data/lib/concurrent-ruby/concurrent/executor/executor_service.rb +185 -0
  73. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +220 -0
  74. data/lib/concurrent-ruby/concurrent/executor/immediate_executor.rb +66 -0
  75. data/lib/concurrent-ruby/concurrent/executor/indirect_immediate_executor.rb +44 -0
  76. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +103 -0
  77. data/lib/concurrent-ruby/concurrent/executor/java_single_thread_executor.rb +30 -0
  78. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +140 -0
  79. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +82 -0
  80. data/lib/concurrent-ruby/concurrent/executor/ruby_single_thread_executor.rb +21 -0
  81. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +368 -0
  82. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +35 -0
  83. data/lib/concurrent-ruby/concurrent/executor/serial_executor_service.rb +34 -0
  84. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +107 -0
  85. data/lib/concurrent-ruby/concurrent/executor/serialized_execution_delegator.rb +28 -0
  86. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +100 -0
  87. data/lib/concurrent-ruby/concurrent/executor/single_thread_executor.rb +57 -0
  88. data/lib/concurrent-ruby/concurrent/executor/thread_pool_executor.rb +88 -0
  89. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +172 -0
  90. data/lib/concurrent-ruby/concurrent/executors.rb +20 -0
  91. data/lib/concurrent-ruby/concurrent/future.rb +141 -0
  92. data/lib/concurrent-ruby/concurrent/hash.rb +59 -0
  93. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +101 -0
  94. data/lib/concurrent-ruby/concurrent/ivar.rb +207 -0
  95. data/lib/concurrent-ruby/concurrent/map.rb +346 -0
  96. data/lib/concurrent-ruby/concurrent/maybe.rb +229 -0
  97. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +239 -0
  98. data/lib/concurrent-ruby/concurrent/mvar.rb +242 -0
  99. data/lib/concurrent-ruby/concurrent/options.rb +42 -0
  100. data/lib/concurrent-ruby/concurrent/promise.rb +580 -0
  101. data/lib/concurrent-ruby/concurrent/promises.rb +2167 -0
  102. data/lib/concurrent-ruby/concurrent/re_include.rb +58 -0
  103. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +331 -0
  104. data/lib/concurrent-ruby/concurrent/set.rb +74 -0
  105. data/lib/concurrent-ruby/concurrent/settable_struct.rb +139 -0
  106. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  107. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +24 -0
  108. data/lib/concurrent-ruby/concurrent/synchronization/abstract_struct.rb +171 -0
  109. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +60 -0
  110. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  111. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +45 -0
  112. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +36 -0
  113. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +72 -0
  114. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +44 -0
  115. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +88 -0
  116. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +183 -0
  117. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +71 -0
  118. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +49 -0
  119. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +47 -0
  120. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +36 -0
  121. data/lib/concurrent-ruby/concurrent/synchronization.rb +30 -0
  122. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  123. data/lib/concurrent-ruby/concurrent/thread_safe/util/adder.rb +74 -0
  124. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  125. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +88 -0
  126. data/lib/concurrent-ruby/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  127. data/lib/concurrent-ruby/concurrent/thread_safe/util/striped64.rb +246 -0
  128. data/lib/concurrent-ruby/concurrent/thread_safe/util/volatile.rb +75 -0
  129. data/lib/concurrent-ruby/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  130. data/lib/concurrent-ruby/concurrent/thread_safe/util.rb +16 -0
  131. data/lib/concurrent-ruby/concurrent/timer_task.rb +311 -0
  132. data/lib/concurrent-ruby/concurrent/tuple.rb +86 -0
  133. data/lib/concurrent-ruby/concurrent/tvar.rb +221 -0
  134. data/lib/concurrent-ruby/concurrent/utility/engine.rb +56 -0
  135. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +90 -0
  136. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +79 -0
  137. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +53 -0
  138. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +130 -0
  139. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  140. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  141. data/lib/concurrent-ruby/concurrent.rb +134 -0
  142. metadata +192 -0
@@ -0,0 +1,73 @@
1
+ module Concurrent
2
+ module Concern
3
+
4
+ # Object references in Ruby are mutable. This can lead to serious problems when
5
+ # the `#value` of a concurrent object is a mutable reference. Which is always the
6
+ # case unless the value is a `Fixnum`, `Symbol`, or similar "primitive" data type.
7
+ # Most classes in this library that expose a `#value` getter method do so using the
8
+ # `Dereferenceable` mixin module.
9
+ #
10
+ # @!macro copy_options
11
+ module Dereferenceable
12
+ # NOTE: This module is going away in 2.0. In the mean time we need it to
13
+ # play nicely with the synchronization layer. This means that the
14
+ # including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
16
+
17
+ # Return the value this object represents after applying the options specified
18
+ # by the `#set_deref_options` method.
19
+ #
20
+ # @return [Object] the current value of the object
21
+ def value
22
+ synchronize { apply_deref_options(@value) }
23
+ end
24
+ alias_method :deref, :value
25
+
26
+ protected
27
+
28
+ # Set the internal value of this object
29
+ #
30
+ # @param [Object] value the new value
31
+ def value=(value)
32
+ synchronize{ @value = value }
33
+ end
34
+
35
+ # @!macro dereferenceable_set_deref_options
36
+ # Set the options which define the operations #value performs before
37
+ # returning data to the caller (dereferencing).
38
+ #
39
+ # @note Most classes that include this module will call `#set_deref_options`
40
+ # from within the constructor, thus allowing these options to be set at
41
+ # object creation.
42
+ #
43
+ # @param [Hash] opts the options defining dereference behavior.
44
+ # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
45
+ # @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
46
+ # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
47
+ # the internal value and returning the value returned from the proc
48
+ def set_deref_options(opts = {})
49
+ synchronize{ ns_set_deref_options(opts) }
50
+ end
51
+
52
+ # @!macro dereferenceable_set_deref_options
53
+ # @!visibility private
54
+ def ns_set_deref_options(opts)
55
+ @dup_on_deref = opts[:dup_on_deref] || opts[:dup]
56
+ @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
57
+ @copy_on_deref = opts[:copy_on_deref] || opts[:copy]
58
+ @do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
59
+ nil
60
+ end
61
+
62
+ # @!visibility private
63
+ def apply_deref_options(value)
64
+ return nil if value.nil?
65
+ return value if @do_nothing_on_deref
66
+ value = @copy_on_deref.call(value) if @copy_on_deref
67
+ value = value.dup if @dup_on_deref
68
+ value = value.freeze if @freeze_on_deref
69
+ value
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ require 'logger'
2
+
3
+ module Concurrent
4
+ module Concern
5
+
6
+ # Include where logging is needed
7
+ #
8
+ # @!visibility private
9
+ module Logging
10
+ include Logger::Severity
11
+
12
+ # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
13
+ # @param [Integer] level one of Logger::Severity constants
14
+ # @param [String] progname e.g. a path of an Actor
15
+ # @param [String, nil] message when nil block is used to generate the message
16
+ # @yieldreturn [String] a message
17
+ def log(level, progname, message = nil, &block)
18
+ #NOTE: Cannot require 'concurrent/configuration' above due to circular references.
19
+ # Assume that the gem has been initialized if we've gotten this far.
20
+ logger = if defined?(@logger) && @logger
21
+ @logger
22
+ else
23
+ Concurrent.global_logger
24
+ end
25
+ logger.call level, progname, message, &block
26
+ rescue => error
27
+ $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
28
+ "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,220 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ require 'concurrent/atomic/event'
5
+ require 'concurrent/concern/dereferenceable'
6
+
7
+ module Concurrent
8
+ module Concern
9
+
10
+ module Obligation
11
+ include Concern::Dereferenceable
12
+ # NOTE: The Dereferenceable module is going away in 2.0. In the mean time
13
+ # we need it to place nicely with the synchronization layer. This means
14
+ # that the including class SHOULD be synchronized and it MUST implement a
15
+ # `#synchronize` method. Not doing so will lead to runtime errors.
16
+
17
+ # Has the obligation been fulfilled?
18
+ #
19
+ # @return [Boolean]
20
+ def fulfilled?
21
+ state == :fulfilled
22
+ end
23
+ alias_method :realized?, :fulfilled?
24
+
25
+ # Has the obligation been rejected?
26
+ #
27
+ # @return [Boolean]
28
+ def rejected?
29
+ state == :rejected
30
+ end
31
+
32
+ # Is obligation completion still pending?
33
+ #
34
+ # @return [Boolean]
35
+ def pending?
36
+ state == :pending
37
+ end
38
+
39
+ # Is the obligation still unscheduled?
40
+ #
41
+ # @return [Boolean]
42
+ def unscheduled?
43
+ state == :unscheduled
44
+ end
45
+
46
+ # Has the obligation completed processing?
47
+ #
48
+ # @return [Boolean]
49
+ def complete?
50
+ [:fulfilled, :rejected].include? state
51
+ end
52
+
53
+ # Is the obligation still awaiting completion of processing?
54
+ #
55
+ # @return [Boolean]
56
+ def incomplete?
57
+ ! complete?
58
+ end
59
+
60
+ # The current value of the obligation. Will be `nil` while the state is
61
+ # pending or the operation has been rejected.
62
+ #
63
+ # @param [Numeric] timeout the maximum time in seconds to wait.
64
+ # @return [Object] see Dereferenceable#deref
65
+ def value(timeout = nil)
66
+ wait timeout
67
+ deref
68
+ end
69
+
70
+ # Wait until obligation is complete or the timeout has been reached.
71
+ #
72
+ # @param [Numeric] timeout the maximum time in seconds to wait.
73
+ # @return [Obligation] self
74
+ def wait(timeout = nil)
75
+ event.wait(timeout) if timeout != 0 && incomplete?
76
+ self
77
+ end
78
+
79
+ # Wait until obligation is complete or the timeout is reached. Will re-raise
80
+ # any exceptions raised during processing (but will not raise an exception
81
+ # on timeout).
82
+ #
83
+ # @param [Numeric] timeout the maximum time in seconds to wait.
84
+ # @return [Obligation] self
85
+ # @raise [Exception] raises the reason when rejected
86
+ def wait!(timeout = nil)
87
+ wait(timeout).tap { raise self if rejected? }
88
+ end
89
+ alias_method :no_error!, :wait!
90
+
91
+ # The current value of the obligation. Will be `nil` while the state is
92
+ # pending or the operation has been rejected. Will re-raise any exceptions
93
+ # raised during processing (but will not raise an exception on timeout).
94
+ #
95
+ # @param [Numeric] timeout the maximum time in seconds to wait.
96
+ # @return [Object] see Dereferenceable#deref
97
+ # @raise [Exception] raises the reason when rejected
98
+ def value!(timeout = nil)
99
+ wait(timeout)
100
+ if rejected?
101
+ raise self
102
+ else
103
+ deref
104
+ end
105
+ end
106
+
107
+ # The current state of the obligation.
108
+ #
109
+ # @return [Symbol] the current state
110
+ def state
111
+ synchronize { @state }
112
+ end
113
+
114
+ # If an exception was raised during processing this will return the
115
+ # exception object. Will return `nil` when the state is pending or if
116
+ # the obligation has been successfully fulfilled.
117
+ #
118
+ # @return [Exception] the exception raised during processing or `nil`
119
+ def reason
120
+ synchronize { @reason }
121
+ end
122
+
123
+ # @example allows Obligation to be risen
124
+ # rejected_ivar = Ivar.new.fail
125
+ # raise rejected_ivar
126
+ def exception(*args)
127
+ raise 'obligation is not rejected' unless rejected?
128
+ reason.exception(*args)
129
+ end
130
+
131
+ protected
132
+
133
+ # @!visibility private
134
+ def get_arguments_from(opts = {})
135
+ [*opts.fetch(:args, [])]
136
+ end
137
+
138
+ # @!visibility private
139
+ def init_obligation
140
+ @event = Event.new
141
+ @value = @reason = nil
142
+ end
143
+
144
+ # @!visibility private
145
+ def event
146
+ @event
147
+ end
148
+
149
+ # @!visibility private
150
+ def set_state(success, value, reason)
151
+ if success
152
+ @value = value
153
+ @state = :fulfilled
154
+ else
155
+ @reason = reason
156
+ @state = :rejected
157
+ end
158
+ end
159
+
160
+ # @!visibility private
161
+ def state=(value)
162
+ synchronize { ns_set_state(value) }
163
+ end
164
+
165
+ # Atomic compare and set operation
166
+ # State is set to `next_state` only if `current state == expected_current`.
167
+ #
168
+ # @param [Symbol] next_state
169
+ # @param [Symbol] expected_current
170
+ #
171
+ # @return [Boolean] true is state is changed, false otherwise
172
+ #
173
+ # @!visibility private
174
+ def compare_and_set_state(next_state, *expected_current)
175
+ synchronize do
176
+ if expected_current.include? @state
177
+ @state = next_state
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+ end
184
+
185
+ # Executes the block within mutex if current state is included in expected_states
186
+ #
187
+ # @return block value if executed, false otherwise
188
+ #
189
+ # @!visibility private
190
+ def if_state(*expected_states)
191
+ synchronize do
192
+ raise ArgumentError.new('no block given') unless block_given?
193
+
194
+ if expected_states.include? @state
195
+ yield
196
+ else
197
+ false
198
+ end
199
+ end
200
+ end
201
+
202
+ protected
203
+
204
+ # Am I in the current state?
205
+ #
206
+ # @param [Symbol] expected The state to check against
207
+ # @return [Boolean] true if in the expected state else false
208
+ #
209
+ # @!visibility private
210
+ def ns_check_state?(expected)
211
+ @state == expected
212
+ end
213
+
214
+ # @!visibility private
215
+ def ns_set_state(value)
216
+ @state = value
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,110 @@
1
+ require 'concurrent/collection/copy_on_notify_observer_set'
2
+ require 'concurrent/collection/copy_on_write_observer_set'
3
+
4
+ module Concurrent
5
+ module Concern
6
+
7
+ # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one
8
+ # of the most useful design patterns.
9
+ #
10
+ # The workflow is very simple:
11
+ # - an `observer` can register itself to a `subject` via a callback
12
+ # - many `observers` can be registered to the same `subject`
13
+ # - the `subject` notifies all registered observers when its status changes
14
+ # - an `observer` can deregister itself when is no more interested to receive
15
+ # event notifications
16
+ #
17
+ # In a single threaded environment the whole pattern is very easy: the
18
+ # `subject` can use a simple data structure to manage all its subscribed
19
+ # `observer`s and every `observer` can react directly to every event without
20
+ # caring about synchronization.
21
+ #
22
+ # In a multi threaded environment things are more complex. The `subject` must
23
+ # synchronize the access to its data structure and to do so currently we're
24
+ # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet}
25
+ # and {Concurrent::Concern::CopyOnNotifyObserverSet}.
26
+ #
27
+ # When implementing and `observer` there's a very important rule to remember:
28
+ # **there are no guarantees about the thread that will execute the callback**
29
+ #
30
+ # Let's take this example
31
+ # ```
32
+ # class Observer
33
+ # def initialize
34
+ # @count = 0
35
+ # end
36
+ #
37
+ # def update
38
+ # @count += 1
39
+ # end
40
+ # end
41
+ #
42
+ # obs = Observer.new
43
+ # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) }
44
+ # # execute [obj1, obj2, obj3, obj4]
45
+ # ```
46
+ #
47
+ # `obs` is wrong because the variable `@count` can be accessed by different
48
+ # threads at the same time, so it should be synchronized (using either a Mutex
49
+ # or an AtomicFixum)
50
+ module Observable
51
+
52
+ # @!macro observable_add_observer
53
+ #
54
+ # Adds an observer to this set. If a block is passed, the observer will be
55
+ # created by this method and no other params should be passed.
56
+ #
57
+ # @param [Object] observer the observer to add
58
+ # @param [Symbol] func the function to call on the observer during notification.
59
+ # Default is :update
60
+ # @return [Object] the added observer
61
+ def add_observer(observer = nil, func = :update, &block)
62
+ observers.add_observer(observer, func, &block)
63
+ end
64
+
65
+ # As `#add_observer` but can be used for chaining.
66
+ #
67
+ # @param [Object] observer the observer to add
68
+ # @param [Symbol] func the function to call on the observer during notification.
69
+ # @return [Observable] self
70
+ def with_observer(observer = nil, func = :update, &block)
71
+ add_observer(observer, func, &block)
72
+ self
73
+ end
74
+
75
+ # @!macro observable_delete_observer
76
+ #
77
+ # Remove `observer` as an observer on this object so that it will no
78
+ # longer receive notifications.
79
+ #
80
+ # @param [Object] observer the observer to remove
81
+ # @return [Object] the deleted observer
82
+ def delete_observer(observer)
83
+ observers.delete_observer(observer)
84
+ end
85
+
86
+ # @!macro observable_delete_observers
87
+ #
88
+ # Remove all observers associated with this object.
89
+ #
90
+ # @return [Observable] self
91
+ def delete_observers
92
+ observers.delete_observers
93
+ self
94
+ end
95
+
96
+ # @!macro observable_count_observers
97
+ #
98
+ # Return the number of observers associated with this object.
99
+ #
100
+ # @return [Integer] the observers count
101
+ def count_observers
102
+ observers.count_observers
103
+ end
104
+
105
+ protected
106
+
107
+ attr_accessor :observers
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,188 @@
1
+ require 'thread'
2
+ require 'concurrent/delay'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/atomic/atomic_reference'
5
+ require 'concurrent/concern/logging'
6
+ require 'concurrent/concern/deprecation'
7
+ require 'concurrent/executor/immediate_executor'
8
+ require 'concurrent/executor/cached_thread_pool'
9
+ require 'concurrent/utility/processor_counter'
10
+
11
+ module Concurrent
12
+ extend Concern::Logging
13
+ extend Concern::Deprecation
14
+
15
+ autoload :Options, 'concurrent/options'
16
+ autoload :TimerSet, 'concurrent/executor/timer_set'
17
+ autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor'
18
+
19
+ # @return [Logger] Logger with provided level and output.
20
+ def self.create_simple_logger(level = Logger::FATAL, output = $stderr)
21
+ # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking
22
+ lambda do |severity, progname, message = nil, &block|
23
+ return false if severity < level
24
+
25
+ message = block ? block.call : message
26
+ formatted_message = case message
27
+ when String
28
+ message
29
+ when Exception
30
+ format "%s (%s)\n%s",
31
+ message.message, message.class, (message.backtrace || []).join("\n")
32
+ else
33
+ message.inspect
34
+ end
35
+
36
+ output.print format "[%s] %5s -- %s: %s\n",
37
+ Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'),
38
+ Logger::SEV_LABEL[severity],
39
+ progname,
40
+ formatted_message
41
+ true
42
+ end
43
+ end
44
+
45
+ # Use logger created by #create_simple_logger to log concurrent-ruby messages.
46
+ def self.use_simple_logger(level = Logger::FATAL, output = $stderr)
47
+ Concurrent.global_logger = create_simple_logger level, output
48
+ end
49
+
50
+ # @return [Logger] Logger with provided level and output.
51
+ # @deprecated
52
+ def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
53
+ logger = Logger.new(output)
54
+ logger.level = level
55
+ logger.formatter = lambda do |severity, datetime, progname, msg|
56
+ formatted_message = case msg
57
+ when String
58
+ msg
59
+ when Exception
60
+ format "%s (%s)\n%s",
61
+ msg.message, msg.class, (msg.backtrace || []).join("\n")
62
+ else
63
+ msg.inspect
64
+ end
65
+ format "[%s] %5s -- %s: %s\n",
66
+ datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
67
+ severity,
68
+ progname,
69
+ formatted_message
70
+ end
71
+
72
+ lambda do |loglevel, progname, message = nil, &block|
73
+ logger.add loglevel, message, progname, &block
74
+ end
75
+ end
76
+
77
+ # Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
78
+ # @deprecated
79
+ def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
80
+ Concurrent.global_logger = create_stdlib_logger level, output
81
+ end
82
+
83
+ # TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods
84
+
85
+ # Suppresses all output when used for logging.
86
+ NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
87
+
88
+ # @!visibility private
89
+ GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN))
90
+ private_constant :GLOBAL_LOGGER
91
+
92
+ def self.global_logger
93
+ GLOBAL_LOGGER.value
94
+ end
95
+
96
+ def self.global_logger=(value)
97
+ GLOBAL_LOGGER.value = value
98
+ end
99
+
100
+ # @!visibility private
101
+ GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor }
102
+ private_constant :GLOBAL_FAST_EXECUTOR
103
+
104
+ # @!visibility private
105
+ GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor }
106
+ private_constant :GLOBAL_IO_EXECUTOR
107
+
108
+ # @!visibility private
109
+ GLOBAL_TIMER_SET = Delay.new { TimerSet.new }
110
+ private_constant :GLOBAL_TIMER_SET
111
+
112
+ # @!visibility private
113
+ GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new
114
+ private_constant :GLOBAL_IMMEDIATE_EXECUTOR
115
+
116
+ # Disables AtExit handlers including pool auto-termination handlers.
117
+ # When disabled it will be the application programmer's responsibility
118
+ # to ensure that the handlers are shutdown properly prior to application
119
+ # exit by calling `AtExit.run` method.
120
+ #
121
+ # @note this option should be needed only because of `at_exit` ordering
122
+ # issues which may arise when running some of the testing frameworks.
123
+ # E.g. Minitest's test-suite runs itself in `at_exit` callback which
124
+ # executes after the pools are already terminated. Then auto termination
125
+ # needs to be disabled and called manually after test-suite ends.
126
+ # @note This method should *never* be called
127
+ # from within a gem. It should *only* be used from within the main
128
+ # application and even then it should be used only when necessary.
129
+ # @deprecated Has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841.
130
+ #
131
+ def self.disable_at_exit_handlers!
132
+ deprecated "Method #disable_at_exit_handlers! has no effect since it is no longer needed, see https://github.com/ruby-concurrency/concurrent-ruby/pull/841."
133
+ end
134
+
135
+ # Global thread pool optimized for short, fast *operations*.
136
+ #
137
+ # @return [ThreadPoolExecutor] the thread pool
138
+ def self.global_fast_executor
139
+ GLOBAL_FAST_EXECUTOR.value
140
+ end
141
+
142
+ # Global thread pool optimized for long, blocking (IO) *tasks*.
143
+ #
144
+ # @return [ThreadPoolExecutor] the thread pool
145
+ def self.global_io_executor
146
+ GLOBAL_IO_EXECUTOR.value
147
+ end
148
+
149
+ def self.global_immediate_executor
150
+ GLOBAL_IMMEDIATE_EXECUTOR
151
+ end
152
+
153
+ # Global thread pool user for global *timers*.
154
+ #
155
+ # @return [Concurrent::TimerSet] the thread pool
156
+ def self.global_timer_set
157
+ GLOBAL_TIMER_SET.value
158
+ end
159
+
160
+ # General access point to global executors.
161
+ # @param [Symbol, Executor] executor_identifier symbols:
162
+ # - :fast - {Concurrent.global_fast_executor}
163
+ # - :io - {Concurrent.global_io_executor}
164
+ # - :immediate - {Concurrent.global_immediate_executor}
165
+ # @return [Executor]
166
+ def self.executor(executor_identifier)
167
+ Options.executor(executor_identifier)
168
+ end
169
+
170
+ def self.new_fast_executor(opts = {})
171
+ FixedThreadPool.new(
172
+ [2, Concurrent.processor_count].max,
173
+ auto_terminate: opts.fetch(:auto_terminate, true),
174
+ idletime: 60, # 1 minute
175
+ max_queue: 0, # unlimited
176
+ fallback_policy: :abort, # shouldn't matter -- 0 max queue
177
+ name: "fast"
178
+ )
179
+ end
180
+
181
+ def self.new_io_executor(opts = {})
182
+ CachedThreadPool.new(
183
+ auto_terminate: opts.fetch(:auto_terminate, true),
184
+ fallback_policy: :abort, # shouldn't matter -- 0 max queue
185
+ name: "io"
186
+ )
187
+ end
188
+ end
@@ -0,0 +1,8 @@
1
+ module Concurrent
2
+
3
+ # Various classes within allows for +nil+ values to be stored,
4
+ # so a special +NULL+ token is required to indicate the "nil-ness".
5
+ # @!visibility private
6
+ NULL = ::Object.new
7
+
8
+ end