ratomic 0.3.5 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cce49de24aecb066a0fcab6361ab4637b138ea1162888678701ba9030bfe2d1
4
- data.tar.gz: 93ff58b2084bb8fd10edccf7b059e5e2b6503f0814256ecabba9065c9a5e4139
3
+ metadata.gz: a08c265d5b7a1c6f780a36dc2448b7cb49421c53f15957a242dd1457503b33c1
4
+ data.tar.gz: 5dcbb8b1fd92a0e162dcc4daa9ca6cec31117c918bb984ddf03a5ccb68df8779
5
5
  SHA512:
6
- metadata.gz: 49cb72854fd73aa9e0cc5c3ee9614ba01433748101bd6f7037d8b498d3b9853761293efe1a680b2c3e9251c5b85274c250e068674696959e84513080682b8cdf
7
- data.tar.gz: 63f5f4e7584a46d5f3fd34f611fce08e0d6d451d72b91907a947018ce5b2a14ffca87e551189ee92dec64be3989ed56f062cf8f96a62a4c5a1df42890b456093
6
+ metadata.gz: 0dccc6d4fcceed16ac0437d2550c25deb6ad83c4de57a5b0465d0de604f4376baa776312607d9bd361073c53eeef14e88c651db5cf3efb09bedcd3a38e9ee93d
7
+ data.tar.gz: b6e9154857321453b8e0b2919ee35551beab7763b80c02358172537e35375628420f10c434d3e97432f87f99e567794de6343855a791d74aa0540d7d11e5ea7c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-06-10
4
+
5
+ ### Added
6
+
7
+ * Introduced `Ratomic::LocalPool`, a new pooling primitive for live resources that should remain local to the Ractor that created them.
8
+ * Added Redis-based smoke tests demonstrating safe operation across both Threads and Ractors.
9
+ * Added queue producer/consumer Redis examples.
10
+ * Added RBS definitions for `LocalPool`.
11
+ * Expanded API documentation and usage examples.
12
+
13
+ ### Design
14
+
15
+ `Ratomic::Pool` and `Ratomic::LocalPool` serve different ownership models:
16
+
17
+ * `Pool` — ownership transfer
18
+ * `LocalPool` — ownership preservation
19
+
20
+ `LocalPool` is intended for resources such as:
21
+
22
+ * Redis clients
23
+ * Database connections
24
+ * HTTP clients
25
+ * Kafka producers
26
+ * Other stateful network resources
27
+
28
+ ### Notes
29
+
30
+ `LocalPool` is implemented in pure Ruby and is not backed by Ratomic's Rust extension.
31
+
32
+
3
33
  ## [0.3.5] - 2026-06-06
4
34
 
5
35
  - Fix the native loader so development loads the compiled extension from the
data/Cargo.toml CHANGED
@@ -2,7 +2,14 @@
2
2
  members = ["ext/ratomic"]
3
3
  resolver = "2"
4
4
 
5
+ [profile.dev]
6
+ debug = true
7
+
5
8
  [profile.release]
6
9
  panic = "abort"
7
10
  lto = true
8
11
  codegen-units = 1
12
+ # By default, debug symbols are stripped from the final binary which makes it
13
+ # harder to debug if something goes wrong. It's recommended to keep debug
14
+ # symbols in the release build so that you can debug the final binary if needed.
15
+ debug = false
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%204.0-ruby.svg)](https://www.ruby-lang.org/en/)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- Ratomic provides mutable data structures for Ruby Ractors. Its primitives are backed by native Rust concurrency libraries so Ruby code can share useful state across Ractors without falling back to one global lock. `Pool` uses Ruby Ractor ownership-transfer primitives instead of the native Rust path.
8
+ Ratomic provides mutable data structures for Ruby Ractors. Its core shared primitives are backed by native Rust concurrency libraries so Ruby code can share useful state across Ractors without falling back to one global lock. `Pool` and `LocalPool` are pure Ruby primitives that use Ruby Ractor ownership and locality semantics instead of the native Rust path.
9
9
 
10
10
  ## Project Direction
11
11
 
@@ -43,18 +43,27 @@ RBS signatures are included under `sig/` for downstream type checking.
43
43
  ## Examples And Benchmarks
44
44
 
45
45
  - [`redis_poc`](./redis_poc) contains local Redis scripts that exercise
46
- `Ratomic::Map`, `Ratomic::Counter`, and `Ratomic::Pool` under Thread and
46
+ `Ratomic::Map`, `Ratomic::Counter`, and `Ratomic::LocalPool` under Thread and
47
47
  Ractor workloads.
48
+ - [`pgoutput-parser`](https://github.com/kanutocd/pgoutput-parser#relation-metadata-tracking)
49
+ uses `Ratomic::Map` for relation metadata tracking in a real CDC pipeline
50
+ POC, with a matching benchmark and deeper implementation notes in
51
+ [docs/relation_tracker.md](https://github.com/kanutocd/pgoutput-parser/blob/main/docs/relation_tracker.md).
52
+ - [`sidekiq-tenant-policy-cache`](https://github.com/kanutocd/sidekiq-tenant-policy-cache)
53
+ shows `Ratomic::Map` and `Ratomic::Counter` in Sidekiq middleware for tenant
54
+ policy caching and cache-hit / cache-miss tracking, with a benchmarked
55
+ cache-vs-policy-every-job comparison.
48
56
  - The [`cdc-parallel` Ratomic benchmark][cdc-parallel-ratomic] demonstrates
49
57
  Ractor workers updating shared CDC processing metrics through `Ratomic::Map`
50
58
  and `Ratomic::Counter`.
51
59
 
52
60
  ## Usage
53
61
 
54
- Ratomic provides two safety models:
62
+ Ratomic provides three safety models:
55
63
 
56
64
  - `Counter`, `Map`, and `Queue` are shared concurrent structures.
57
- - `Pool` transfers ownership of mutable objects between Ractors.
65
+ - `Pool` transfers ownership of plain mutable objects between Ractors.
66
+ - `LocalPool` keeps live resources local to the Ractor that created them.
58
67
 
59
68
  That distinction matters. A mutable pooled object is not shared by multiple Ractors
60
69
  at the same time. It is moved to the caller on checkout and moved back to the pool
@@ -119,6 +128,11 @@ groups.append("jobs", "import") # => ["import"]
119
128
  groups.add_to_set("workers", "alpha") # => #<Set: {"alpha"}>
120
129
  ```
121
130
 
131
+ Some `Map` methods hold an internal guard while a block runs or while a
132
+ reference is live. Avoid re-entering the same map from inside those blocks or
133
+ mutating the same key while holding a reference from `get` or `[]`. The API
134
+ docs cover the exact locking caveats.
135
+
122
136
  ### `Ratomic::Queue`
123
137
 
124
138
  `Ratomic::Queue` is a Ractor-shareable multi-producer, multi-consumer queue.
@@ -219,6 +233,142 @@ The lower-level `Ratomic::FixedSizeObjectPool` native class may still exist, but
219
233
  `Ratomic::Pool` does not inherit from it. The public `Pool` API is implemented
220
234
  in Ruby so it can use Ruby's Ractor ownership primitives directly.
221
235
 
236
+
237
+ ### `Ratomic::LocalPool`
238
+
239
+ `Ratomic::LocalPool` is the safe pool shape for live resources that should stay
240
+ local to the Ractor that created them.
241
+
242
+ Use it for resources such as:
243
+
244
+ - Redis clients
245
+ - database connections
246
+ - HTTP clients
247
+ - Kafka producers
248
+ - OpenSearch clients
249
+ - per-worker caches, buffers, encoders, or aggregators
250
+
251
+ Unlike `Ratomic::Pool`, `LocalPool` does **not** move pooled objects between
252
+ Ractors. The `LocalPool` instance is a shareable facade. Each Ractor lazily
253
+ creates and owns its own private, thread-safe resource pool behind that facade.
254
+ Threads inside the same Ractor share that local pool, but different Ractors
255
+ never share the live resources.
256
+
257
+ ```ruby
258
+ require "ratomic"
259
+ require "redis-client"
260
+
261
+ RedisFactory = Data.define(:host) do
262
+ def call
263
+ RedisClient.new(host: host)
264
+ end
265
+ end
266
+
267
+ REDIS = Ratomic::LocalPool.new(
268
+ size: 10,
269
+ timeout: 1,
270
+ factory: RedisFactory.new("127.0.0.1".freeze)
271
+ )
272
+
273
+ REDIS.with do |client|
274
+ client.call("ping")
275
+ end
276
+ ```
277
+
278
+ Use `Pool` for plain mutable values where ownership transfer is the intended
279
+ safety model. Use `LocalPool` for live resources that should be created, used,
280
+ and reused inside the same Ractor.
281
+
282
+ The intended topology is:
283
+
284
+ ```text
285
+ shareable LocalPool facade
286
+
287
+ one local resource pool per Ractor
288
+
289
+ threads inside that Ractor share local resources
290
+ ```
291
+
292
+ The mental model is intentionally close to a Ruby local variable: the resource is
293
+ local to the execution scope that owns it. For `LocalPool`, that scope is the
294
+ current Ractor.
295
+
296
+ #### Pure Ruby implementation
297
+
298
+ `LocalPool` is implemented in pure Ruby.
299
+
300
+ Unlike `Counter`, `Map`, and `Queue`, it is not backed by the Rust native
301
+ extension. Its safety comes from Ruby Ractor ownership boundaries and locality,
302
+ not from Rust synchronization primitives.
303
+
304
+ #### Why not use `Pool` for Redis clients?
305
+
306
+ `Pool` moves checked-out objects between Ractors. That is correct for plain
307
+ mutable Ruby values such as arrays or buffers, but it is a poor fit for live I/O
308
+ resources. Redis clients, database connections, sockets, and similar resources
309
+ carry internal connection state. Moving those objects across Ractor boundaries can
310
+ leave nested internal state unusable, producing errors such as
311
+ `Ractor::MovedError`.
312
+
313
+ `LocalPool` avoids that class of bug by not moving live resources at all. Work
314
+ moves between Ractors. Live resources stay local.
315
+
316
+ #### Redis smoke-test snapshot
317
+
318
+ The Redis POC includes two scripts under `redis_poc/`.
319
+
320
+ `basic_redis.rb` exercises repeated Redis operations from both Threads and
321
+ Ractors:
322
+
323
+ ```text
324
+ Thread
325
+ [{"one" => 31501}, {"two" => 31379}, {"three" => 31320}, {"four" => 31410}, {"five" => 31454}]
326
+
327
+ Ractor
328
+ [{"one" => 42419}, {"two" => 42186}, {"three" => 42400}, {"four" => 42206}, {"five" => 42568}]
329
+ ```
330
+
331
+ `queue_redis.rb` exercises a producer/consumer Redis queue workload:
332
+
333
+ ```text
334
+ [:start, Ractor, 2026-06-10 01:17:57.584727984 +0800]
335
+ [[:producer_done, 0], [:producer_done, 1]]
336
+ [[:consumer_done, 0, 7998], [:consumer_done, 1, 7987], [:consumer_done, 2, 7984], [:consumer_done, 3, 8035], [:consumer_done, 4, 7996]]
337
+ [{"one" => 0}, {"two" => 0}, {"three" => 0}, {"four" => 0}, {"five" => 0}]
338
+ [:end, 2026-06-10 01:18:02.226310862 +0800]
339
+ ```
340
+
341
+ These numbers are a smoke-test snapshot, not a formal benchmark claim. The
342
+ important interpretation is:
343
+
344
+ - no `Ractor::MovedError`
345
+ - no `Ractor::IsolationError`
346
+ - no process crash
347
+ - all produced queue items were consumed
348
+ - Redis queues drained to zero
349
+ - live Redis clients remained owned by the Ractor that created them
350
+
351
+ #### Inception pool
352
+
353
+ Internally, `LocalPool` follows the "inception pool" shape discovered while
354
+ experimenting with Redis clients under Ruby Ractors:
355
+
356
+ ```text
357
+ pool facade
358
+
359
+ local pool
360
+
361
+ resource
362
+ ```
363
+
364
+ That same ownership pattern appears in hybrid execution runtimes: put parallel
365
+ workers on the outside, keep I/O concurrency and live resources inside the worker
366
+ that owns them. Ratomic keeps the primitive general-purpose and independent of
367
+ any specific runtime, scheduler, database, or message system.
368
+
369
+ `LocalPool#close` closes only the current Ractor's local pool. Other Ractors own
370
+ their own pools and must close them independently when needed.
371
+
222
372
  ## Contributing
223
373
 
224
374
  Please read the [Code of Conduct](./CODE_OF_CONDUCT.md) before contributing.
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module Ratomic
6
+ # Design Note
7
+ #
8
+ # LocalPool originated while investigating Redis clients under Ruby
9
+ # Ractors. The original goal was to reuse Pool, but ownership-transfer
10
+ # semantics proved incompatible with live resources containing internal
11
+ # state.
12
+ #
13
+ # The resulting architecture became known as the "Inception Pool"
14
+ # design:
15
+ #
16
+ # LocalPool facade
17
+ # ↓
18
+ # Ractor-local pool
19
+ # ↓
20
+ # Live resources
21
+ #
22
+ # or informally:
23
+ #
24
+ # Pool
25
+ # ↓
26
+ # Pool
27
+ # ↓
28
+ # Resource
29
+ #
30
+ # The public API intentionally uses the more descriptive name `LocalPool`.
31
+ #
32
+ # A shareable facade over resources that stay local to each Ractor.
33
+ #
34
+ # LocalPool is intended for live resources such as Redis clients,
35
+ # database connections, sockets, and other objects which must not be moved
36
+ # between Ractors. The facade itself is shareable, but each Ractor lazily
37
+ # creates and owns an independent thread-safe local pool. Threads inside the
38
+ # same Ractor share that local pool; different Ractors never share the live
39
+ # resources.
40
+ #
41
+ # This is the correct shape for resources with process, socket, connection,
42
+ # or native state. Move work across Ractor boundaries, not live clients.
43
+ #
44
+ # The factory must be Ractor-shareable because the facade stores it and each
45
+ # Ractor calls it when its local resource pool needs to create a resource. Prefer a
46
+ # small immutable callable object instead of a block when the pool will be
47
+ # used from multiple Ractors.
48
+ #
49
+ # @example Redis clients owned by each Ractor
50
+ # RedisFactory = Data.define(:host) do
51
+ # def call
52
+ # RedisClient.new(host: host)
53
+ # end
54
+ # end
55
+ #
56
+ # REDIS = Ratomic::LocalPool.new(
57
+ # size: 5,
58
+ # timeout: 1,
59
+ # factory: RedisFactory.new("127.0.0.1".freeze)
60
+ # )
61
+ #
62
+ # REDIS.with { |client| client.call("ping") }
63
+ #
64
+ # @note LocalPool is implemented in pure Ruby. It is not backed by the Rust
65
+ # native extension used by Counter, Map, and Queue. Its safety comes from
66
+ # Ruby Ractor locality: live resources are created and reused inside the
67
+ # Ractor that owns them.
68
+ #
69
+ # @see Pool Use Pool for plain mutable Ruby values where ownership transfer
70
+ # is the desired safety model.
71
+ class LocalPool
72
+ # Create a per-Ractor local pool facade.
73
+ #
74
+ # @param size [Integer] maximum number of resources in each Ractor-local pool
75
+ # @param timeout [Numeric, nil] checkout timeout in seconds, or nil to wait indefinitely
76
+ # @param factory [#call, nil] shareable object factory
77
+ # @yieldreturn [Object] resource created inside the current Ractor
78
+ # @raise [ArgumentError] if size, timeout, or factory is invalid
79
+ # @raise [LocalJumpError] if no factory or block is given
80
+ def initialize(size: 5, timeout: 1.0, factory: nil, &block)
81
+ raise ArgumentError, "pool size must be positive" unless size.is_a?(Integer) && size.positive?
82
+ raise ArgumentError, "pool timeout must be numeric or nil" unless timeout.nil? || timeout.is_a?(Numeric)
83
+ raise ArgumentError, "pool timeout must be non-negative" if timeout && timeout.negative?
84
+ raise ArgumentError, "use either factory: or block, not both" if factory && block
85
+
86
+ factory ||= block
87
+ raise LocalJumpError, "no factory given" unless factory
88
+ raise ArgumentError, "factory must respond to #call" unless factory.respond_to?(:call)
89
+ raise ArgumentError, "factory must be Ractor-shareable" unless Ractor.shareable?(factory)
90
+
91
+ @size = size
92
+ @timeout = timeout&.to_f
93
+ @factory = factory
94
+ @storage_key = :"ratomic_local_pool_#{object_id}"
95
+
96
+ freeze
97
+ Ractor.make_shareable(self)
98
+ end
99
+
100
+ # Checkout a current-Ractor-owned resource, yield it, then return it to the
101
+ # same Ractor-local pool.
102
+ #
103
+ # No resource is moved between Ractors. The yielded object belongs to the
104
+ # Ractor which called this method.
105
+ #
106
+ # @yieldparam object [Object] current-Ractor-owned resource
107
+ # @raise [Ratomic::Error] if checkout times out
108
+ # @return [Object] the block return value
109
+ def with
110
+ local_pool.with { |object| yield object }
111
+ rescue Timeout::Error
112
+ raise Ratomic::Error, "pool checkout timeout"
113
+ end
114
+
115
+ # Close the current Ractor's local pool, if it has been initialized.
116
+ #
117
+ # Other Ractors own independent local pools and are not affected. Available
118
+ # resources are closed if they respond to #close. Resources currently
119
+ # checked out by threads in this Ractor are closed when returned.
120
+ #
121
+ # @return [nil]
122
+ def close
123
+ pool = Ractor.current[@storage_key]
124
+ return nil unless pool
125
+
126
+ Ractor.current[@storage_key] = nil
127
+ pool.close
128
+ nil
129
+ end
130
+
131
+ # Minimal thread-safe resource pool used inside exactly one Ractor.
132
+ class ResourcePool
133
+ def initialize(size:, timeout:, factory:)
134
+ @size = size
135
+ @timeout = timeout
136
+ @factory = factory
137
+ @available = []
138
+ @created = 0
139
+ @closed = false
140
+ @mutex = Mutex.new
141
+ @condition = ConditionVariable.new
142
+ end
143
+
144
+ def with
145
+ object = checkout
146
+ yield object
147
+ ensure
148
+ checkin(object) if object
149
+ end
150
+
151
+ def close
152
+ objects = nil
153
+
154
+ @mutex.synchronize do
155
+ @closed = true
156
+ objects = @available.dup
157
+ @available.clear
158
+ @condition.broadcast
159
+ end
160
+
161
+ objects.each { |object| close_object(object) }
162
+ nil
163
+ end
164
+
165
+ private
166
+
167
+ def checkout
168
+ deadline = monotonic_deadline
169
+
170
+ should_create = @mutex.synchronize do
171
+ raise IOError, "pool is closed" if @closed
172
+
173
+ loop do
174
+ object = @available.pop
175
+ return object if object
176
+
177
+ if @created < @size
178
+ @created += 1
179
+ break true
180
+ end
181
+
182
+ wait_for_available(deadline)
183
+ raise IOError, "pool is closed" if @closed
184
+ end
185
+ end
186
+
187
+ create_object if should_create
188
+ end
189
+
190
+ def checkin(object)
191
+ close_now = false
192
+
193
+ @mutex.synchronize do
194
+ if @closed
195
+ close_now = true
196
+ else
197
+ @available << object
198
+ @condition.signal
199
+ end
200
+ end
201
+
202
+ close_object(object) if close_now
203
+ nil
204
+ end
205
+
206
+ def create_object
207
+ @factory.call
208
+ rescue Exception # rubocop:disable Lint/RescueException
209
+ @mutex.synchronize do
210
+ @created -= 1
211
+ @condition.signal
212
+ end
213
+ raise
214
+ end
215
+
216
+ def close_object(object)
217
+ object.close if object.respond_to?(:close)
218
+ end
219
+
220
+ def monotonic_deadline
221
+ return nil unless @timeout
222
+
223
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout
224
+ end
225
+
226
+ def wait_for_available(deadline)
227
+ if deadline
228
+ remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
229
+ raise Timeout::Error, "pool checkout timeout" if remaining <= 0
230
+
231
+ @condition.wait(@mutex, remaining)
232
+ else
233
+ @condition.wait(@mutex)
234
+ end
235
+ end
236
+ end
237
+
238
+ private
239
+
240
+ def local_pool
241
+ Ractor.current[@storage_key] ||= ResourcePool.new(size: @size, timeout: @timeout, factory: @factory)
242
+ end
243
+
244
+ private_constant :ResourcePool
245
+ end
246
+ end
data/lib/ratomic/map.rb CHANGED
@@ -11,6 +11,11 @@ module Ratomic
11
11
  # This is not a full Hash replacement. Iteration and arbitrary mutable object
12
12
  # borrowing are intentionally absent.
13
13
  #
14
+ # Some methods on this type hold an internal entry guard while the block runs
15
+ # or while a reference is live. Do not call back into the same map from inside
16
+ # those blocks, and do not hold a reference from #get or #[] while mutating
17
+ # the same key. That can deadlock the underlying DashMap bucket.
18
+ #
14
19
  # @example Store pipeline offsets
15
20
  # OFFSETS = Ratomic::Map.new
16
21
  # OFFSETS[:source_a] = 42
@@ -22,6 +27,10 @@ module Ratomic
22
27
  # Missing keys return nil, so use #key? or #fetch when stored nil values
23
28
  # need to be distinguished from missing entries.
24
29
  #
30
+ # If you keep the returned reference alive and then mutate the same key, you
31
+ # can deadlock the underlying DashMap bucket. Copy out what you need and let
32
+ # the reference go out of scope before mutating.
33
+ #
25
34
  # @param key [Object] lookup key
26
35
  # @return [Object, nil] the stored value, or nil when the key is missing
27
36
  #
@@ -102,6 +111,10 @@ module Ratomic
102
111
  # into the same map from inside the block. Prefer native update helpers such
103
112
  # as #increment when they fit the workflow.
104
113
  #
114
+ # Never call this method from inside another live reference to the same
115
+ # entry or from a block that already holds the same map's guard. That can
116
+ # deadlock the bucket.
117
+ #
105
118
  # If the block raises, the previous value is preserved. If the key was
106
119
  # missing, no entry is inserted.
107
120
  #
@@ -141,6 +154,10 @@ module Ratomic
141
154
  # into the same map from inside the block. Prefer native update helpers such
142
155
  # as #increment when they fit the workflow.
143
156
  #
157
+ # Never call this method from inside another live reference to the same
158
+ # entry or from a block that already holds the same map's guard. That can
159
+ # deadlock the bucket.
160
+ #
144
161
  # If the block raises, the previous value is preserved.
145
162
  #
146
163
  # @param key [Object] key to update
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Ratomic
4
4
  # Current gem version string.
5
- VERSION = "0.3.5"
5
+ VERSION = "0.4.0"
6
6
  end
data/lib/ratomic.rb CHANGED
@@ -4,10 +4,11 @@ require "rbconfig"
4
4
 
5
5
  # Ratomic provides mutable data structures for Ruby Ractors. Its primitives
6
6
  # are backed by native Rust concurrency libraries so Ruby code can share useful
7
- # state across Ractors without falling back to one global lock. Pool uses Ruby
8
- # Ractor ownership-transfer primitives instead of the native Rust path.
7
+ # state across Ractors without falling back to one global lock. `Pool` and
8
+ # `LocalPool` are pure Ruby primitives which use Ruby Ractor ownership and
9
+ # locality semantics instead of the native Rust path.
9
10
  #
10
- # The public API currently includes {Counter}, {Map}, {Queue}, and {Pool}.
11
+ # The public API currently includes {Counter}, {Map}, {Queue}, {Pool}, and {LocalPool}.
11
12
  module Ratomic
12
13
  # Base error class for Ratomic-specific failures.
13
14
  class Error < StandardError; end
@@ -53,3 +54,5 @@ require "ratomic/counter"
53
54
  require "ratomic/map"
54
55
  require "ratomic/queue"
55
56
  require "ratomic/pool"
57
+
58
+ require "ratomic/local_pool"
data/sig/ratomic.rbs CHANGED
@@ -62,6 +62,14 @@ module Ratomic
62
62
  def length: () -> Integer
63
63
  end
64
64
 
65
+ class LocalPool
66
+ def self.new: (?size: Integer, ?timeout: (Numeric | nil), factory: untyped) -> LocalPool
67
+ | (?size: Integer, ?timeout: (Numeric | nil)) { () -> untyped } -> LocalPool
68
+
69
+ def with: () { (untyped object) -> untyped } -> untyped
70
+ def close: () -> nil
71
+ end
72
+
65
73
  class Pool
66
74
  def self.new: (?Integer size, ?(Numeric | nil) timeout) { () -> untyped } -> Pool
67
75
 
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.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -50,6 +50,7 @@ files:
50
50
  - ext/ratomic/src/sem.rs
51
51
  - lib/ratomic.rb
52
52
  - lib/ratomic/counter.rb
53
+ - lib/ratomic/local_pool.rb
53
54
  - lib/ratomic/map.rb
54
55
  - lib/ratomic/pool.rb
55
56
  - lib/ratomic/queue.rb