ratomic 0.4.0 → 0.4.2
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 +40 -0
- data/README.md +19 -9
- data/ext/ratomic/src/hashmap.rs +25 -3
- data/ext/ratomic/src/lib.rs +26 -5
- data/lib/ratomic/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 764ab083a8815f4f2bd0958be83d5209b10b21d8765dc19720fedd6d042eec89
|
|
4
|
+
data.tar.gz: 5bf02090ace17e0ca172bcc9f9f2a56f79030f0a92ecf669adecba72aa2ec641
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7a3e86bfe551b27b28af6655bd7853c1be683d69e759d6b628388282e7443030fe8b56c7e036e1c25f8234de74052fc621747eb94484e84e979b771aaf7ebcc
|
|
7
|
+
data.tar.gz: 03e9c06d8c5771ab340e1219113b5f67c05da3e97c5ad2a5b7ddefb9e5b54103988eb6cb7c299e09a0808e27fec660b3eaea8a7a3fc200a5aa8dd84161faf484
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Prevent `Map#compute` from deadlocking when multiple Ruby threads contend for the same key.
|
|
8
|
+
|
|
9
|
+
## 0.4.1
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
- Document the `LocalPool` factory contract.
|
|
14
|
+
- Clarify ownership-transfer versus resource-locality semantics.
|
|
15
|
+
- Add guidance for designing Ractor-shareable factory objects.
|
|
16
|
+
- Add examples of correct and incorrect factory implementations.
|
|
17
|
+
- Improve `LocalPool` API documentation and YARD comments.
|
|
18
|
+
|
|
19
|
+
### Smoke Tests
|
|
20
|
+
|
|
21
|
+
- Add PostgreSQL smoke tests alongside Redis smoke tests.
|
|
22
|
+
- Consolidate smoke-test documentation.
|
|
23
|
+
- Add captured Redis and PostgreSQL validation snapshots.
|
|
24
|
+
- Document expected outcomes and interpretation of smoke-test runs.
|
|
25
|
+
|
|
26
|
+
### Validation
|
|
27
|
+
|
|
28
|
+
Redis and PostgreSQL smoke tests now demonstrate:
|
|
29
|
+
|
|
30
|
+
- successful Thread and Ractor execution
|
|
31
|
+
- no `Ractor::MovedError`
|
|
32
|
+
- no `Ractor::IsolationError`
|
|
33
|
+
- successful queue draining
|
|
34
|
+
- preservation of live resource ownership
|
|
35
|
+
- safe reuse of Redis clients and PostgreSQL connections
|
|
36
|
+
|
|
37
|
+
These tests validate that `LocalPool` is a general-purpose
|
|
38
|
+
resource-locality primitive for stateful network clients rather
|
|
39
|
+
than a Redis-specific abstraction.
|
|
40
|
+
|
|
3
41
|
## [0.4.0] - 2026-06-10
|
|
4
42
|
|
|
5
43
|
### Added
|
|
@@ -10,6 +48,8 @@
|
|
|
10
48
|
* Added RBS definitions for `LocalPool`.
|
|
11
49
|
* Expanded API documentation and usage examples.
|
|
12
50
|
|
|
51
|
+
|
|
52
|
+
|
|
13
53
|
### Design
|
|
14
54
|
|
|
15
55
|
`Ratomic::Pool` and `Ratomic::LocalPool` serve different ownership models:
|
data/README.md
CHANGED
|
@@ -42,8 +42,8 @@ RBS signatures are included under `sig/` for downstream type checking.
|
|
|
42
42
|
|
|
43
43
|
## Examples And Benchmarks
|
|
44
44
|
|
|
45
|
-
- [`
|
|
46
|
-
|
|
45
|
+
- [`smoke_tests`](./smoke_tests) contains local Redis and PostgreSQL scripts that
|
|
46
|
+
exercise `Ratomic::LocalPool` with real stateful clients under Thread and
|
|
47
47
|
Ractor workloads.
|
|
48
48
|
- [`pgoutput-parser`](https://github.com/kanutocd/pgoutput-parser#relation-metadata-tracking)
|
|
49
49
|
uses `Ratomic::Map` for relation metadata tracking in a real CDC pipeline
|
|
@@ -313,11 +313,12 @@ leave nested internal state unusable, producing errors such as
|
|
|
313
313
|
`LocalPool` avoids that class of bug by not moving live resources at all. Work
|
|
314
314
|
moves between Ractors. Live resources stay local.
|
|
315
315
|
|
|
316
|
-
#### Redis smoke-test
|
|
316
|
+
#### Redis and PostgreSQL smoke-test snapshots
|
|
317
317
|
|
|
318
|
-
The
|
|
318
|
+
The smoke tests live under [`smoke_tests/`](./smoke_tests/) and exercise `LocalPool` with real
|
|
319
|
+
Redis clients and PostgreSQL connections.
|
|
319
320
|
|
|
320
|
-
`basic_redis.rb` exercises repeated Redis operations from both Threads and
|
|
321
|
+
`redis/basic_redis.rb` exercises repeated Redis operations from both Threads and
|
|
321
322
|
Ractors:
|
|
322
323
|
|
|
323
324
|
```text
|
|
@@ -328,7 +329,7 @@ Ractor
|
|
|
328
329
|
[{"one" => 42419}, {"two" => 42186}, {"three" => 42400}, {"four" => 42206}, {"five" => 42568}]
|
|
329
330
|
```
|
|
330
331
|
|
|
331
|
-
`queue_redis.rb` exercises a producer/consumer Redis queue workload:
|
|
332
|
+
`redis/queue_redis.rb` exercises a producer/consumer Redis queue workload:
|
|
332
333
|
|
|
333
334
|
```text
|
|
334
335
|
[:start, Ractor, 2026-06-10 01:17:57.584727984 +0800]
|
|
@@ -338,15 +339,24 @@ Ractor
|
|
|
338
339
|
[:end, 2026-06-10 01:18:02.226310862 +0800]
|
|
339
340
|
```
|
|
340
341
|
|
|
341
|
-
|
|
342
|
+
`postgres/basic_postgres.rb` exercises repeated PostgreSQL upserts from both
|
|
343
|
+
Threads and Ractors. `postgres/queue_postgres.rb` exercises a table-backed
|
|
344
|
+
producer/consumer workload using `FOR UPDATE SKIP LOCKED`.
|
|
345
|
+
|
|
346
|
+
These numbers are smoke-test snapshots, not formal benchmark claims. The
|
|
342
347
|
important interpretation is:
|
|
343
348
|
|
|
344
349
|
- no `Ractor::MovedError`
|
|
345
350
|
- no `Ractor::IsolationError`
|
|
346
351
|
- no process crash
|
|
347
352
|
- all produced queue items were consumed
|
|
348
|
-
- Redis queues drained to zero
|
|
349
|
-
- live Redis clients remained owned by the Ractor
|
|
353
|
+
- Redis queues and PostgreSQL table queues drained to zero
|
|
354
|
+
- live Redis clients and PostgreSQL connections remained owned by the Ractor
|
|
355
|
+
that created them
|
|
356
|
+
|
|
357
|
+
Redis proves the network-client shape. PostgreSQL proves the database-connection
|
|
358
|
+
shape. Together they demonstrate that `LocalPool` is not Redis-specific; it is a
|
|
359
|
+
resource-locality primitive for live stateful clients.
|
|
350
360
|
|
|
351
361
|
#### Inception pool
|
|
352
362
|
|
data/ext/ratomic/src/hashmap.rs
CHANGED
|
@@ -54,6 +54,21 @@ impl MapStore {
|
|
|
54
54
|
self.map.len()
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
fn with_entry<F, R, W>(&self, key: VALUE, mut on_locked: W, f: F) -> R
|
|
58
|
+
where
|
|
59
|
+
F: FnOnce(Entry<'_, RubyHashEql, VALUE>) -> R,
|
|
60
|
+
W: FnMut(),
|
|
61
|
+
{
|
|
62
|
+
let mut f = Some(f);
|
|
63
|
+
loop {
|
|
64
|
+
if let Some(entry) = self.map.try_entry(RubyHashEql(key)) {
|
|
65
|
+
return f.take().expect("entry closure already used")(entry);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
on_locked();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
pub fn fetch_and_modify<F>(&self, key: VALUE, f: F)
|
|
58
73
|
where
|
|
59
74
|
F: FnOnce(VALUE) -> VALUE,
|
|
@@ -61,11 +76,18 @@ impl MapStore {
|
|
|
61
76
|
self.map.alter(&RubyHashEql(key), |_, value| f(value));
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
pub fn compute<F, E
|
|
79
|
+
pub fn compute<F, E, W>(
|
|
80
|
+
&self,
|
|
81
|
+
key: VALUE,
|
|
82
|
+
missing: VALUE,
|
|
83
|
+
on_locked: W,
|
|
84
|
+
f: F,
|
|
85
|
+
) -> Result<VALUE, E>
|
|
65
86
|
where
|
|
66
87
|
F: FnOnce(VALUE) -> Result<VALUE, E>,
|
|
88
|
+
W: FnMut(),
|
|
67
89
|
{
|
|
68
|
-
|
|
90
|
+
self.with_entry(key, on_locked, |entry| match entry {
|
|
69
91
|
Entry::Occupied(mut entry) => {
|
|
70
92
|
let new_value = f(*entry.get())?;
|
|
71
93
|
entry.insert(new_value);
|
|
@@ -76,7 +98,7 @@ impl MapStore {
|
|
|
76
98
|
entry.insert(new_value);
|
|
77
99
|
Ok(new_value)
|
|
78
100
|
}
|
|
79
|
-
}
|
|
101
|
+
})
|
|
80
102
|
}
|
|
81
103
|
|
|
82
104
|
pub fn update<F, E>(&self, key: VALUE, f: F) -> Result<VALUE, E>
|
data/ext/ratomic/src/lib.rs
CHANGED
|
@@ -18,7 +18,7 @@ use magnus::{
|
|
|
18
18
|
use mpmc_queue::MpmcQueue;
|
|
19
19
|
use parking_lot::Mutex;
|
|
20
20
|
use rb_sys::{rb_ext_ractor_safe, rb_thread_call_without_gvl, ruby_special_consts, VALUE};
|
|
21
|
-
use std::{ffi::c_void, mem::transmute};
|
|
21
|
+
use std::{ffi::c_void, mem::transmute, time::Duration};
|
|
22
22
|
|
|
23
23
|
fn value_to_raw(value: Value) -> VALUE {
|
|
24
24
|
unsafe { transmute::<Value, VALUE>(value) }
|
|
@@ -38,6 +38,22 @@ fn make_shareable(ruby: &Ruby, value: Value) -> Result<Value, Error> {
|
|
|
38
38
|
ractor.funcall("make_shareable", (value,))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
unsafe extern "C" fn sleep_briefly_without_gvl(_: *mut c_void) -> *mut c_void {
|
|
42
|
+
std::thread::sleep(Duration::from_millis(1));
|
|
43
|
+
std::ptr::null_mut()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn wait_for_entry_without_gvl() {
|
|
47
|
+
unsafe {
|
|
48
|
+
rb_thread_call_without_gvl(
|
|
49
|
+
Some(sleep_briefly_without_gvl),
|
|
50
|
+
std::ptr::null_mut(),
|
|
51
|
+
None,
|
|
52
|
+
std::ptr::null_mut(),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
41
57
|
struct Counter(AtomicCounter);
|
|
42
58
|
|
|
43
59
|
impl Counter {
|
|
@@ -158,10 +174,15 @@ impl HashMap {
|
|
|
158
174
|
}
|
|
159
175
|
|
|
160
176
|
let proc = ruby.block_proc()?;
|
|
161
|
-
let raw = rb_self.0.compute(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
177
|
+
let raw = rb_self.0.compute(
|
|
178
|
+
value_to_raw(key),
|
|
179
|
+
qnil_raw(),
|
|
180
|
+
wait_for_entry_without_gvl,
|
|
181
|
+
|value| {
|
|
182
|
+
proc.call::<_, Value>((unsafe { value_from_raw(value) },))
|
|
183
|
+
.map(value_to_raw)
|
|
184
|
+
},
|
|
185
|
+
)?;
|
|
165
186
|
|
|
166
187
|
Ok(unsafe { value_from_raw(raw) }.into_value_with(ruby))
|
|
167
188
|
}
|
data/lib/ratomic/version.rb
CHANGED
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.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Perham
|
|
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '0'
|
|
83
83
|
requirements: []
|
|
84
|
-
rubygems_version: 4.0.
|
|
84
|
+
rubygems_version: 4.0.16
|
|
85
85
|
specification_version: 4
|
|
86
86
|
summary: Ractor-safe concurrent data structures for Ruby
|
|
87
87
|
test_files: []
|