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,229 @@
1
+ require 'concurrent/synchronization'
2
+
3
+ module Concurrent
4
+
5
+ # A `Maybe` encapsulates an optional value. A `Maybe` either contains a value
6
+ # of (represented as `Just`), or it is empty (represented as `Nothing`). Using
7
+ # `Maybe` is a good way to deal with errors or exceptional cases without
8
+ # resorting to drastic measures such as exceptions.
9
+ #
10
+ # `Maybe` is a replacement for the use of `nil` with better type checking.
11
+ #
12
+ # For compatibility with {Concurrent::Concern::Obligation} the predicate and
13
+ # accessor methods are aliased as `fulfilled?`, `rejected?`, `value`, and
14
+ # `reason`.
15
+ #
16
+ # ## Motivation
17
+ #
18
+ # A common pattern in languages with pattern matching, such as Erlang and
19
+ # Haskell, is to return *either* a value *or* an error from a function
20
+ # Consider this Erlang code:
21
+ #
22
+ # ```erlang
23
+ # case file:consult("data.dat") of
24
+ # {ok, Terms} -> do_something_useful(Terms);
25
+ # {error, Reason} -> lager:error(Reason)
26
+ # end.
27
+ # ```
28
+ #
29
+ # In this example the standard library function `file:consult` returns a
30
+ # [tuple](http://erlang.org/doc/reference_manual/data_types.html#id69044)
31
+ # with two elements: an [atom](http://erlang.org/doc/reference_manual/data_types.html#id64134)
32
+ # (similar to a ruby symbol) and a variable containing ancillary data. On
33
+ # success it returns the atom `ok` and the data from the file. On failure it
34
+ # returns `error` and a string with an explanation of the problem. With this
35
+ # pattern there is no ambiguity regarding success or failure. If the file is
36
+ # empty the return value cannot be misinterpreted as an error. And when an
37
+ # error occurs the return value provides useful information.
38
+ #
39
+ # In Ruby we tend to return `nil` when an error occurs or else we raise an
40
+ # exception. Both of these idioms are problematic. Returning `nil` is
41
+ # ambiguous because `nil` may also be a valid value. It also lacks
42
+ # information pertaining to the nature of the error. Raising an exception
43
+ # is both expensive and usurps the normal flow of control. All of these
44
+ # problems can be solved with the use of a `Maybe`.
45
+ #
46
+ # A `Maybe` is unambiguous with regard to whether or not it contains a value.
47
+ # When `Just` it contains a value, when `Nothing` it does not. When `Just`
48
+ # the value it contains may be `nil`, which is perfectly valid. When
49
+ # `Nothing` the reason for the lack of a value is contained as well. The
50
+ # previous Erlang example can be duplicated in Ruby in a principled way by
51
+ # having functions return `Maybe` objects:
52
+ #
53
+ # ```ruby
54
+ # result = MyFileUtils.consult("data.dat") # returns a Maybe
55
+ # if result.just?
56
+ # do_something_useful(result.value) # or result.just
57
+ # else
58
+ # logger.error(result.reason) # or result.nothing
59
+ # end
60
+ # ```
61
+ #
62
+ # @example Returning a Maybe from a Function
63
+ # module MyFileUtils
64
+ # def self.consult(path)
65
+ # file = File.open(path, 'r')
66
+ # Concurrent::Maybe.just(file.read)
67
+ # rescue => ex
68
+ # return Concurrent::Maybe.nothing(ex)
69
+ # ensure
70
+ # file.close if file
71
+ # end
72
+ # end
73
+ #
74
+ # maybe = MyFileUtils.consult('bogus.file')
75
+ # maybe.just? #=> false
76
+ # maybe.nothing? #=> true
77
+ # maybe.reason #=> #<Errno::ENOENT: No such file or directory @ rb_sysopen - bogus.file>
78
+ #
79
+ # maybe = MyFileUtils.consult('README.md')
80
+ # maybe.just? #=> true
81
+ # maybe.nothing? #=> false
82
+ # maybe.value #=> "# Concurrent Ruby\n[![Gem Version..."
83
+ #
84
+ # @example Using Maybe with a Block
85
+ # result = Concurrent::Maybe.from do
86
+ # Client.find(10) # Client is an ActiveRecord model
87
+ # end
88
+ #
89
+ # # -- if the record was found
90
+ # result.just? #=> true
91
+ # result.value #=> #<Client id: 10, first_name: "Ryan">
92
+ #
93
+ # # -- if the record was not found
94
+ # result.just? #=> false
95
+ # result.reason #=> ActiveRecord::RecordNotFound
96
+ #
97
+ # @example Using Maybe with the Null Object Pattern
98
+ # # In a Rails controller...
99
+ # result = ClientService.new(10).find # returns a Maybe
100
+ # render json: result.or(NullClient.new)
101
+ #
102
+ # @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe
103
+ # @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe
104
+ class Maybe < Synchronization::Object
105
+ include Comparable
106
+ safe_initialization!
107
+
108
+ # Indicates that the given attribute has not been set.
109
+ # When `Just` the {#nothing} getter will return `NONE`.
110
+ # When `Nothing` the {#just} getter will return `NONE`.
111
+ NONE = ::Object.new.freeze
112
+
113
+ # The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`.
114
+ attr_reader :just
115
+
116
+ # The reason for the `Maybe` when `Nothing`. Will be `NONE` when `Just`.
117
+ attr_reader :nothing
118
+
119
+ private_class_method :new
120
+
121
+ # Create a new `Maybe` using the given block.
122
+ #
123
+ # Runs the given block passing all function arguments to the block as block
124
+ # arguments. If the block runs to completion without raising an exception
125
+ # a new `Just` is created with the value set to the return value of the
126
+ # block. If the block raises an exception a new `Nothing` is created with
127
+ # the reason being set to the raised exception.
128
+ #
129
+ # @param [Array<Object>] args Zero or more arguments to pass to the block.
130
+ # @yield The block from which to create a new `Maybe`.
131
+ # @yieldparam [Array<Object>] args Zero or more block arguments passed as
132
+ # arguments to the function.
133
+ #
134
+ # @return [Maybe] The newly created object.
135
+ #
136
+ # @raise [ArgumentError] when no block given.
137
+ def self.from(*args)
138
+ raise ArgumentError.new('no block given') unless block_given?
139
+ begin
140
+ value = yield(*args)
141
+ return new(value, NONE)
142
+ rescue => ex
143
+ return new(NONE, ex)
144
+ end
145
+ end
146
+
147
+ # Create a new `Just` with the given value.
148
+ #
149
+ # @param [Object] value The value to set for the new `Maybe` object.
150
+ #
151
+ # @return [Maybe] The newly created object.
152
+ def self.just(value)
153
+ return new(value, NONE)
154
+ end
155
+
156
+ # Create a new `Nothing` with the given (optional) reason.
157
+ #
158
+ # @param [Exception] error The reason to set for the new `Maybe` object.
159
+ # When given a string a new `StandardError` will be created with the
160
+ # argument as the message. When no argument is given a new
161
+ # `StandardError` with an empty message will be created.
162
+ #
163
+ # @return [Maybe] The newly created object.
164
+ def self.nothing(error = '')
165
+ if error.is_a?(Exception)
166
+ nothing = error
167
+ else
168
+ nothing = StandardError.new(error.to_s)
169
+ end
170
+ return new(NONE, nothing)
171
+ end
172
+
173
+ # Is this `Maybe` a `Just` (successfully fulfilled with a value)?
174
+ #
175
+ # @return [Boolean] True if `Just` or false if `Nothing`.
176
+ def just?
177
+ ! nothing?
178
+ end
179
+ alias :fulfilled? :just?
180
+
181
+ # Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)?
182
+ #
183
+ # @return [Boolean] True if `Nothing` or false if `Just`.
184
+ def nothing?
185
+ @nothing != NONE
186
+ end
187
+ alias :rejected? :nothing?
188
+
189
+ alias :value :just
190
+
191
+ alias :reason :nothing
192
+
193
+ # Comparison operator.
194
+ #
195
+ # @return [Integer] 0 if self and other are both `Nothing`;
196
+ # -1 if self is `Nothing` and other is `Just`;
197
+ # 1 if self is `Just` and other is nothing;
198
+ # `self.just <=> other.just` if both self and other are `Just`.
199
+ def <=>(other)
200
+ if nothing?
201
+ other.nothing? ? 0 : -1
202
+ else
203
+ other.nothing? ? 1 : just <=> other.just
204
+ end
205
+ end
206
+
207
+ # Return either the value of self or the given default value.
208
+ #
209
+ # @return [Object] The value of self when `Just`; else the given default.
210
+ def or(other)
211
+ just? ? just : other
212
+ end
213
+
214
+ private
215
+
216
+ # Create a new `Maybe` with the given attributes.
217
+ #
218
+ # @param [Object] just The value when `Just` else `NONE`.
219
+ # @param [Exception, Object] nothing The exception when `Nothing` else `NONE`.
220
+ #
221
+ # @return [Maybe] The new `Maybe`.
222
+ #
223
+ # @!visibility private
224
+ def initialize(just, nothing)
225
+ @just = just
226
+ @nothing = nothing
227
+ end
228
+ end
229
+ end
@@ -0,0 +1,239 @@
1
+ require 'concurrent/synchronization/abstract_struct'
2
+ require 'concurrent/synchronization'
3
+
4
+ module Concurrent
5
+
6
+ # An thread-safe variation of Ruby's standard `Struct`. Values can be set at
7
+ # construction or safely changed at any time during the object's lifecycle.
8
+ #
9
+ # @see http://ruby-doc.org/core/Struct.html Ruby standard library `Struct`
10
+ module MutableStruct
11
+ include Synchronization::AbstractStruct
12
+
13
+ # @!macro struct_new
14
+ #
15
+ # Factory for creating new struct classes.
16
+ #
17
+ # ```
18
+ # new([class_name] [, member_name]+>) -> StructClass click to toggle source
19
+ # new([class_name] [, member_name]+>) {|StructClass| block } -> StructClass
20
+ # new(value, ...) -> obj
21
+ # StructClass[value, ...] -> obj
22
+ # ```
23
+ #
24
+ # The first two forms are used to create a new struct subclass `class_name`
25
+ # that can contain a value for each member_name . This subclass can be
26
+ # used to create instances of the structure like any other Class .
27
+ #
28
+ # If the `class_name` is omitted an anonymous struct class will be created.
29
+ # Otherwise, the name of this struct will appear as a constant in the struct class,
30
+ # so it must be unique for all structs under this base class and must start with a
31
+ # capital letter. Assigning a struct class to a constant also gives the class
32
+ # the name of the constant.
33
+ #
34
+ # If a block is given it will be evaluated in the context of `StructClass`, passing
35
+ # the created class as a parameter. This is the recommended way to customize a struct.
36
+ # Subclassing an anonymous struct creates an extra anonymous class that will never be used.
37
+ #
38
+ # The last two forms create a new instance of a struct subclass. The number of value
39
+ # parameters must be less than or equal to the number of attributes defined for the
40
+ # struct. Unset parameters default to nil. Passing more parameters than number of attributes
41
+ # will raise an `ArgumentError`.
42
+ #
43
+ # @see http://ruby-doc.org/core/Struct.html#method-c-new Ruby standard library `Struct#new`
44
+
45
+ # @!macro struct_values
46
+ #
47
+ # Returns the values for this struct as an Array.
48
+ #
49
+ # @return [Array] the values for this struct
50
+ #
51
+ def values
52
+ synchronize { ns_values }
53
+ end
54
+ alias_method :to_a, :values
55
+
56
+ # @!macro struct_values_at
57
+ #
58
+ # Returns the struct member values for each selector as an Array.
59
+ #
60
+ # A selector may be either an Integer offset or a Range of offsets (as in `Array#values_at`).
61
+ #
62
+ # @param [Fixnum, Range] indexes the index(es) from which to obatin the values (in order)
63
+ def values_at(*indexes)
64
+ synchronize { ns_values_at(indexes) }
65
+ end
66
+
67
+ # @!macro struct_inspect
68
+ #
69
+ # Describe the contents of this struct in a string.
70
+ #
71
+ # @return [String] the contents of this struct in a string
72
+ def inspect
73
+ synchronize { ns_inspect }
74
+ end
75
+ alias_method :to_s, :inspect
76
+
77
+ # @!macro struct_merge
78
+ #
79
+ # Returns a new struct containing the contents of `other` and the contents
80
+ # of `self`. If no block is specified, the value for entries with duplicate
81
+ # keys will be that of `other`. Otherwise the value for each duplicate key
82
+ # is determined by calling the block with the key, its value in `self` and
83
+ # its value in `other`.
84
+ #
85
+ # @param [Hash] other the hash from which to set the new values
86
+ # @yield an options block for resolving duplicate keys
87
+ # @yieldparam [String, Symbol] member the name of the member which is duplicated
88
+ # @yieldparam [Object] selfvalue the value of the member in `self`
89
+ # @yieldparam [Object] othervalue the value of the member in `other`
90
+ #
91
+ # @return [Synchronization::AbstractStruct] a new struct with the new values
92
+ #
93
+ # @raise [ArgumentError] of given a member that is not defined in the struct
94
+ def merge(other, &block)
95
+ synchronize { ns_merge(other, &block) }
96
+ end
97
+
98
+ # @!macro struct_to_h
99
+ #
100
+ # Returns a hash containing the names and values for the struct’s members.
101
+ #
102
+ # @return [Hash] the names and values for the struct’s members
103
+ def to_h
104
+ synchronize { ns_to_h }
105
+ end
106
+
107
+ # @!macro struct_get
108
+ #
109
+ # Attribute Reference
110
+ #
111
+ # @param [Symbol, String, Integer] member the string or symbol name of the member
112
+ # for which to obtain the value or the member's index
113
+ #
114
+ # @return [Object] the value of the given struct member or the member at the given index.
115
+ #
116
+ # @raise [NameError] if the member does not exist
117
+ # @raise [IndexError] if the index is out of range.
118
+ def [](member)
119
+ synchronize { ns_get(member) }
120
+ end
121
+
122
+ # @!macro struct_equality
123
+ #
124
+ # Equality
125
+ #
126
+ # @return [Boolean] true if other has the same struct subclass and has
127
+ # equal member values (according to `Object#==`)
128
+ def ==(other)
129
+ synchronize { ns_equality(other) }
130
+ end
131
+
132
+ # @!macro struct_each
133
+ #
134
+ # Yields the value of each struct member in order. If no block is given
135
+ # an enumerator is returned.
136
+ #
137
+ # @yield the operation to be performed on each struct member
138
+ # @yieldparam [Object] value each struct value (in order)
139
+ def each(&block)
140
+ return enum_for(:each) unless block_given?
141
+ synchronize { ns_each(&block) }
142
+ end
143
+
144
+ # @!macro struct_each_pair
145
+ #
146
+ # Yields the name and value of each struct member in order. If no block is
147
+ # given an enumerator is returned.
148
+ #
149
+ # @yield the operation to be performed on each struct member/value pair
150
+ # @yieldparam [Object] member each struct member (in order)
151
+ # @yieldparam [Object] value each struct value (in order)
152
+ def each_pair(&block)
153
+ return enum_for(:each_pair) unless block_given?
154
+ synchronize { ns_each_pair(&block) }
155
+ end
156
+
157
+ # @!macro struct_select
158
+ #
159
+ # Yields each member value from the struct to the block and returns an Array
160
+ # containing the member values from the struct for which the given block
161
+ # returns a true value (equivalent to `Enumerable#select`).
162
+ #
163
+ # @yield the operation to be performed on each struct member
164
+ # @yieldparam [Object] value each struct value (in order)
165
+ #
166
+ # @return [Array] an array containing each value for which the block returns true
167
+ def select(&block)
168
+ return enum_for(:select) unless block_given?
169
+ synchronize { ns_select(&block) }
170
+ end
171
+
172
+ # @!macro struct_set
173
+ #
174
+ # Attribute Assignment
175
+ #
176
+ # Sets the value of the given struct member or the member at the given index.
177
+ #
178
+ # @param [Symbol, String, Integer] member the string or symbol name of the member
179
+ # for which to obtain the value or the member's index
180
+ #
181
+ # @return [Object] the value of the given struct member or the member at the given index.
182
+ #
183
+ # @raise [NameError] if the name does not exist
184
+ # @raise [IndexError] if the index is out of range.
185
+ def []=(member, value)
186
+ if member.is_a? Integer
187
+ length = synchronize { @values.length }
188
+ if member >= length
189
+ raise IndexError.new("offset #{member} too large for struct(size:#{length})")
190
+ end
191
+ synchronize { @values[member] = value }
192
+ else
193
+ send("#{member}=", value)
194
+ end
195
+ rescue NoMethodError
196
+ raise NameError.new("no member '#{member}' in struct")
197
+ end
198
+
199
+ private
200
+
201
+ # @!visibility private
202
+ def initialize_copy(original)
203
+ synchronize do
204
+ super(original)
205
+ ns_initialize_copy
206
+ end
207
+ end
208
+
209
+ # @!macro struct_new
210
+ def self.new(*args, &block)
211
+ clazz_name = nil
212
+ if args.length == 0
213
+ raise ArgumentError.new('wrong number of arguments (0 for 1+)')
214
+ elsif args.length > 0 && args.first.is_a?(String)
215
+ clazz_name = args.shift
216
+ end
217
+ FACTORY.define_struct(clazz_name, args, &block)
218
+ end
219
+
220
+ FACTORY = Class.new(Synchronization::LockableObject) do
221
+ def define_struct(name, members, &block)
222
+ synchronize do
223
+ clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::LockableObject, name, members, &block)
224
+ members.each_with_index do |member, index|
225
+ clazz.send :remove_method, member
226
+ clazz.send(:define_method, member) do
227
+ synchronize { @values[index] }
228
+ end
229
+ clazz.send(:define_method, "#{member}=") do |value|
230
+ synchronize { @values[index] = value }
231
+ end
232
+ end
233
+ clazz
234
+ end
235
+ end
236
+ end.new
237
+ private_constant :FACTORY
238
+ end
239
+ end
@@ -0,0 +1,242 @@
1
+ require 'concurrent/concern/dereferenceable'
2
+ require 'concurrent/synchronization'
3
+
4
+ module Concurrent
5
+
6
+ # An `MVar` is a synchronized single element container. They are empty or
7
+ # contain one item. Taking a value from an empty `MVar` blocks, as does
8
+ # putting a value into a full one. You can either think of them as blocking
9
+ # queue of length one, or a special kind of mutable variable.
10
+ #
11
+ # On top of the fundamental `#put` and `#take` operations, we also provide a
12
+ # `#mutate` that is atomic with respect to operations on the same instance.
13
+ # These operations all support timeouts.
14
+ #
15
+ # We also support non-blocking operations `#try_put!` and `#try_take!`, a
16
+ # `#set!` that ignores existing values, a `#value` that returns the value
17
+ # without removing it or returns `MVar::EMPTY`, and a `#modify!` that yields
18
+ # `MVar::EMPTY` if the `MVar` is empty and can be used to set `MVar::EMPTY`.
19
+ # You shouldn't use these operations in the first instance.
20
+ #
21
+ # `MVar` is a [Dereferenceable](Dereferenceable).
22
+ #
23
+ # `MVar` is related to M-structures in Id, `MVar` in Haskell and `SyncVar` in Scala.
24
+ #
25
+ # Note that unlike the original Haskell paper, our `#take` is blocking. This is how
26
+ # Haskell and Scala do it today.
27
+ #
28
+ # @!macro copy_options
29
+ #
30
+ # ## See Also
31
+ #
32
+ # 1. P. Barth, R. Nikhil, and Arvind. [M-Structures: Extending a parallel, non- strict, functional language with state](http://dl.acm.org/citation.cfm?id=652538). In Proceedings of the 5th
33
+ # ACM Conference on Functional Programming Languages and Computer Architecture (FPCA), 1991.
34
+ #
35
+ # 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794).
36
+ # In Proceedings of the 23rd Symposium on Principles of Programming Languages
37
+ # (PoPL), 1996.
38
+ class MVar < Synchronization::Object
39
+ include Concern::Dereferenceable
40
+ safe_initialization!
41
+
42
+ # Unique value that represents that an `MVar` was empty
43
+ EMPTY = ::Object.new
44
+
45
+ # Unique value that represents that an `MVar` timed out before it was able
46
+ # to produce a value.
47
+ TIMEOUT = ::Object.new
48
+
49
+ # Create a new `MVar`, either empty or with an initial value.
50
+ #
51
+ # @param [Hash] opts the options controlling how the future will be processed
52
+ #
53
+ # @!macro deref_options
54
+ def initialize(value = EMPTY, opts = {})
55
+ @value = value
56
+ @mutex = Mutex.new
57
+ @empty_condition = ConditionVariable.new
58
+ @full_condition = ConditionVariable.new
59
+ set_deref_options(opts)
60
+ end
61
+
62
+ # Remove the value from an `MVar`, leaving it empty, and blocking if there
63
+ # isn't a value. A timeout can be set to limit the time spent blocked, in
64
+ # which case it returns `TIMEOUT` if the time is exceeded.
65
+ # @return [Object] the value that was taken, or `TIMEOUT`
66
+ def take(timeout = nil)
67
+ @mutex.synchronize do
68
+ wait_for_full(timeout)
69
+
70
+ # If we timed out we'll still be empty
71
+ if unlocked_full?
72
+ value = @value
73
+ @value = EMPTY
74
+ @empty_condition.signal
75
+ apply_deref_options(value)
76
+ else
77
+ TIMEOUT
78
+ end
79
+ end
80
+ end
81
+
82
+ # acquires lock on the from an `MVAR`, yields the value to provided block,
83
+ # and release lock. A timeout can be set to limit the time spent blocked,
84
+ # in which case it returns `TIMEOUT` if the time is exceeded.
85
+ # @return [Object] the value returned by the block, or `TIMEOUT`
86
+ def borrow(timeout = nil)
87
+ @mutex.synchronize do
88
+ wait_for_full(timeout)
89
+
90
+ # if we timeoud out we'll still be empty
91
+ if unlocked_full?
92
+ yield @value
93
+ else
94
+ TIMEOUT
95
+ end
96
+ end
97
+ end
98
+
99
+ # Put a value into an `MVar`, blocking if there is already a value until
100
+ # it is empty. A timeout can be set to limit the time spent blocked, in
101
+ # which case it returns `TIMEOUT` if the time is exceeded.
102
+ # @return [Object] the value that was put, or `TIMEOUT`
103
+ def put(value, timeout = nil)
104
+ @mutex.synchronize do
105
+ wait_for_empty(timeout)
106
+
107
+ # If we timed out we won't be empty
108
+ if unlocked_empty?
109
+ @value = value
110
+ @full_condition.signal
111
+ apply_deref_options(value)
112
+ else
113
+ TIMEOUT
114
+ end
115
+ end
116
+ end
117
+
118
+ # Atomically `take`, yield the value to a block for transformation, and then
119
+ # `put` the transformed value. Returns the transformed value. A timeout can
120
+ # be set to limit the time spent blocked, in which case it returns `TIMEOUT`
121
+ # if the time is exceeded.
122
+ # @return [Object] the transformed value, or `TIMEOUT`
123
+ def modify(timeout = nil)
124
+ raise ArgumentError.new('no block given') unless block_given?
125
+
126
+ @mutex.synchronize do
127
+ wait_for_full(timeout)
128
+
129
+ # If we timed out we'll still be empty
130
+ if unlocked_full?
131
+ value = @value
132
+ @value = yield value
133
+ @full_condition.signal
134
+ apply_deref_options(value)
135
+ else
136
+ TIMEOUT
137
+ end
138
+ end
139
+ end
140
+
141
+ # Non-blocking version of `take`, that returns `EMPTY` instead of blocking.
142
+ def try_take!
143
+ @mutex.synchronize do
144
+ if unlocked_full?
145
+ value = @value
146
+ @value = EMPTY
147
+ @empty_condition.signal
148
+ apply_deref_options(value)
149
+ else
150
+ EMPTY
151
+ end
152
+ end
153
+ end
154
+
155
+ # Non-blocking version of `put`, that returns whether or not it was successful.
156
+ def try_put!(value)
157
+ @mutex.synchronize do
158
+ if unlocked_empty?
159
+ @value = value
160
+ @full_condition.signal
161
+ true
162
+ else
163
+ false
164
+ end
165
+ end
166
+ end
167
+
168
+ # Non-blocking version of `put` that will overwrite an existing value.
169
+ def set!(value)
170
+ @mutex.synchronize do
171
+ old_value = @value
172
+ @value = value
173
+ @full_condition.signal
174
+ apply_deref_options(old_value)
175
+ end
176
+ end
177
+
178
+ # Non-blocking version of `modify` that will yield with `EMPTY` if there is no value yet.
179
+ def modify!
180
+ raise ArgumentError.new('no block given') unless block_given?
181
+
182
+ @mutex.synchronize do
183
+ value = @value
184
+ @value = yield value
185
+ if unlocked_empty?
186
+ @empty_condition.signal
187
+ else
188
+ @full_condition.signal
189
+ end
190
+ apply_deref_options(value)
191
+ end
192
+ end
193
+
194
+ # Returns if the `MVar` is currently empty.
195
+ def empty?
196
+ @mutex.synchronize { @value == EMPTY }
197
+ end
198
+
199
+ # Returns if the `MVar` currently contains a value.
200
+ def full?
201
+ !empty?
202
+ end
203
+
204
+ protected
205
+
206
+ def synchronize(&block)
207
+ @mutex.synchronize(&block)
208
+ end
209
+
210
+ private
211
+
212
+ def unlocked_empty?
213
+ @value == EMPTY
214
+ end
215
+
216
+ def unlocked_full?
217
+ ! unlocked_empty?
218
+ end
219
+
220
+ def wait_for_full(timeout)
221
+ wait_while(@full_condition, timeout) { unlocked_empty? }
222
+ end
223
+
224
+ def wait_for_empty(timeout)
225
+ wait_while(@empty_condition, timeout) { unlocked_full? }
226
+ end
227
+
228
+ def wait_while(condition, timeout)
229
+ if timeout.nil?
230
+ while yield
231
+ condition.wait(@mutex)
232
+ end
233
+ else
234
+ stop = Concurrent.monotonic_time + timeout
235
+ while yield && timeout > 0.0
236
+ condition.wait(@mutex, timeout)
237
+ timeout = stop - Concurrent.monotonic_time
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end