ratomic 0.2.1 → 0.3.1

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.
data/lib/ratomic/map.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ratomic
4
- # A Ractor-shareable concurrent map.
4
+ # A Ractor-shareable concurrent Hash backed by Rust's DashMap.
5
5
  #
6
- # Map is a public alias for the native ConcurrentHashMap class. It is suitable
7
- # for runtime state with shareable keys and values that are safe to access
8
- # from multiple Ractors, such as counters or immutable offsets.
6
+ # Map gives Ruby code a small, Ruby-shaped API over DashMap's concurrent
7
+ # storage. It is suitable for runtime state with shareable keys and values
8
+ # that are safe to access from multiple Ractors, such as integer counters or
9
+ # immutable offsets.
9
10
  #
10
11
  # This is not a full Hash replacement. Iteration and arbitrary mutable object
11
12
  # borrowing are intentionally absent.
@@ -14,15 +15,190 @@ module Ratomic
14
15
  # OFFSETS = Ratomic::Map.new
15
16
  # OFFSETS[:source_a] = 42
16
17
  # OFFSETS[:source_a] # => 42
17
- Map = ConcurrentHashMap
18
-
19
- # Ruby convenience methods for {Map}.
20
- module MapMethods
18
+ #
19
+ # @!method get(key)
20
+ # Read a value by +key+.
21
+ #
22
+ # Missing keys return nil, so use #key? or #fetch when stored nil values
23
+ # need to be distinguished from missing entries.
24
+ #
25
+ # @param key [Object] lookup key
26
+ # @return [Object, nil] the stored value, or nil when the key is missing
27
+ #
28
+ # @!method set(key, value)
29
+ # Set a value for +key+.
30
+ #
31
+ # This is the method behind `#[]=` and follows Ruby setter semantics by
32
+ # returning the assigned value.
33
+ #
34
+ # @param key [Object] key to write
35
+ # @param value [Object] value to store
36
+ # @return [Object] the assigned value
37
+ #
38
+ # @!method []=(key, value)
39
+ # Set a value for +key+.
40
+ #
41
+ # In assignment form, Ruby returns the value assigned to the expression.
42
+ #
43
+ # @param key [Object] key to write
44
+ # @param value [Object] value to store
45
+ # @return [Object] the assigned value
46
+ #
47
+ # @!method key?(key)
48
+ # Check whether +key+ currently exists in the map.
49
+ #
50
+ # Unlike #get and #[], this distinguishes missing keys from stored nil
51
+ # values.
52
+ #
53
+ # @param key [Object] lookup key
54
+ # @return [Boolean] true when the key currently exists
55
+ #
56
+ # @!method delete(key)
57
+ # Remove +key+ and return its previous value.
58
+ #
59
+ # Missing keys return nil. Stored nil values also return nil; use #key?
60
+ # before deleting if that distinction matters.
61
+ #
62
+ # @param key [Object] key to remove
63
+ # @return [Object, nil] the previous value, or nil when the key was missing
64
+ #
65
+ # @!method clear
66
+ # Remove all entries from the map.
67
+ #
68
+ # @return [Ratomic::Map] self
69
+ #
70
+ # @!method size
71
+ # Return the current number of entries.
72
+ #
73
+ # Since this is a concurrent map, the value is a moment-in-time observation.
74
+ #
75
+ # @return [Integer] the current number of entries
76
+ #
77
+ # @!method fetch_and_modify(key)
78
+ # Replace the existing value for +key+ with the block return value.
79
+ #
80
+ # TODO: Revisit this name once the Map API settles. Prefer public method
81
+ # names that stay as close as possible to Ruby Hash semantics.
82
+ #
83
+ # The key must already exist. The operation is atomic for the key. The block
84
+ # runs while the map entry is locked, so avoid using this method for
85
+ # Ractor-hot loops or calling back into the same map from inside the block.
86
+ # If the block raises, the previous value is preserved.
87
+ #
88
+ # @param key [Object] key to modify in place
89
+ # @yieldparam value [Object] the current stored value
90
+ # @return [void] nothing useful is returned
91
+ # @raise [LocalJumpError] if no block is given
92
+ # @raise [Exception] any exception raised by the block
93
+ #
94
+ # @!method compute(key)
95
+ # Atomically compute and store a value for +key+.
96
+ #
97
+ # If +key+ exists, yields the current value. If +key+ is missing, yields
98
+ # nil. The block return value is stored and returned.
99
+ #
100
+ # The operation is atomic for the key. The block runs while the map entry is
101
+ # locked, so avoid using this method for Ractor-hot loops or calling back
102
+ # into the same map from inside the block. Prefer native update helpers such
103
+ # as #increment when they fit the workflow.
104
+ #
105
+ # If the block raises, the previous value is preserved. If the key was
106
+ # missing, no entry is inserted.
107
+ #
108
+ # @param key [Object] key to compute
109
+ # @yieldparam value [Object, nil] the current stored value, or nil when missing
110
+ # @return [Object] the newly stored value
111
+ # @raise [LocalJumpError] if no block is given
112
+ # @raise [Exception] any exception raised by the block
113
+ #
114
+ # @!method fetch_or_store(key)
115
+ # Return the existing value for +key+, or atomically store the block result.
116
+ #
117
+ # If +key+ exists, returns the current value and does not yield. If +key+
118
+ # is missing, yields once, stores the block return value, and returns it.
119
+ #
120
+ # The operation is atomic for the key. Under contention, only one stored
121
+ # value wins for a missing key. The block runs while the map entry is locked,
122
+ # so avoid using this method for Ractor-hot loops or calling back into the
123
+ # same map from inside the block.
124
+ #
125
+ # If the block raises, no entry is inserted.
126
+ #
127
+ # @param key [Object] key to read or initialize
128
+ # @return [Object] the existing or newly stored value
129
+ # @raise [LocalJumpError] if no block is given
130
+ # @raise [Exception] any exception raised by the block
131
+ #
132
+ # @!method upsert(key, initial)
133
+ # Atomically insert +initial+ for a missing key, or update an existing value.
134
+ #
135
+ # If +key+ is missing, stores and returns +initial+ without yielding. If
136
+ # +key+ exists, yields the current value, stores the block return value, and
137
+ # returns it.
138
+ #
139
+ # The operation is atomic for the key. The block runs while the map entry is
140
+ # locked, so avoid using this method for Ractor-hot loops or calling back
141
+ # into the same map from inside the block. Prefer native update helpers such
142
+ # as #increment when they fit the workflow.
143
+ #
144
+ # If the block raises, the previous value is preserved.
145
+ #
146
+ # @param key [Object] key to update
147
+ # @param initial [Object] value to use when the key is missing
148
+ # @yieldparam value [Object, nil] the current stored value, or nil when missing
149
+ # @return [Object] the inserted or newly stored value
150
+ # @raise [LocalJumpError] if no block is given
151
+ # @raise [Exception] any exception raised by the block
152
+ #
153
+ # @!method increment(key, by = 1)
154
+ # Atomically increment the numeric value for +key+.
155
+ #
156
+ # Missing keys start at zero. Existing non-numeric values raise TypeError
157
+ # and are left unchanged. This uses a native update path and is the preferred
158
+ # counter primitive for Ractor-heavy workloads.
159
+ #
160
+ # @param key [Object] counter key to increment
161
+ # @param by [Numeric] amount to add
162
+ # @return [Numeric] the newly stored value
163
+ # @raise [TypeError] if +by+ or the existing value is not numeric
164
+ #
165
+ # @!method decrement(key, by = 1)
166
+ # Atomically decrement the numeric value for +key+.
167
+ #
168
+ # Missing keys start at zero.
169
+ #
170
+ # @param key [Object] counter key to decrement
171
+ # @param by [Numeric] amount to subtract
172
+ # @return [Numeric] the newly stored value
173
+ # @raise [TypeError] if +by+ or the existing value is not numeric
174
+ #
175
+ # @!method append(key, value)
176
+ # Atomically append +value+ to an Array bucket for +key+.
177
+ #
178
+ # The stored Array is replaced rather than mutated in place.
179
+ #
180
+ # @param key [Object] bucket key to append into
181
+ # @param value [Object] value to append
182
+ # @return [Array] the newly stored frozen Array
183
+ # @raise [TypeError] if the existing value is not an Array
184
+ #
185
+ # @!method add_to_set(key, value)
186
+ # Atomically add +value+ to a Set bucket for +key+.
187
+ #
188
+ # The stored Set is replaced rather than mutated in place.
189
+ #
190
+ # @param key [Object] bucket key to update
191
+ # @param value [Object] value to add to the set
192
+ # @return [Set] the newly stored frozen Set
193
+ # @raise [TypeError] if the existing value is not a Set
194
+ class Map
21
195
  # Set a value for +key+.
22
196
  #
23
- # @param key [Object]
24
- # @param value [Object]
25
- # @return [void]
197
+ # In assignment form, Ruby returns the assigned value.
198
+ #
199
+ # @param key [Object] key to write
200
+ # @param value [Object] value to store
201
+ # @return [Object] the assigned value
26
202
  def []=(key, value)
27
203
  set(key, value)
28
204
  end
@@ -31,26 +207,128 @@ module Ratomic
31
207
  #
32
208
  # Missing keys currently return nil, so storing nil is ambiguous.
33
209
  #
34
- # @param key [Object]
35
- # @return [Object, nil]
210
+ # @param key [Object] lookup key
211
+ # @return [Object, nil] the stored value, or nil when the key is missing
36
212
  def [](key)
37
213
  get(key)
38
214
  end
39
215
 
216
+ # Fetch a value by +key+.
217
+ #
218
+ # Unlike #[], this distinguishes missing keys from explicit nil values.
219
+ #
220
+ # @param key [Object] lookup key
221
+ # @param default [Object] fallback value to return when the key is missing
222
+ # @yieldparam key [Object] the missing key
223
+ # @return [Object] the found value, default, or block result
224
+ # @raise [KeyError] if +key+ is missing and no default or block is provided
225
+ def fetch(key, default = UNDEFINED)
226
+ return get(key) if key?(key)
227
+ return yield key if block_given?
228
+ return default unless default.equal?(UNDEFINED)
229
+
230
+ raise KeyError, "key not found: #{key.inspect}"
231
+ end
232
+
233
+ # Atomically increment the numeric value for +key+.
234
+ #
235
+ # Missing keys start at zero. Existing non-numeric values raise TypeError
236
+ # and are left unchanged. This uses a native update path and is the preferred
237
+ # counter primitive for Ractor-heavy workloads.
238
+ #
239
+ # @param key [Object] counter key to increment
240
+ # @param by [Numeric] amount to add
241
+ # @return [Numeric] the newly stored value
242
+ # @raise [TypeError] if +by+ or the existing value is not numeric
243
+ def increment(key, by = 1)
244
+ raise TypeError, "amount must be numeric: #{by.inspect}" unless by.is_a?(Numeric)
245
+
246
+ __increment_numeric(key, by)
247
+ end
248
+
249
+ # Atomically decrement the numeric value for +key+.
250
+ #
251
+ # Missing keys start at zero.
252
+ #
253
+ # @param key [Object] counter key to decrement
254
+ # @param by [Numeric] amount to subtract
255
+ # @return [Numeric] the newly stored value
256
+ # @raise [TypeError] if +by+ or the existing value is not numeric
257
+ def decrement(key, by = 1)
258
+ raise TypeError, "amount must be numeric: #{by.inspect}" unless by.is_a?(Numeric)
259
+
260
+ increment(key, -by)
261
+ end
262
+
263
+ # Atomically append +value+ to an Array bucket for +key+.
264
+ #
265
+ # The stored Array is replaced rather than mutated in place.
266
+ #
267
+ # @param key [Object] bucket key to append into
268
+ # @param value [Object] value to append
269
+ # @return [Array] the newly stored frozen Array
270
+ # @raise [TypeError] if the existing value is not an Array
271
+ def append(key, value)
272
+ missing = !key?(key)
273
+ compute(key) do |old_value|
274
+ if old_value.nil? && missing
275
+ [value].freeze
276
+ else
277
+ unless old_value.is_a?(Array)
278
+ raise TypeError,
279
+ "existing value for #{key.inspect} must be an Array: #{old_value.inspect}"
280
+ end
281
+
282
+ (old_value + [value]).freeze
283
+ end
284
+ end
285
+ end
286
+
287
+ # Atomically add +value+ to a Set bucket for +key+.
288
+ #
289
+ # The stored Set is replaced rather than mutated in place.
290
+ #
291
+ # @param key [Object] bucket key to update
292
+ # @param value [Object] value to add to the set
293
+ # @return [Set] the newly stored frozen Set
294
+ # @raise [TypeError] if the existing value is not a Set
295
+ def add_to_set(key, value)
296
+ missing = !key?(key)
297
+ compute(key) do |old_value|
298
+ if old_value.nil? && missing
299
+ Set[value].freeze
300
+ else
301
+ unless old_value.is_a?(Set)
302
+ raise TypeError,
303
+ "existing value for #{key.inspect} must be a Set: #{old_value.inspect}"
304
+ end
305
+
306
+ (old_value | [value]).freeze
307
+ end
308
+ end
309
+ end
310
+
40
311
  # Alias for #size.
41
312
  #
42
- # @return [Integer]
313
+ # @return [Integer] the current number of entries
43
314
  def length
44
315
  size
45
316
  end
46
317
 
47
318
  # Check whether the map currently has no entries.
48
319
  #
49
- # @return [Boolean]
320
+ # @return [Boolean] true when the map currently has no entries
50
321
  def empty?
51
322
  size.zero?
52
323
  end
53
- end
54
324
 
55
- ConcurrentHashMap.prepend(MapMethods)
325
+ # Alias for #key?.
326
+ #
327
+ # @param key [Object] lookup key
328
+ # @return [Boolean] true when the key currently exists
329
+ def include?(key)
330
+ key?(key)
331
+ end
332
+ alias member? include?
333
+ end
56
334
  end
data/lib/ratomic/pool.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "timeout"
4
+
3
5
  module Ratomic
4
6
  # A Ractor-safe ownership-transfer pool for mutable Ruby objects.
5
7
  #
@@ -26,9 +28,9 @@ module Ratomic
26
28
  class Pool
27
29
  # Create a pool and seed it with +size+ objects from the factory block.
28
30
  #
29
- # @param size [Integer] number of pooled objects
31
+ # @param size [Integer] number of pooled objects to create up front
30
32
  # @param timeout [Numeric, nil] checkout timeout in seconds, or nil to wait indefinitely
31
- # @yieldreturn [Object] mutable object to store in the pool
33
+ # @yieldreturn [Object] a mutable object to place into the pool
32
34
  # @raise [ArgumentError] if +size+ is not positive
33
35
  # @raise [LocalJumpError] if no factory block is given
34
36
  def initialize(size = 5, timeout = 1.0)
@@ -47,7 +49,7 @@ module Ratomic
47
49
  # The returned object has been moved from the pool to the caller. The caller
48
50
  # owns it until it is passed to #checkin.
49
51
  #
50
- # @return [Object, nil] pooled object, or nil after timeout
52
+ # @return [Object, nil] the checked-out object, or nil if the timeout expires
51
53
  def checkout
52
54
  reply = Ractor::Port.new
53
55
  request_id = reply.object_id
@@ -66,8 +68,8 @@ module Ratomic
66
68
  # use the object after calling this method; Ruby raises Ractor::MovedError
67
69
  # for stale references.
68
70
  #
69
- # @param object [Object] previously checked-out pooled object
70
- # @return [nil]
71
+ # @param object [Object] the object previously checked out from the pool
72
+ # @return [nil] nothing useful is returned
71
73
  def checkin(object)
72
74
  @control.send([:checkin, object], move: true)
73
75
  nil
@@ -78,7 +80,7 @@ module Ratomic
78
80
  # This is primarily useful for tests and short-lived scripts. A closed pool
79
81
  # should not be used for further checkout/checkin operations.
80
82
  #
81
- # @return [nil]
83
+ # @return [nil] nothing useful is returned
82
84
  def close
83
85
  @control << [:shutdown]
84
86
  @control.value
@@ -92,9 +94,9 @@ module Ratomic
92
94
  # This is the preferred API because it guarantees checkin through an ensure
93
95
  # block. If checkout times out, raises Ratomic::Error and does not yield.
94
96
  #
95
- # @yieldparam object [Object] checked-out pooled object
97
+ # @yieldparam object [Object] the object checked out from the pool
96
98
  # @raise [Ratomic::Error] if checkout times out
97
- # @return [Object] block return value
99
+ # @return [Object] the block return value
98
100
  def with
99
101
  object = checkout
100
102
  raise Ratomic::Error, "pool checkout timeout" if object.nil?
data/lib/ratomic/queue.rb CHANGED
@@ -1,17 +1,71 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ratomic
4
- # Ruby convenience methods for {Queue}.
5
- module QueueMethods
4
+ # A Ractor-shareable multi-producer, multi-consumer queue.
5
+ #
6
+ # Queue stores Ruby objects in a fixed-size native ring buffer. Push blocks
7
+ # when the queue is full; pop blocks when the queue is empty.
8
+ #
9
+ # @example Push and pop work
10
+ # queue = Ratomic::Queue.new(128)
11
+ # queue << "job"
12
+ # queue.pop # => "job"
13
+ #
14
+ # @!method self.new(capacity)
15
+ # Create a queue with a fixed capacity.
16
+ #
17
+ # @param capacity [Integer] maximum number of items the queue can hold
18
+ # @return [Ratomic::Queue] a new shareable queue
19
+ # @raise [ArgumentError] if +capacity+ is outside the supported range
20
+ # @raise [TypeError] if +capacity+ cannot be converted to an Integer
21
+ #
22
+ # @!method push(item)
23
+ # Push an item, blocking until space is available.
24
+ #
25
+ # @param item [Object] the item to append to the queue
26
+ # @return [Ratomic::Queue] self
27
+ #
28
+ # @!method pop
29
+ # Pop an item, blocking until one is available.
30
+ #
31
+ # @return [Object] the next queued item
32
+ #
33
+ # @!method peek
34
+ # Return the next item without removing it.
35
+ #
36
+ # Since this is a concurrent queue, the value is a moment-in-time
37
+ # observation.
38
+ #
39
+ # @return [Object, nil] the next item, or nil if the queue is empty
40
+ #
41
+ # @!method empty?
42
+ # Check whether the queue currently appears empty.
43
+ #
44
+ # Since this is a concurrent queue, the value is a moment-in-time
45
+ # observation.
46
+ #
47
+ # @return [Boolean] true when the queue currently has no items
48
+ #
49
+ # @!method size
50
+ # Return the current queue size.
51
+ #
52
+ # Since this is a concurrent queue, the value is a moment-in-time
53
+ # observation.
54
+ #
55
+ # @return [Integer] the current number of queued items
56
+ #
57
+ # @!method length
58
+ # Alias for #size.
59
+ #
60
+ # @return [Integer] the current number of queued items
61
+ class Queue
6
62
  # Push an item and return the queue for chaining.
7
63
  #
8
- # @param item [Object]
9
- # @return [Ratomic::Queue]
64
+ # @param item [Object] the item to append to the queue
65
+ # @return [Ratomic::Queue] self
10
66
  def <<(item)
11
67
  push(item)
12
68
  self
13
69
  end
14
70
  end
15
-
16
- Queue.prepend(QueueMethods)
17
71
  end
@@ -4,7 +4,9 @@ module Ratomic
4
4
  # Internal sentinel object for future Hash-like APIs that need to distinguish
5
5
  # missing keys from explicit nil values.
6
6
  class Undefined
7
- # @return [String]
7
+ # Return the sentinel's stable inspection string.
8
+ #
9
+ # @return [String] a human-readable sentinel marker
8
10
  def inspect
9
11
  "#<Undefined>"
10
12
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ratomic
4
- VERSION = "0.2.1"
4
+ # Current gem version string.
5
+ VERSION = "0.3.1"
5
6
  end
data/lib/ratomic.rb CHANGED
@@ -1,16 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "timeout"
4
-
5
- require_relative "ratomic/version"
6
- require_relative "ratomic/ratomic"
7
- require_relative "ratomic/counter"
8
- require_relative "ratomic/undefined"
9
- require_relative "ratomic/pool"
10
- require_relative "ratomic/map"
11
- require_relative "ratomic/queue"
12
-
3
+ # Ratomic provides mutable data structures for Ruby Ractors. Its primitives
4
+ # are backed by native Rust concurrency libraries so Ruby code can share useful
5
+ # state across Ractors without falling back to one global lock. Pool uses Ruby
6
+ # Ractor ownership-transfer primitives instead of the native Rust path.
7
+ #
8
+ # The public API currently includes {Counter}, {Map}, {Queue}, and {Pool}.
13
9
  module Ratomic
14
- # Base error for Ratomic-specific runtime failures.
10
+ # Base error class for Ratomic-specific failures.
15
11
  class Error < StandardError; end
16
12
  end
13
+
14
+ require "ratomic/ratomic"
15
+ require "ratomic/version"
16
+
17
+ require "ratomic/undefined"
18
+ require "ratomic/counter"
19
+ require "ratomic/map"
20
+ require "ratomic/queue"
21
+ require "ratomic/pool"
data/ratomic.gemspec CHANGED
@@ -9,15 +9,16 @@ Gem::Specification.new do |spec|
9
9
  spec.email = ["mike@perham.net"]
10
10
  spec.metadata["maintainers"] = "Ken C. Demanawa"
11
11
 
12
- spec.summary = "Mutable data structures for Ractors"
13
- spec.description = spec.summary
14
- spec.homepage = "https://github.com/mperham/ratomic"
12
+ spec.summary = "Ractor-safe concurrent data structures for Ruby"
13
+ spec.description = "Ractor-safe counters, maps, queues, and ownership-transfer pools " \
14
+ "backed by native Rust concurrency primitives."
15
+ spec.homepage = "https://mperham.github.io/ratomic"
15
16
  spec.license = "MIT"
16
- spec.required_ruby_version = ">= 4.0.0"
17
+ spec.required_ruby_version = [">= 4.0", "< 4.1.dev"]
17
18
 
18
19
  spec.metadata["homepage_uri"] = spec.homepage
19
20
  spec.metadata["source_code_uri"] = "https://github.com/mperham/ratomic"
20
- spec.metadata["changelog_uri"] = "https://github.com/mperham/ratomic"
21
+ spec.metadata["changelog_uri"] = "https://github.com/mperham/ratomic/blob/trunk/CHANGELOG.md"
21
22
  spec.metadata["rubygems_mfa_required"] = "true"
22
23
 
23
24
  # Specify which files should be added to the gem when it is released.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratomic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -24,7 +24,8 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.9.128
27
- description: Mutable data structures for Ractors
27
+ description: Ractor-safe counters, maps, queues, and ownership-transfer pools backed
28
+ by native Rust concurrency primitives.
28
29
  email:
29
30
  - mike@perham.net
30
31
  executables: []
@@ -55,14 +56,14 @@ files:
55
56
  - lib/ratomic/undefined.rb
56
57
  - lib/ratomic/version.rb
57
58
  - ratomic.gemspec
58
- homepage: https://github.com/mperham/ratomic
59
+ homepage: https://mperham.github.io/ratomic
59
60
  licenses:
60
61
  - MIT
61
62
  metadata:
62
63
  maintainers: Ken C. Demanawa
63
- homepage_uri: https://github.com/mperham/ratomic
64
+ homepage_uri: https://mperham.github.io/ratomic
64
65
  source_code_uri: https://github.com/mperham/ratomic
65
- changelog_uri: https://github.com/mperham/ratomic
66
+ changelog_uri: https://github.com/mperham/ratomic/blob/trunk/CHANGELOG.md
66
67
  rubygems_mfa_required: 'true'
67
68
  rdoc_options: []
68
69
  require_paths:
@@ -71,7 +72,10 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
72
  requirements:
72
73
  - - ">="
73
74
  - !ruby/object:Gem::Version
74
- version: 4.0.0
75
+ version: '4.0'
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: 4.1.dev
75
79
  required_rubygems_version: !ruby/object:Gem::Requirement
76
80
  requirements:
77
81
  - - ">="
@@ -80,5 +84,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
84
  requirements: []
81
85
  rubygems_version: 4.0.10
82
86
  specification_version: 4
83
- summary: Mutable data structures for Ractors
87
+ summary: Ractor-safe concurrent data structures for Ruby
84
88
  test_files: []