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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +165 -98
- data/ext/ratomic/src/hashmap.rs +79 -3
- data/ext/ratomic/src/lib.rs +154 -25
- data/lib/ratomic/counter.rb +35 -29
- data/lib/ratomic/map.rb +295 -17
- data/lib/ratomic/pool.rb +10 -8
- data/lib/ratomic/queue.rb +60 -6
- data/lib/ratomic/undefined.rb +3 -1
- data/lib/ratomic/version.rb +2 -1
- data/lib/ratomic.rb +16 -11
- data/ratomic.gemspec +6 -5
- metadata +11 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bdf323b8b7b61fa10e96f376cf5cf5001b5df0c5155e0a2ab815a86561c2245d
|
|
4
|
+
data.tar.gz: c5d6536e0ef372bde684f19ca4af1174d1f7ab6a2143270bb884c674ab76fba2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 268606f63d6868b2f1c02421ad27e09d8344c2b404653d2e2e43376db814f9deaafdd608bce146129addd72ba43fc4b534a4a1aca17851c9fec35aad8a781400
|
|
7
|
+
data.tar.gz: fc604b15381e5ef3bd9a48c4294eefb4dcdfc5c2d6dbdf526ecfe621ce9adcad26db861eba41a33cf1d1dd7388696ea6d2f959dd2992cb77e7f13e108e6f9de0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.1] - 2026-06-05
|
|
4
|
+
|
|
5
|
+
- Realign `Counter`, `Map`, and `Queue` return semantics with Ruby conventions.
|
|
6
|
+
- Expand YARD comments across the public API to keep full documentation coverage.
|
|
7
|
+
- Move the top-level module docs to the gem entrypoint for a cleaner load path.
|
|
8
|
+
|
|
9
|
+
## [0.3.0] - 2026-06-05
|
|
10
|
+
|
|
11
|
+
- Promote the DashMap-backed `Ratomic::Map` API as the primary concurrent Hash primitive.
|
|
12
|
+
- Add `Ratomic::Map#key?`, `#include?`, `#member?`, and `#delete`.
|
|
13
|
+
- Add `Ratomic::Map#fetch`, `#compute`, `#fetch_or_store`, and `#upsert` for atomic per-key workflows.
|
|
14
|
+
- Add `Ratomic::Map#increment`, `#decrement`, `#append`, and `#add_to_set` convenience methods for shared counters and bucket-style values.
|
|
15
|
+
- Improve API documentation for `Counter`, `Map`, `Queue`, and `Pool`.
|
|
16
|
+
- Add GitHub Pages deployment for generated YARD API documentation.
|
|
17
|
+
- Add Redis POC scripts for exercising `Ratomic::Map`, `Ratomic::Counter`, and `Ratomic::Pool` under Thread and Ractor workloads.
|
|
18
|
+
- Remove `Counter#inc` and `Counter#dec` in favor of the native `#increment` and `#decrement` methods.
|
|
19
|
+
|
|
3
20
|
## [0.2.1] - 2026-06-04
|
|
4
21
|
|
|
5
22
|
- Fix `Ratomic::Queue` slot indexing for non-power-of-two capacities.
|
data/README.md
CHANGED
|
@@ -2,180 +2,247 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/ratomic)
|
|
4
4
|
[](https://github.com/mperham/ratomic/actions)
|
|
5
|
-
[](https://codecov.io/gh/mperham/ratomic)
|
|
6
5
|
[](https://www.ruby-lang.org/en/)
|
|
7
6
|
[](https://opensource.org/licenses/MIT)
|
|
8
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.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
This allows Ruby code to scale beyond the infamous GVL.
|
|
10
|
+
## Project Direction
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
Ratomic focuses on practical Ractor-safe primitives with a small API surface, clear
|
|
13
|
+
ownership semantics, and honest documentation about sharp edges.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
> If you don't know Rust or C, consider this a challenge to learn and solve.
|
|
18
|
-
> Read through the [issues](//github.com/mperham/ratomic/issues) to find work that sounds interesting to you.
|
|
15
|
+
`Ratomic::Map` is the current priority: a Ruby-facing concurrent Hash powered by
|
|
16
|
+
DashMap, with atomic per-key operations designed for real Ractor workloads.
|
|
19
17
|
|
|
20
|
-
##
|
|
18
|
+
## Requirements
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
- Ruby 4.0 or newer
|
|
21
|
+
- Bundler
|
|
22
|
+
- Rust toolchain when building the native extension from source
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add Ratomic to your application's Gemfile:
|
|
25
27
|
|
|
26
28
|
```bash
|
|
27
|
-
|
|
29
|
+
bundle add ratomic
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
The test suite writes a SimpleCov report to `coverage/index.html` so you can see which Ruby wrapper paths are covered.
|
|
32
|
+
Then require it from Ruby:
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
```ruby
|
|
35
|
+
require "ratomic"
|
|
36
|
+
```
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
## Documentation
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
bundle add ratomic
|
|
39
|
-
```
|
|
40
|
+
API documentation is published to GitHub Pages:
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
- [mperham.github.io/ratomic](https://mperham.github.io/ratomic/)
|
|
43
|
+
|
|
44
|
+
## Examples And Benchmarks
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
- [`redis_poc`](./redis_poc) contains local Redis scripts that exercise
|
|
47
|
+
`Ratomic::Map`, `Ratomic::Counter`, and `Ratomic::Pool` under Thread and
|
|
48
|
+
Ractor workloads.
|
|
49
|
+
- The [`cdc-parallel` Ratomic benchmark][cdc-parallel-ratomic] demonstrates
|
|
50
|
+
Ractor workers updating shared CDC processing metrics through `Ratomic::Map`
|
|
51
|
+
and `Ratomic::Counter`.
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
## Usage
|
|
47
54
|
|
|
48
|
-
Ratomic
|
|
55
|
+
Ratomic provides two safety models:
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
- `Counter`, `Map`, and `Queue` are shared concurrent structures.
|
|
58
|
+
- `Pool` transfers ownership of mutable objects between Ractors.
|
|
52
59
|
|
|
53
|
-
That distinction matters. A mutable pooled object is not shared by multiple Ractors
|
|
60
|
+
That distinction matters. A mutable pooled object is not shared by multiple Ractors
|
|
61
|
+
at the same time. It is moved to the caller on checkout and moved back to the pool
|
|
62
|
+
on checkin.
|
|
63
|
+
|
|
64
|
+
These structures are designed for use as class-level constants so they can be
|
|
65
|
+
shared by many Ractors.
|
|
54
66
|
|
|
55
67
|
### `Ratomic::Counter`
|
|
56
68
|
|
|
69
|
+
`Ratomic::Counter` is a Ractor-shareable atomic counter.
|
|
70
|
+
|
|
57
71
|
```ruby
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
counter = Ratomic::Counter.new
|
|
73
|
+
|
|
74
|
+
counter.read # => 0
|
|
75
|
+
counter.increment(1)
|
|
76
|
+
counter.increment(5)
|
|
77
|
+
counter.decrement(1)
|
|
78
|
+
counter.decrement(1)
|
|
79
|
+
|
|
80
|
+
counter.read # => 4
|
|
81
|
+
counter.to_i # => 4
|
|
82
|
+
counter.zero? # => false
|
|
67
83
|
```
|
|
68
84
|
|
|
69
|
-
### `Ratomic::
|
|
85
|
+
### `Ratomic::Map`
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
`Ratomic::Map` is a Ractor-safe concurrent Hash backed by Rust's DashMap. It is
|
|
88
|
+
not a full `Hash` replacement; iteration and arbitrary mutable object borrowing
|
|
89
|
+
are intentionally absent.
|
|
72
90
|
|
|
73
91
|
```ruby
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
OFFSETS = Ratomic::Map.new
|
|
93
|
+
|
|
94
|
+
OFFSETS["mike"] = 123
|
|
95
|
+
OFFSETS["mike"] # => 123
|
|
96
|
+
OFFSETS.key?("mike") # => true
|
|
97
|
+
OFFSETS.fetch("missing", 0) # => 0
|
|
98
|
+
|
|
99
|
+
OFFSETS.fetch_or_store("count") { 0 } # => 0
|
|
100
|
+
OFFSETS.compute("mike") { |value| value + 1 } # => 124
|
|
101
|
+
OFFSETS.upsert("mike", 1) { |value| value + 1 } # => 125
|
|
102
|
+
OFFSETS.fetch_and_modify("mike") { |value| value + 1 }
|
|
103
|
+
|
|
104
|
+
OFFSETS.delete("mike") # => 126
|
|
105
|
+
OFFSETS.length
|
|
106
|
+
OFFSETS.empty?
|
|
107
|
+
OFFSETS.clear
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`Map` also includes atomic convenience methods for common bucket patterns:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
counts = Ratomic::Map.new
|
|
114
|
+
counts.increment("jobs") # => 1
|
|
115
|
+
counts.decrement("jobs") # => 0
|
|
116
|
+
|
|
117
|
+
groups = Ratomic::Map.new
|
|
118
|
+
groups.append("jobs", "import") # => ["import"]
|
|
119
|
+
groups.add_to_set("workers", "alpha") # => #<Set: {"alpha"}>
|
|
79
120
|
```
|
|
80
121
|
|
|
81
|
-
|
|
122
|
+
### `Ratomic::Queue`
|
|
123
|
+
|
|
124
|
+
`Ratomic::Queue` is a Ractor-shareable multi-producer, multi-consumer queue.
|
|
82
125
|
|
|
83
|
-
|
|
126
|
+
```ruby
|
|
127
|
+
queue = Ratomic::Queue.new(128)
|
|
84
128
|
|
|
85
|
-
|
|
129
|
+
queue.push("hello")
|
|
130
|
+
queue << "world"
|
|
86
131
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
132
|
+
queue.size # => 2
|
|
133
|
+
queue.empty? # => false
|
|
134
|
+
queue.peek # => "hello"
|
|
135
|
+
queue.pop # => "hello"
|
|
136
|
+
queue.pop # => "world"
|
|
137
|
+
queue.empty? # => true
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `.new(capacity)` method initializes the queue with a fixed-size buffer.
|
|
141
|
+
Capacity must be at least `1` and at most `2**20`. Non-power-of-two capacities
|
|
142
|
+
are supported exactly.
|
|
143
|
+
|
|
144
|
+
Since `Ratomic::Queue` is concurrent, `size`, `empty?`, and `peek` are
|
|
145
|
+
moment-in-time observations. Their results may already be stale by the time your
|
|
146
|
+
code uses them.
|
|
147
|
+
|
|
148
|
+
### `Ratomic::Pool`
|
|
149
|
+
|
|
150
|
+
`Ratomic::Pool` is a Ractor-safe ownership-transfer pool for mutable Ruby objects.
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
BUFFERS = Ratomic::Pool.new(5, 1.0) { [] }
|
|
154
|
+
|
|
155
|
+
BUFFERS.with do |buffer|
|
|
156
|
+
buffer.clear
|
|
157
|
+
buffer << "work"
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`Pool` uses Ruby 4's `Ractor::Port` and `move: true` semantics so only one
|
|
162
|
+
Ractor owns a checked-out object at a time.
|
|
91
163
|
|
|
92
164
|
When an object is checked out:
|
|
93
165
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
166
|
+
- the pool moves the object to the caller
|
|
167
|
+
- the caller can mutate the object while it owns it
|
|
168
|
+
- the pool cannot hand that object to another Ractor until it is checked in
|
|
97
169
|
|
|
98
170
|
When an object is checked in:
|
|
99
171
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
172
|
+
- ownership moves back to the pool
|
|
173
|
+
- stale references held by the caller become moved objects
|
|
174
|
+
- using those stale references raises `Ractor::MovedError`
|
|
103
175
|
|
|
104
|
-
This means incorrect usage fails at the Ruby object-ownership boundary rather
|
|
176
|
+
This means incorrect usage fails at the Ruby object-ownership boundary rather
|
|
177
|
+
than allowing two Ractors to mutate the same object concurrently.
|
|
105
178
|
|
|
106
179
|
```ruby
|
|
107
180
|
outside = nil
|
|
108
181
|
|
|
109
|
-
|
|
110
|
-
outside =
|
|
111
|
-
|
|
182
|
+
BUFFERS.with do |buffer|
|
|
183
|
+
outside = buffer
|
|
184
|
+
buffer << "inside"
|
|
112
185
|
end
|
|
113
186
|
|
|
114
187
|
outside << "outside"
|
|
115
188
|
# raises Ractor::MovedError
|
|
116
189
|
```
|
|
117
190
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Manual checkout/checkin is also supported:
|
|
191
|
+
Manual checkout and checkin are also supported:
|
|
121
192
|
|
|
122
193
|
```ruby
|
|
123
|
-
|
|
124
|
-
raise "pool checkout timeout" if
|
|
194
|
+
buffer = BUFFERS.checkout
|
|
195
|
+
raise "pool checkout timeout" if buffer.nil?
|
|
125
196
|
|
|
126
197
|
begin
|
|
127
|
-
|
|
198
|
+
buffer << "manual work"
|
|
128
199
|
ensure
|
|
129
|
-
|
|
200
|
+
BUFFERS.checkin(buffer) if buffer
|
|
130
201
|
end
|
|
131
202
|
```
|
|
132
203
|
|
|
133
|
-
`checkout` returns `nil` if no pooled object becomes available before the
|
|
204
|
+
`checkout` returns `nil` if no pooled object becomes available before the
|
|
205
|
+
configured timeout. `with` raises `Ratomic::Error` in that case.
|
|
134
206
|
|
|
135
|
-
|
|
207
|
+
`Pool` uses ownership transfer, not Rust's full borrow checker:
|
|
136
208
|
|
|
137
|
-
|
|
209
|
+
- the Rust owner maps to the Ractor that currently checked out the pooled object
|
|
210
|
+
- the Rust move maps to `Ractor::Port#send(..., move: true)`
|
|
211
|
+
- Rust's "cannot use after move" rule maps to Ruby raising `Ractor::MovedError`
|
|
212
|
+
- borrowing is not modeled; `Pool` transfers ownership instead of lending references
|
|
138
213
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
HASH["mike"] # => 123
|
|
143
|
-
HASH.fetch_and_modify("mike") { |value| value + 1 }
|
|
144
|
-
HASH.length
|
|
145
|
-
HASH.empty?
|
|
146
|
-
HASH.clear
|
|
147
|
-
```
|
|
214
|
+
This design addresses [issue #5](https://github.com/mperham/ratomic/issues/5),
|
|
215
|
+
where using a pooled object after `with` could lead to memory corruption or a
|
|
216
|
+
process crash.
|
|
148
217
|
|
|
149
|
-
|
|
218
|
+
The lower-level `Ratomic::FixedSizeObjectPool` native class may still exist, but
|
|
219
|
+
`Ratomic::Pool` does not inherit from it. The public `Pool` API is implemented
|
|
220
|
+
in Ruby so it can use Ruby's Ractor ownership primitives directly.
|
|
150
221
|
|
|
151
|
-
|
|
222
|
+
## Contributing
|
|
152
223
|
|
|
153
|
-
|
|
154
|
-
q = Ratomic::Queue.new(128)
|
|
224
|
+
Please read the [Code of Conduct](./CODE_OF_CONDUCT.md) before contributing.
|
|
155
225
|
|
|
156
|
-
|
|
157
|
-
q << "world"
|
|
226
|
+
After changing code, run:
|
|
158
227
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
q.peek # => "hello"
|
|
162
|
-
item = q.pop # => "hello"
|
|
163
|
-
item = q.pop # => "world"
|
|
164
|
-
q.empty? # => true
|
|
228
|
+
```bash
|
|
229
|
+
rake
|
|
165
230
|
```
|
|
166
|
-
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>.
|
|
167
|
-
|
|
168
|
-
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.
|
|
169
231
|
|
|
170
|
-
|
|
232
|
+
This compiles the Rust code and runs the test suite. The test suite writes a
|
|
233
|
+
SimpleCov report to `coverage/index.html` for the Ruby wrapper paths.
|
|
171
234
|
|
|
172
235
|
## Thanks
|
|
173
236
|
|
|
174
|
-
[Ilya Bylich](https://github.com/iliabylich) wrote and documented his original
|
|
175
|
-
|
|
237
|
+
[Ilya Bylich](https://github.com/iliabylich) wrote and documented his original
|
|
238
|
+
research at [Ruby, Ractors, and Lock-free Data Structures][ractor-research].
|
|
176
239
|
|
|
177
|
-
This repo
|
|
240
|
+
This repo continues that research into the usability and limitations of
|
|
241
|
+
Ractor-friendly structures in Ruby code and gems.
|
|
178
242
|
|
|
179
243
|
## License
|
|
180
244
|
|
|
181
245
|
[MIT License](https://opensource.org/licenses/MIT).
|
|
246
|
+
|
|
247
|
+
[cdc-parallel-ratomic]: https://github.com/kanutocd/cdc-parallel/blob/main/benchmark/RATOMIC.md
|
|
248
|
+
[ractor-research]: https://iliabylich.github.io/ruby-ractors-and-lock-free-data-structures/
|
data/ext/ratomic/src/hashmap.rs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use dashmap::mapref::entry::Entry;
|
|
1
2
|
use rb_sys::{rb_eql, rb_hash, VALUE};
|
|
2
3
|
|
|
3
4
|
#[derive(Debug)]
|
|
@@ -18,11 +19,11 @@ impl std::hash::Hash for RubyHashEql {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
pub struct
|
|
22
|
+
pub struct MapStore {
|
|
22
23
|
map: dashmap::DashMap<RubyHashEql, VALUE>,
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
impl
|
|
26
|
+
impl MapStore {
|
|
26
27
|
pub fn new() -> Self {
|
|
27
28
|
Self {
|
|
28
29
|
map: dashmap::DashMap::new(),
|
|
@@ -33,10 +34,18 @@ impl ConcurrentHashMap {
|
|
|
33
34
|
self.map.get(&RubyHashEql(key)).map(|v| *v)
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
pub fn contains_key(&self, key: VALUE) -> bool {
|
|
38
|
+
self.map.contains_key(&RubyHashEql(key))
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
pub fn set(&self, key: VALUE, value: VALUE) {
|
|
37
42
|
self.map.insert(RubyHashEql(key), value);
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
pub fn delete(&self, key: VALUE) -> Option<VALUE> {
|
|
46
|
+
self.map.remove(&RubyHashEql(key)).map(|(_, value)| value)
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
pub fn clear(&self) {
|
|
41
50
|
self.map.clear()
|
|
42
51
|
}
|
|
@@ -52,6 +61,73 @@ impl ConcurrentHashMap {
|
|
|
52
61
|
self.map.alter(&RubyHashEql(key), |_, value| f(value));
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
pub fn compute<F, E>(&self, key: VALUE, missing: VALUE, f: F) -> Result<VALUE, E>
|
|
65
|
+
where
|
|
66
|
+
F: FnOnce(VALUE) -> Result<VALUE, E>,
|
|
67
|
+
{
|
|
68
|
+
match self.map.entry(RubyHashEql(key)) {
|
|
69
|
+
Entry::Occupied(mut entry) => {
|
|
70
|
+
let new_value = f(*entry.get())?;
|
|
71
|
+
entry.insert(new_value);
|
|
72
|
+
Ok(new_value)
|
|
73
|
+
}
|
|
74
|
+
Entry::Vacant(entry) => {
|
|
75
|
+
let new_value = f(missing)?;
|
|
76
|
+
entry.insert(new_value);
|
|
77
|
+
Ok(new_value)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn update<F, E>(&self, key: VALUE, f: F) -> Result<VALUE, E>
|
|
83
|
+
where
|
|
84
|
+
F: FnOnce(Option<VALUE>) -> Result<VALUE, E>,
|
|
85
|
+
{
|
|
86
|
+
match self.map.entry(RubyHashEql(key)) {
|
|
87
|
+
Entry::Occupied(mut entry) => {
|
|
88
|
+
let new_value = f(Some(*entry.get()))?;
|
|
89
|
+
entry.insert(new_value);
|
|
90
|
+
Ok(new_value)
|
|
91
|
+
}
|
|
92
|
+
Entry::Vacant(entry) => {
|
|
93
|
+
let new_value = f(None)?;
|
|
94
|
+
entry.insert(new_value);
|
|
95
|
+
Ok(new_value)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub fn fetch_or_store<F, E>(&self, key: VALUE, f: F) -> Result<VALUE, E>
|
|
101
|
+
where
|
|
102
|
+
F: FnOnce() -> Result<VALUE, E>,
|
|
103
|
+
{
|
|
104
|
+
match self.map.entry(RubyHashEql(key)) {
|
|
105
|
+
Entry::Occupied(entry) => Ok(*entry.get()),
|
|
106
|
+
Entry::Vacant(entry) => {
|
|
107
|
+
let value = f()?;
|
|
108
|
+
entry.insert(value);
|
|
109
|
+
Ok(value)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
pub fn upsert<F, E>(&self, key: VALUE, initial: VALUE, f: F) -> Result<VALUE, E>
|
|
115
|
+
where
|
|
116
|
+
F: FnOnce(VALUE) -> Result<VALUE, E>,
|
|
117
|
+
{
|
|
118
|
+
match self.map.entry(RubyHashEql(key)) {
|
|
119
|
+
Entry::Occupied(mut entry) => {
|
|
120
|
+
let new_value = f(*entry.get())?;
|
|
121
|
+
entry.insert(new_value);
|
|
122
|
+
Ok(new_value)
|
|
123
|
+
}
|
|
124
|
+
Entry::Vacant(entry) => {
|
|
125
|
+
entry.insert(initial);
|
|
126
|
+
Ok(initial)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
55
131
|
pub fn mark<F>(&self, f: F)
|
|
56
132
|
where
|
|
57
133
|
F: Fn(VALUE),
|
|
@@ -63,7 +139,7 @@ impl ConcurrentHashMap {
|
|
|
63
139
|
}
|
|
64
140
|
}
|
|
65
141
|
|
|
66
|
-
impl Default for
|
|
142
|
+
impl Default for MapStore {
|
|
67
143
|
fn default() -> Self {
|
|
68
144
|
Self::new()
|
|
69
145
|
}
|