ratomic 0.2.0-x86_64-linux
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 +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +176 -0
- data/lib/ratomic/3.0/ratomic.so +0 -0
- data/lib/ratomic/3.1/ratomic.so +0 -0
- data/lib/ratomic/3.2/ratomic.so +0 -0
- data/lib/ratomic/3.3/ratomic.so +0 -0
- data/lib/ratomic/3.4/ratomic.so +0 -0
- data/lib/ratomic/4.0/ratomic.so +0 -0
- data/lib/ratomic/counter.rb +51 -0
- data/lib/ratomic/map.rb +56 -0
- data/lib/ratomic/pool.rb +154 -0
- data/lib/ratomic/queue.rb +17 -0
- data/lib/ratomic/undefined.rb +15 -0
- data/lib/ratomic/version.rb +5 -0
- data/lib/ratomic.rb +16 -0
- data/ratomic.gemspec +46 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b3286cccf68f0a8ade3d2ac457a72d160f1118526ee307b5806aaa72f5a4dfdb
|
|
4
|
+
data.tar.gz: 14d72b6c235f3bb55152f22de80af2993e771fd3ad91e86ce177e7926bcfb1ee
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 98091fcd188cf500f959e29e8cef6e7adc456bd8c19afba087098bd9b160400c6de98e2e0bafe69a40d824b76bad36992d601405ff39fdfad67abe58fa8a6c4c
|
|
7
|
+
data.tar.gz: ba08bd30f0e721faf954d5bb9f049b0d15eec2ad427be47b773c00b6666f362bbcdf83e9981a6faa79691ce19b25f58d59e6710cb8b9b82f7f89a026a9a08639
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-06-04
|
|
4
|
+
|
|
5
|
+
- Drop Ruby 3.x support and require Ruby 4.
|
|
6
|
+
- Add `length`, `size`, `peek`, and `empty?` methods to `Ratomic::Queue`.
|
|
7
|
+
- Change `Ratomic::Pool` to use Ruby 4 `Ractor::Port` ownership transfer semantics, fixing the unsafe stale-reference behavior reported in [#5]
|
|
8
|
+
(https://github.com/mperham/ratomic/issues/5).
|
|
9
|
+
- Organize Ruby wrapper code by primitive.
|
|
10
|
+
- Add primitive contract tests for `Counter`, `Map`, `Queue`, and `Pool`.
|
|
11
|
+
- Add SimpleCov coverage reporting.
|
|
12
|
+
- Undefine native typed-data default allocators to remove Ruby 4 warnings.
|
|
13
|
+
|
|
14
|
+
## [0.1.0] - 2025-03-20
|
|
15
|
+
|
|
16
|
+
- Initial release
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Ilya Bylich
|
|
4
|
+
Copyright (c) Contributed Systems LLC
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Ratomic
|
|
2
|
+
|
|
3
|
+
Ratomic provides mutable data structures for use with Ruby's Ractors.
|
|
4
|
+
This allows Ruby code to scale beyond the infamous GVL.
|
|
5
|
+
|
|
6
|
+
# HELP WANTED!
|
|
7
|
+
|
|
8
|
+
> If you know Rust and Ruby C-extensions, we need your help!
|
|
9
|
+
> This project is brand new and could use your knowledge!
|
|
10
|
+
> If you don't know Rust or C, consider this a challenge to learn and solve.
|
|
11
|
+
> Read through the [issues](//github.com/mperham/ratomic/issues) to find work that sounds interesting to you.
|
|
12
|
+
|
|
13
|
+
## How to contribute
|
|
14
|
+
|
|
15
|
+
Please make sure to understand our [Code of Conduct](./CODE_OF_CONDUCT.md).
|
|
16
|
+
|
|
17
|
+
After changing code, you can give it a spin with:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
rake
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This should compile the Rust code and run all tests.
|
|
24
|
+
The test suite writes a SimpleCov report to `coverage/index.html` so you can see which Ruby wrapper paths are covered.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
bundle add ratomic
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
TODO: We have not released a gem yet.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
Ratomic provides several useful Ractor-safe structures.
|
|
39
|
+
Note the APIs available are frequently very limited compared to Ruby's broad API.
|
|
40
|
+
|
|
41
|
+
These structures are designed for use as class-level constants so they can be shared by numerous Ractors.
|
|
42
|
+
|
|
43
|
+
Ratomic has two different safety models:
|
|
44
|
+
|
|
45
|
+
* `Counter`, `Map`, and `Queue` are shared concurrent structures.
|
|
46
|
+
* `Pool` transfers ownership of mutable objects between Ractors.
|
|
47
|
+
|
|
48
|
+
That distinction matters. A mutable pooled object is not shared by multiple Ractors at the same time. It is moved to the caller on checkout and moved back to the pool on checkin.
|
|
49
|
+
|
|
50
|
+
### `Ratomic::Counter`
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
c = Ratomic::Counter.new
|
|
54
|
+
c.read # => 0
|
|
55
|
+
c.inc
|
|
56
|
+
c.inc(5)
|
|
57
|
+
c.dec(1)
|
|
58
|
+
c.dec
|
|
59
|
+
c.read # => 4
|
|
60
|
+
c.to_i # => 4
|
|
61
|
+
c.zero? # => false
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `Ratomic::Pool`
|
|
65
|
+
|
|
66
|
+
A Ractor-safe object pool:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
POOL = Ratomic::Pool.new(5, 1.0) { [] }
|
|
70
|
+
POOL.with do |obj|
|
|
71
|
+
# do something with obj
|
|
72
|
+
obj << "work"
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`Pool` is an ownership-transfer pool for mutable Ruby objects. It uses Ruby 4's `Ractor::Port` and `move: true` semantics so only one Ractor owns a checked-out object at a time.
|
|
77
|
+
|
|
78
|
+
This design addresses [issue #5](https://github.com/mperham/ratomic/issues/5), where using a pooled object after `with` could lead to memory corruption or a process crash. The fix is Rust-inspired ownership transfer, not Rust's full borrow checker: Ruby enforces the boundary dynamically at runtime through Ractor move semantics, while Rust enforces ownership and borrowing statically at compile time.
|
|
79
|
+
|
|
80
|
+
In that model:
|
|
81
|
+
|
|
82
|
+
* the Rust owner maps to the Ractor that currently checked out the pooled object
|
|
83
|
+
* the Rust move maps to `Ractor::Port#send(..., move: true)`
|
|
84
|
+
* Rust's "cannot use after move" rule maps to Ruby raising `Ractor::MovedError`
|
|
85
|
+
* borrowing is not modeled; `Pool` transfers ownership instead of lending references
|
|
86
|
+
|
|
87
|
+
When an object is checked out:
|
|
88
|
+
|
|
89
|
+
* the pool moves the object to the caller
|
|
90
|
+
* the caller can mutate the object while it owns it
|
|
91
|
+
* the pool cannot hand that object to another Ractor until it is checked in
|
|
92
|
+
|
|
93
|
+
When an object is checked in:
|
|
94
|
+
|
|
95
|
+
* ownership moves back to the pool
|
|
96
|
+
* stale references held by the caller become moved objects
|
|
97
|
+
* using those stale references raises `Ractor::MovedError`
|
|
98
|
+
|
|
99
|
+
This means incorrect usage fails at the Ruby object-ownership boundary rather than allowing two Ractors to mutate the same object concurrently.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
outside = nil
|
|
103
|
+
|
|
104
|
+
POOL.with do |obj|
|
|
105
|
+
outside = obj
|
|
106
|
+
obj << "inside"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
outside << "outside"
|
|
110
|
+
# raises Ractor::MovedError
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The lower-level `Ratomic::FixedSizeObjectPool` native class may still exist, but `Ratomic::Pool` does not inherit from it. The public `Pool` API is implemented in Ruby so it can use Ruby's Ractor ownership primitives directly.
|
|
114
|
+
|
|
115
|
+
Manual checkout/checkin is also supported:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
obj = POOL.checkout
|
|
119
|
+
raise "pool checkout timeout" if obj.nil?
|
|
120
|
+
|
|
121
|
+
begin
|
|
122
|
+
obj << "manual work"
|
|
123
|
+
ensure
|
|
124
|
+
POOL.checkin(obj) if obj
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`checkout` returns `nil` if no pooled object becomes available before the configured timeout. `with` raises `Ratomic::Error` in that case.
|
|
129
|
+
|
|
130
|
+
### `Ratomic::Map`
|
|
131
|
+
|
|
132
|
+
A Ractor-safe map/hash structure:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
HASH = Ratomic::Map.new
|
|
136
|
+
HASH["mike"] = 123
|
|
137
|
+
HASH["mike"] # => 123
|
|
138
|
+
HASH.fetch_and_modify("mike") { |value| value + 1 }
|
|
139
|
+
HASH.length
|
|
140
|
+
HASH.empty?
|
|
141
|
+
HASH.clear
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `Ratomic::Queue`
|
|
145
|
+
|
|
146
|
+
A multi-producer, multi-consumer queue.
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
q = Ratomic::Queue.new(128)
|
|
150
|
+
|
|
151
|
+
q.push("hello")
|
|
152
|
+
q << "world"
|
|
153
|
+
|
|
154
|
+
q.size # => 2
|
|
155
|
+
q.empty? # => false
|
|
156
|
+
q.peek # => "hello"
|
|
157
|
+
item = q.pop # => "hello"
|
|
158
|
+
item = q.pop # => "world"
|
|
159
|
+
q.empty? # => true
|
|
160
|
+
```
|
|
161
|
+
The `.new(capacity)` method initializes the queue with a fixed-size buffer. The capacity must be greater than or equal to 1 and less than or equal to 2<sup>20</sup>.
|
|
162
|
+
|
|
163
|
+
Values that are not a power of two are rounded up to the nearest greater power of two, which enables efficient indexing and wrap-around calculations in the underlying buffer.
|
|
164
|
+
|
|
165
|
+
Since `Ratomic::Queue` is a concurrent queue, the `size`, `empty?`, and `peek` methods provide only a best-effort guess — the values they return might be stale or incorrect.
|
|
166
|
+
|
|
167
|
+
## Thanks
|
|
168
|
+
|
|
169
|
+
[Ilya Bylich](https://github.com/iliabylich) wrote and documented his original research at [Ruby, Ractors, and Lock-free Data Structures/](https://iliabylich.github.io/ruby-ractors-and-lock-free-data-structures/).
|
|
170
|
+
Thank you for your impressive work, Ilya!
|
|
171
|
+
|
|
172
|
+
This repo is further research into the usability and limitations of Ractor-friendly structures in Ruby code and gems.
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ratomic
|
|
4
|
+
# Ruby convenience methods for {Counter}.
|
|
5
|
+
module CounterMethods
|
|
6
|
+
# Read the current counter value.
|
|
7
|
+
#
|
|
8
|
+
# @return [Integer]
|
|
9
|
+
def value
|
|
10
|
+
read
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Coerce the counter to an Integer snapshot.
|
|
14
|
+
#
|
|
15
|
+
# @return [Integer]
|
|
16
|
+
def to_i
|
|
17
|
+
read
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Check whether the current counter value is zero.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def zero?
|
|
24
|
+
read.zero?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Increment the counter.
|
|
28
|
+
#
|
|
29
|
+
# @param amt [Integer] amount to add
|
|
30
|
+
# @raise [ArgumentError] if +amt+ is negative
|
|
31
|
+
# @return [void]
|
|
32
|
+
def inc(amt = 1)
|
|
33
|
+
raise ArgumentError, "amount must be positive: #{amt}" if amt.negative?
|
|
34
|
+
|
|
35
|
+
increment(amt)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Decrement the counter.
|
|
39
|
+
#
|
|
40
|
+
# @param amt [Integer] amount to subtract
|
|
41
|
+
# @raise [ArgumentError] if +amt+ is negative
|
|
42
|
+
# @return [void]
|
|
43
|
+
def dec(amt = 1)
|
|
44
|
+
raise ArgumentError, "amount must be positive: #{amt}" if amt.negative?
|
|
45
|
+
|
|
46
|
+
decrement(amt)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
Counter.prepend(CounterMethods)
|
|
51
|
+
end
|
data/lib/ratomic/map.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ratomic
|
|
4
|
+
# A Ractor-shareable concurrent map.
|
|
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.
|
|
9
|
+
#
|
|
10
|
+
# This is not a full Hash replacement. Iteration and arbitrary mutable object
|
|
11
|
+
# borrowing are intentionally absent.
|
|
12
|
+
#
|
|
13
|
+
# @example Store pipeline offsets
|
|
14
|
+
# OFFSETS = Ratomic::Map.new
|
|
15
|
+
# OFFSETS[:source_a] = 42
|
|
16
|
+
# OFFSETS[:source_a] # => 42
|
|
17
|
+
Map = ConcurrentHashMap
|
|
18
|
+
|
|
19
|
+
# Ruby convenience methods for {Map}.
|
|
20
|
+
module MapMethods
|
|
21
|
+
# Set a value for +key+.
|
|
22
|
+
#
|
|
23
|
+
# @param key [Object]
|
|
24
|
+
# @param value [Object]
|
|
25
|
+
# @return [void]
|
|
26
|
+
def []=(key, value)
|
|
27
|
+
set(key, value)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Read a value by +key+.
|
|
31
|
+
#
|
|
32
|
+
# Missing keys currently return nil, so storing nil is ambiguous.
|
|
33
|
+
#
|
|
34
|
+
# @param key [Object]
|
|
35
|
+
# @return [Object, nil]
|
|
36
|
+
def [](key)
|
|
37
|
+
get(key)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Alias for #size.
|
|
41
|
+
#
|
|
42
|
+
# @return [Integer]
|
|
43
|
+
def length
|
|
44
|
+
size
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Check whether the map currently has no entries.
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def empty?
|
|
51
|
+
size.zero?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
ConcurrentHashMap.prepend(MapMethods)
|
|
56
|
+
end
|
data/lib/ratomic/pool.rb
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ratomic
|
|
4
|
+
# A Ractor-safe ownership-transfer pool for mutable Ruby objects.
|
|
5
|
+
#
|
|
6
|
+
# Pool follows a Rust-inspired ownership-transfer model: a pooled object has
|
|
7
|
+
# one active owner at a time. #checkout moves ownership from the pool to the
|
|
8
|
+
# caller; #checkin moves ownership back to the pool. Ruby enforces stale
|
|
9
|
+
# caller references dynamically with Ractor::MovedError.
|
|
10
|
+
#
|
|
11
|
+
# This is ownership transfer, not borrowing. Pool never lends shared mutable
|
|
12
|
+
# references across Ractors.
|
|
13
|
+
#
|
|
14
|
+
# Pool uses a private coordinator Ractor and caller-owned Ractor::Port reply
|
|
15
|
+
# ports. Objects are moved to callers on checkout and moved back to the pool
|
|
16
|
+
# on checkin. This is intentionally different from sharing the same mutable
|
|
17
|
+
# object between Ractors: at any instant, exactly one Ractor owns a checked-out
|
|
18
|
+
# object.
|
|
19
|
+
#
|
|
20
|
+
# @example Reuse mutable buffers safely
|
|
21
|
+
# BUFFERS = Ratomic::Pool.new(4, 1.0) { [] }
|
|
22
|
+
# BUFFERS.with do |buffer|
|
|
23
|
+
# buffer.clear
|
|
24
|
+
# buffer << :change
|
|
25
|
+
# end
|
|
26
|
+
class Pool
|
|
27
|
+
# Create a pool and seed it with +size+ objects from the factory block.
|
|
28
|
+
#
|
|
29
|
+
# @param size [Integer] number of pooled objects
|
|
30
|
+
# @param timeout [Numeric, nil] checkout timeout in seconds, or nil to wait indefinitely
|
|
31
|
+
# @yieldreturn [Object] mutable object to store in the pool
|
|
32
|
+
# @raise [ArgumentError] if +size+ is not positive
|
|
33
|
+
# @raise [LocalJumpError] if no factory block is given
|
|
34
|
+
def initialize(size = 5, timeout = 1.0)
|
|
35
|
+
raise ArgumentError, "pool size must be positive" if size <= 0
|
|
36
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
37
|
+
|
|
38
|
+
@timeout = timeout&.to_f
|
|
39
|
+
@control = self.class.send(:new_control_ractor)
|
|
40
|
+
size.times { @control.send([:checkin, yield], move: true) }
|
|
41
|
+
freeze
|
|
42
|
+
Ractor.make_shareable(self)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Checkout one object from the pool.
|
|
46
|
+
#
|
|
47
|
+
# The returned object has been moved from the pool to the caller. The caller
|
|
48
|
+
# owns it until it is passed to #checkin.
|
|
49
|
+
#
|
|
50
|
+
# @return [Object, nil] pooled object, or nil after timeout
|
|
51
|
+
def checkout
|
|
52
|
+
reply = Ractor::Port.new
|
|
53
|
+
request_id = reply.object_id
|
|
54
|
+
@control << [:checkout, request_id, reply]
|
|
55
|
+
receive_checkout_reply(reply)
|
|
56
|
+
rescue Timeout::Error
|
|
57
|
+
nil
|
|
58
|
+
ensure
|
|
59
|
+
@control << [:cancel, request_id] if request_id
|
|
60
|
+
reply&.close unless reply&.closed?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Return an object to the pool.
|
|
64
|
+
#
|
|
65
|
+
# This moves ownership from the caller back to the pool. The caller must not
|
|
66
|
+
# use the object after calling this method; Ruby raises Ractor::MovedError
|
|
67
|
+
# for stale references.
|
|
68
|
+
#
|
|
69
|
+
# @param object [Object] previously checked-out pooled object
|
|
70
|
+
# @return [nil]
|
|
71
|
+
def checkin(object)
|
|
72
|
+
@control.send([:checkin, object], move: true)
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Checkout an object, yield it, then move it back to the pool.
|
|
77
|
+
#
|
|
78
|
+
# This is the preferred API because it guarantees checkin through an ensure
|
|
79
|
+
# block. If checkout times out, raises Ratomic::Error and does not yield.
|
|
80
|
+
#
|
|
81
|
+
# @yieldparam object [Object] checked-out pooled object
|
|
82
|
+
# @raise [Ratomic::Error] if checkout times out
|
|
83
|
+
# @return [Object] block return value
|
|
84
|
+
def with
|
|
85
|
+
object = checkout
|
|
86
|
+
raise Ratomic::Error, "pool checkout timeout" if object.nil?
|
|
87
|
+
|
|
88
|
+
yield object
|
|
89
|
+
ensure
|
|
90
|
+
checkin(object) unless object.nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.new_control_ractor
|
|
94
|
+
Ractor.new { Ratomic::Pool.send(:run_control_loop) }
|
|
95
|
+
end
|
|
96
|
+
private_class_method :new_control_ractor
|
|
97
|
+
|
|
98
|
+
def self.run_control_loop
|
|
99
|
+
available = []
|
|
100
|
+
waiting = {}
|
|
101
|
+
|
|
102
|
+
loop do
|
|
103
|
+
command, *args = Ractor.receive
|
|
104
|
+
handle_command(command, args, available, waiting)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
private_class_method :run_control_loop
|
|
108
|
+
|
|
109
|
+
def self.handle_command(command, args, available, waiting)
|
|
110
|
+
case command
|
|
111
|
+
when :checkout
|
|
112
|
+
handle_checkout(args, available, waiting)
|
|
113
|
+
when :checkin
|
|
114
|
+
handle_checkin(args.fetch(0), available, waiting)
|
|
115
|
+
when :cancel
|
|
116
|
+
waiting.delete(args.fetch(0))
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
private_class_method :handle_command
|
|
120
|
+
|
|
121
|
+
def self.handle_checkout(args, available, waiting)
|
|
122
|
+
request_id, reply = args
|
|
123
|
+
if (object = available.shift)
|
|
124
|
+
reply.send(object, move: true)
|
|
125
|
+
else
|
|
126
|
+
waiting[request_id] = reply
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
private_class_method :handle_checkout
|
|
130
|
+
|
|
131
|
+
def self.handle_checkin(object, available, waiting)
|
|
132
|
+
loop do
|
|
133
|
+
_request_id, reply = waiting.shift
|
|
134
|
+
return available << object if reply.nil?
|
|
135
|
+
|
|
136
|
+
begin
|
|
137
|
+
reply.send(object, move: true)
|
|
138
|
+
return
|
|
139
|
+
rescue Ractor::ClosedError
|
|
140
|
+
next
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
private_class_method :handle_checkin
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def receive_checkout_reply(reply)
|
|
149
|
+
return reply.receive unless @timeout
|
|
150
|
+
|
|
151
|
+
Timeout.timeout(@timeout) { reply.receive }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ratomic
|
|
4
|
+
# Ruby convenience methods for {Queue}.
|
|
5
|
+
module QueueMethods
|
|
6
|
+
# Push an item and return the queue for chaining.
|
|
7
|
+
#
|
|
8
|
+
# @param item [Object]
|
|
9
|
+
# @return [Ratomic::Queue]
|
|
10
|
+
def <<(item)
|
|
11
|
+
push(item)
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Queue.prepend(QueueMethods)
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ratomic
|
|
4
|
+
# Internal sentinel object for future Hash-like APIs that need to distinguish
|
|
5
|
+
# missing keys from explicit nil values.
|
|
6
|
+
class Undefined
|
|
7
|
+
# @return [String]
|
|
8
|
+
def inspect
|
|
9
|
+
"#<Undefined>"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Internal shareable missing-value sentinel.
|
|
14
|
+
UNDEFINED = Ractor.make_shareable(Undefined.new)
|
|
15
|
+
end
|
data/lib/ratomic.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
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
|
+
|
|
13
|
+
module Ratomic
|
|
14
|
+
# Base error for Ratomic-specific runtime failures.
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
end
|
data/ratomic.gemspec
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/ratomic/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "ratomic"
|
|
7
|
+
spec.version = Ratomic::VERSION
|
|
8
|
+
spec.authors = ["Mike Perham", "Ken C. Demanawa"]
|
|
9
|
+
spec.email = ["mike@perham.net"]
|
|
10
|
+
spec.metadata["maintainers"] = "Ken C. Demanawa"
|
|
11
|
+
|
|
12
|
+
spec.summary = "Mutable data structures for Ractors"
|
|
13
|
+
spec.description = spec.summary
|
|
14
|
+
spec.homepage = "https://github.com/mperham/ratomic"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
spec.required_ruby_version = ">= 4.0.0"
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/mperham/ratomic"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/mperham/ratomic"
|
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
22
|
+
|
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
25
|
+
spec.files = [
|
|
26
|
+
"Cargo.lock",
|
|
27
|
+
"Cargo.toml",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"LICENSE.txt",
|
|
30
|
+
"README.md",
|
|
31
|
+
"ratomic.gemspec"
|
|
32
|
+
] + Dir[
|
|
33
|
+
"lib/**/*.rb",
|
|
34
|
+
"ext/ratomic/Cargo.toml",
|
|
35
|
+
"ext/ratomic/build.rs",
|
|
36
|
+
"ext/ratomic/extconf.rb",
|
|
37
|
+
"ext/ratomic/src/**/*.rs"
|
|
38
|
+
]
|
|
39
|
+
spec.require_paths = ["lib"]
|
|
40
|
+
spec.extensions = ["ext/ratomic/extconf.rb"]
|
|
41
|
+
|
|
42
|
+
spec.add_dependency "rb_sys", "~> 0.9.128"
|
|
43
|
+
|
|
44
|
+
# For more information and examples about making a new gem, check out our
|
|
45
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
46
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ratomic
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: x86_64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Mike Perham
|
|
8
|
+
- Ken C. Demanawa
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2026-06-04 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Mutable data structures for Ractors
|
|
15
|
+
email:
|
|
16
|
+
- mike@perham.net
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/ratomic.rb
|
|
25
|
+
- lib/ratomic/3.0/ratomic.so
|
|
26
|
+
- lib/ratomic/3.1/ratomic.so
|
|
27
|
+
- lib/ratomic/3.2/ratomic.so
|
|
28
|
+
- lib/ratomic/3.3/ratomic.so
|
|
29
|
+
- lib/ratomic/3.4/ratomic.so
|
|
30
|
+
- lib/ratomic/4.0/ratomic.so
|
|
31
|
+
- lib/ratomic/counter.rb
|
|
32
|
+
- lib/ratomic/map.rb
|
|
33
|
+
- lib/ratomic/pool.rb
|
|
34
|
+
- lib/ratomic/queue.rb
|
|
35
|
+
- lib/ratomic/undefined.rb
|
|
36
|
+
- lib/ratomic/version.rb
|
|
37
|
+
- ratomic.gemspec
|
|
38
|
+
homepage: https://github.com/mperham/ratomic
|
|
39
|
+
licenses:
|
|
40
|
+
- MIT
|
|
41
|
+
metadata:
|
|
42
|
+
maintainers: Ken C. Demanawa
|
|
43
|
+
homepage_uri: https://github.com/mperham/ratomic
|
|
44
|
+
source_code_uri: https://github.com/mperham/ratomic
|
|
45
|
+
changelog_uri: https://github.com/mperham/ratomic
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
post_install_message:
|
|
48
|
+
rdoc_options: []
|
|
49
|
+
require_paths:
|
|
50
|
+
- lib
|
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: '3.0'
|
|
56
|
+
- - "<"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 4.1.dev
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 3.5.23
|
|
66
|
+
signing_key:
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: Mutable data structures for Ractors
|
|
69
|
+
test_files: []
|