ratomic 0.1.0 → 0.2.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 +21 -0
- data/Cargo.lock +381 -0
- data/Cargo.toml +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +181 -0
- data/ext/ratomic/Cargo.toml +22 -0
- data/ext/ratomic/build.rs +3 -0
- data/ext/ratomic/extconf.rb +2 -12
- data/ext/ratomic/src/counter.rs +26 -0
- data/ext/ratomic/src/fixed_size_object_pool.rs +52 -0
- data/ext/ratomic/src/hashmap.rs +70 -0
- data/ext/ratomic/src/lib.rs +412 -0
- data/ext/ratomic/src/mpmc_queue.rs +164 -0
- data/ext/ratomic/src/sem.rs +72 -0
- data/lib/ratomic/counter.rb +51 -0
- data/lib/ratomic/map.rb +56 -0
- data/lib/ratomic/pool.rb +170 -0
- data/lib/ratomic/queue.rb +17 -0
- data/lib/ratomic/undefined.rb +15 -0
- data/lib/ratomic/version.rb +1 -1
- data/lib/ratomic.rb +8 -60
- data/ratomic.gemspec +19 -13
- metadata +41 -23
- data/ext/ratomic/counter.h +0 -45
- data/ext/ratomic/fixed-size-object-pool.h +0 -76
- data/ext/ratomic/hashmap.h +0 -74
- data/ext/ratomic/mpmc-queue.h +0 -68
- data/ext/ratomic/ratomic.c +0 -16
- data/lib/ratomic/ratomic.bundle +0 -0
- data/rs/Cargo.lock +0 -196
- data/rs/Cargo.toml +0 -26
- data/rs/cbindgen.toml +0 -12
- data/rs/rust-atomics.h +0 -89
- data/rs/src/bin/mpmc_queue.rs +0 -89
- data/rs/src/counter.rs +0 -57
- data/rs/src/fixed_size_object_pool.rs +0 -120
- data/rs/src/hashmap.rs +0 -129
- data/rs/src/lib.rs +0 -23
- data/rs/src/mpmc_queue.rs +0 -231
- data/rs/src/sem.rs +0 -75
- /data/{rs → ext/ratomic}/src/gc_guard.rs +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
2
|
+
|
|
3
|
+
#[derive(Debug)]
|
|
4
|
+
pub struct AtomicCounter {
|
|
5
|
+
value: AtomicU64,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
impl AtomicCounter {
|
|
9
|
+
pub fn new(n: u64) -> Self {
|
|
10
|
+
Self {
|
|
11
|
+
value: AtomicU64::new(n),
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn inc(&self, amt: u64) {
|
|
16
|
+
self.value.fetch_add(amt, Ordering::Relaxed);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub fn dec(&self, amt: u64) {
|
|
20
|
+
self.value.fetch_sub(amt, Ordering::Relaxed);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub fn read(&self) -> u64 {
|
|
24
|
+
self.value.load(Ordering::Relaxed)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
use crossbeam_channel::{Receiver, Sender};
|
|
2
|
+
use rb_sys::VALUE;
|
|
3
|
+
use std::time::Duration;
|
|
4
|
+
|
|
5
|
+
pub struct FixedSizeObjectPool {
|
|
6
|
+
pool: Vec<VALUE>,
|
|
7
|
+
tx: Sender<usize>,
|
|
8
|
+
rx: Receiver<usize>,
|
|
9
|
+
timeout: Duration,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub struct PooledItem {
|
|
13
|
+
pub idx: usize,
|
|
14
|
+
pub rbobj: VALUE,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl FixedSizeObjectPool {
|
|
18
|
+
pub fn new(pool: Vec<VALUE>, timeout_in_ms: u64) -> Self {
|
|
19
|
+
let (tx, rx) = crossbeam_channel::unbounded();
|
|
20
|
+
for idx in 0..pool.len() {
|
|
21
|
+
tx.send(idx).unwrap();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Self {
|
|
25
|
+
pool,
|
|
26
|
+
tx,
|
|
27
|
+
rx,
|
|
28
|
+
timeout: Duration::from_millis(timeout_in_ms),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn mark<F>(&self, f: F)
|
|
33
|
+
where
|
|
34
|
+
F: Fn(VALUE),
|
|
35
|
+
{
|
|
36
|
+
for item in self.pool.iter() {
|
|
37
|
+
f(*item);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn checkout(&self) -> Option<PooledItem> {
|
|
42
|
+
let idx = self.rx.recv_timeout(self.timeout).ok()?;
|
|
43
|
+
Some(PooledItem {
|
|
44
|
+
idx,
|
|
45
|
+
rbobj: self.pool[idx],
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn checkin(&self, idx: usize) {
|
|
50
|
+
self.tx.send(idx).unwrap();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
use rb_sys::{rb_eql, rb_hash, VALUE};
|
|
2
|
+
|
|
3
|
+
#[derive(Debug)]
|
|
4
|
+
struct RubyHashEql(VALUE);
|
|
5
|
+
|
|
6
|
+
impl PartialEq for RubyHashEql {
|
|
7
|
+
fn eq(&self, other: &Self) -> bool {
|
|
8
|
+
unsafe { rb_eql(self.0, other.0) != 0 }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl Eq for RubyHashEql {}
|
|
13
|
+
|
|
14
|
+
impl std::hash::Hash for RubyHashEql {
|
|
15
|
+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
16
|
+
let ruby_hash = unsafe { rb_hash(self.0) };
|
|
17
|
+
ruby_hash.hash(state);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pub struct ConcurrentHashMap {
|
|
22
|
+
map: dashmap::DashMap<RubyHashEql, VALUE>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl ConcurrentHashMap {
|
|
26
|
+
pub fn new() -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
map: dashmap::DashMap::new(),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pub fn get(&self, key: VALUE) -> Option<VALUE> {
|
|
33
|
+
self.map.get(&RubyHashEql(key)).map(|v| *v)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub fn set(&self, key: VALUE, value: VALUE) {
|
|
37
|
+
self.map.insert(RubyHashEql(key), value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn clear(&self) {
|
|
41
|
+
self.map.clear()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn size(&self) -> usize {
|
|
45
|
+
self.map.len()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn fetch_and_modify<F>(&self, key: VALUE, f: F)
|
|
49
|
+
where
|
|
50
|
+
F: FnOnce(VALUE) -> VALUE,
|
|
51
|
+
{
|
|
52
|
+
self.map.alter(&RubyHashEql(key), |_, value| f(value));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub fn mark<F>(&self, f: F)
|
|
56
|
+
where
|
|
57
|
+
F: Fn(VALUE),
|
|
58
|
+
{
|
|
59
|
+
for pair in self.map.iter() {
|
|
60
|
+
f(pair.key().0);
|
|
61
|
+
f(*pair.value());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl Default for ConcurrentHashMap {
|
|
67
|
+
fn default() -> Self {
|
|
68
|
+
Self::new()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
mod counter;
|
|
2
|
+
mod fixed_size_object_pool;
|
|
3
|
+
mod gc_guard;
|
|
4
|
+
mod hashmap;
|
|
5
|
+
mod mpmc_queue;
|
|
6
|
+
mod sem;
|
|
7
|
+
|
|
8
|
+
use counter::AtomicCounter;
|
|
9
|
+
use fixed_size_object_pool::FixedSizeObjectPool;
|
|
10
|
+
use hashmap::ConcurrentHashMap;
|
|
11
|
+
use magnus::{
|
|
12
|
+
data_type_builder, method, IntoValue,
|
|
13
|
+
prelude::*,
|
|
14
|
+
typed_data::{DataType, DataTypeFunctions},
|
|
15
|
+
value::Lazy,
|
|
16
|
+
Error, RClass, Ruby, TryConvert, TypedData, Value,
|
|
17
|
+
};
|
|
18
|
+
use mpmc_queue::MpmcQueue;
|
|
19
|
+
use rb_sys::{rb_ext_ractor_safe, rb_thread_call_without_gvl, ruby_special_consts, VALUE};
|
|
20
|
+
use parking_lot::Mutex;
|
|
21
|
+
use std::{ffi::c_void, mem::transmute};
|
|
22
|
+
|
|
23
|
+
fn value_to_raw(value: Value) -> VALUE {
|
|
24
|
+
unsafe { transmute::<Value, VALUE>(value) }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
unsafe fn value_from_raw(raw: VALUE) -> Value {
|
|
28
|
+
transmute::<VALUE, Value>(raw)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn qnil_raw() -> VALUE {
|
|
32
|
+
ruby_special_consts::RUBY_Qnil as VALUE
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn make_shareable(ruby: &Ruby, value: Value) -> Result<Value, Error> {
|
|
36
|
+
value.freeze();
|
|
37
|
+
let ractor: RClass = ruby.class_object().const_get("Ractor")?;
|
|
38
|
+
ractor.funcall("make_shareable", (value,))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
struct Counter(AtomicCounter);
|
|
42
|
+
|
|
43
|
+
impl Counter {
|
|
44
|
+
fn new(ruby: &Ruby, class: RClass) -> Result<Value, Error> {
|
|
45
|
+
let value = ruby.wrap_as(Self(AtomicCounter::new(0)), class).as_value();
|
|
46
|
+
make_shareable(ruby, value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fn increment(&self, amt: u64) {
|
|
50
|
+
self.0.inc(amt);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn decrement(&self, amt: u64) {
|
|
54
|
+
self.0.dec(amt);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn read(&self) -> u64 {
|
|
58
|
+
self.0.read()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
impl DataTypeFunctions for Counter {}
|
|
63
|
+
|
|
64
|
+
unsafe impl TypedData for Counter {
|
|
65
|
+
fn class(ruby: &Ruby) -> RClass {
|
|
66
|
+
static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
|
67
|
+
let class = ruby.define_module("Ratomic")
|
|
68
|
+
.unwrap()
|
|
69
|
+
.define_class("Counter", ruby.class_object())
|
|
70
|
+
.unwrap();
|
|
71
|
+
class.undef_default_alloc_func();
|
|
72
|
+
class
|
|
73
|
+
});
|
|
74
|
+
ruby.get_inner(&CLASS)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fn data_type() -> &'static DataType {
|
|
78
|
+
static DATA_TYPE: DataType =
|
|
79
|
+
data_type_builder!(Counter, "ratomic/counter").frozen_shareable().build();
|
|
80
|
+
&DATA_TYPE
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
struct HashMap(ConcurrentHashMap);
|
|
85
|
+
|
|
86
|
+
impl HashMap {
|
|
87
|
+
fn new(ruby: &Ruby, class: RClass) -> Result<Value, Error> {
|
|
88
|
+
let value = ruby.wrap_as(Self(ConcurrentHashMap::new()), class).as_value();
|
|
89
|
+
make_shareable(ruby, value)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn get(ruby: &Ruby, rb_self: &Self, key: Value) -> Value {
|
|
93
|
+
let raw = rb_self.0.get(value_to_raw(key)).unwrap_or_else(qnil_raw);
|
|
94
|
+
unsafe { value_from_raw(raw) }.into_value_with(ruby)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn set(&self, key: Value, value: Value) {
|
|
98
|
+
self.0.set(value_to_raw(key), value_to_raw(value));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn clear(&self) {
|
|
102
|
+
self.0.clear();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn size(&self) -> usize {
|
|
106
|
+
self.0.size()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn fetch_and_modify(ruby: &Ruby, rb_self: &Self, key: Value) -> Result<(), Error> {
|
|
110
|
+
if !ruby.block_given() {
|
|
111
|
+
return Err(Error::new(
|
|
112
|
+
ruby.exception_local_jump_error(),
|
|
113
|
+
"no block given",
|
|
114
|
+
));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let proc = ruby.block_proc()?;
|
|
118
|
+
let mut error = None;
|
|
119
|
+
rb_self.0.fetch_and_modify(value_to_raw(key), |value| {
|
|
120
|
+
match proc.call::<_, Value>((unsafe { value_from_raw(value) },)) {
|
|
121
|
+
Ok(result) => value_to_raw(result),
|
|
122
|
+
Err(err) => {
|
|
123
|
+
error = Some(err);
|
|
124
|
+
value
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if let Some(err) = error {
|
|
130
|
+
Err(err)
|
|
131
|
+
} else {
|
|
132
|
+
Ok(())
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl DataTypeFunctions for HashMap {
|
|
138
|
+
fn mark(&self, marker: &magnus::gc::Marker) {
|
|
139
|
+
self.0.mark(|value| marker.mark(unsafe { value_from_raw(value) }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
unsafe impl TypedData for HashMap {
|
|
144
|
+
fn class(ruby: &Ruby) -> RClass {
|
|
145
|
+
static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
|
146
|
+
let class = ruby.define_module("Ratomic")
|
|
147
|
+
.unwrap()
|
|
148
|
+
.define_class("ConcurrentHashMap", ruby.class_object())
|
|
149
|
+
.unwrap();
|
|
150
|
+
class.undef_default_alloc_func();
|
|
151
|
+
class
|
|
152
|
+
});
|
|
153
|
+
ruby.get_inner(&CLASS)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn data_type() -> &'static DataType {
|
|
157
|
+
static DATA_TYPE: DataType = data_type_builder!(HashMap, "ratomic/hashmap")
|
|
158
|
+
.mark()
|
|
159
|
+
.frozen_shareable()
|
|
160
|
+
.build();
|
|
161
|
+
&DATA_TYPE
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
struct Queue(MpmcQueue);
|
|
166
|
+
|
|
167
|
+
struct PushPayload<'a> {
|
|
168
|
+
queue: &'a MpmcQueue,
|
|
169
|
+
item: VALUE,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
unsafe extern "C" fn push_without_gvl(payload: *mut c_void) -> *mut c_void {
|
|
173
|
+
let payload = &*(payload as *const PushPayload<'_>);
|
|
174
|
+
payload.queue.push(payload.item);
|
|
175
|
+
std::ptr::null_mut()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
unsafe extern "C" fn pop_without_gvl(queue: *mut c_void) -> *mut c_void {
|
|
179
|
+
let queue = &*(queue as *const MpmcQueue);
|
|
180
|
+
queue.pop() as *mut c_void
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
impl Queue {
|
|
184
|
+
fn new(ruby: &Ruby, class: RClass, capacity: Value) -> Result<Value, Error> {
|
|
185
|
+
if !capacity.is_kind_of(ruby.class_integer()) {
|
|
186
|
+
return Err(Error::new(
|
|
187
|
+
ruby.exception_type_error(),
|
|
188
|
+
"no implicit conversion into Integer",
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
let capacity = i64::try_convert(capacity)?;
|
|
192
|
+
if capacity < 1 || capacity > (1 << 20) {
|
|
193
|
+
return Err(Error::new(
|
|
194
|
+
ruby.exception_arg_error(),
|
|
195
|
+
"queue capacity must be between 1 and 1048576",
|
|
196
|
+
));
|
|
197
|
+
}
|
|
198
|
+
let capacity = capacity as usize;
|
|
199
|
+
|
|
200
|
+
let value = ruby
|
|
201
|
+
.wrap_as(Self(MpmcQueue::new(capacity, qnil_raw())), class)
|
|
202
|
+
.as_value();
|
|
203
|
+
make_shareable(ruby, value)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn push(&self, item: Value) {
|
|
207
|
+
let mut payload = PushPayload {
|
|
208
|
+
queue: &self.0,
|
|
209
|
+
item: value_to_raw(item),
|
|
210
|
+
};
|
|
211
|
+
unsafe {
|
|
212
|
+
rb_thread_call_without_gvl(
|
|
213
|
+
Some(push_without_gvl),
|
|
214
|
+
&mut payload as *mut PushPayload<'_> as *mut c_void,
|
|
215
|
+
None,
|
|
216
|
+
std::ptr::null_mut(),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn pop(&self) -> Value {
|
|
222
|
+
let raw = unsafe {
|
|
223
|
+
rb_thread_call_without_gvl(
|
|
224
|
+
Some(pop_without_gvl),
|
|
225
|
+
&self.0 as *const MpmcQueue as *mut c_void,
|
|
226
|
+
None,
|
|
227
|
+
std::ptr::null_mut(),
|
|
228
|
+
) as VALUE
|
|
229
|
+
};
|
|
230
|
+
unsafe { value_from_raw(raw) }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn peek(&self) -> Option<Value> {
|
|
234
|
+
self.0.peek().map(|raw| unsafe { value_from_raw(raw) })
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn is_empty(&self) -> bool {
|
|
238
|
+
self.0.is_empty()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn size(&self) -> usize {
|
|
242
|
+
self.0.size()
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
impl DataTypeFunctions for Queue {
|
|
247
|
+
fn mark(&self, marker: &magnus::gc::Marker) {
|
|
248
|
+
self.0.mark(|value| marker.mark(unsafe { value_from_raw(value) }));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
unsafe impl TypedData for Queue {
|
|
253
|
+
fn class(ruby: &Ruby) -> RClass {
|
|
254
|
+
static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
|
255
|
+
let class = ruby.define_module("Ratomic")
|
|
256
|
+
.unwrap()
|
|
257
|
+
.define_class("Queue", ruby.class_object())
|
|
258
|
+
.unwrap();
|
|
259
|
+
class.undef_default_alloc_func();
|
|
260
|
+
class
|
|
261
|
+
});
|
|
262
|
+
ruby.get_inner(&CLASS)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn data_type() -> &'static DataType {
|
|
266
|
+
static DATA_TYPE: DataType = data_type_builder!(Queue, "ratomic/queue")
|
|
267
|
+
.mark()
|
|
268
|
+
.frozen_shareable()
|
|
269
|
+
.build();
|
|
270
|
+
&DATA_TYPE
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
struct Pool(Mutex<Option<FixedSizeObjectPool>>);
|
|
275
|
+
|
|
276
|
+
impl Pool {
|
|
277
|
+
fn new(ruby: &Ruby, class: RClass, args: &[Value]) -> Result<Value, Error> {
|
|
278
|
+
if args.len() > 2 {
|
|
279
|
+
return Err(Error::new(
|
|
280
|
+
ruby.exception_arg_error(),
|
|
281
|
+
format!("wrong number of arguments (given {}, expected 0..2)", args.len()),
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
let size = args
|
|
285
|
+
.first()
|
|
286
|
+
.copied()
|
|
287
|
+
.map(usize::try_convert)
|
|
288
|
+
.transpose()?
|
|
289
|
+
.unwrap_or(5);
|
|
290
|
+
let timeout_ms = args
|
|
291
|
+
.get(1)
|
|
292
|
+
.copied()
|
|
293
|
+
.map(f64::try_convert)
|
|
294
|
+
.transpose()?
|
|
295
|
+
.map(|timeout| (timeout * 1000.0) as u64)
|
|
296
|
+
.unwrap_or(1000);
|
|
297
|
+
|
|
298
|
+
if size == 0 {
|
|
299
|
+
return Err(Error::new(ruby.exception_arg_error(), "pool size must be positive"));
|
|
300
|
+
}
|
|
301
|
+
if !ruby.block_given() {
|
|
302
|
+
return Err(Error::new(
|
|
303
|
+
ruby.exception_local_jump_error(),
|
|
304
|
+
"no block given",
|
|
305
|
+
));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let value = ruby.wrap_as(Self(Mutex::new(None)), class).as_value();
|
|
309
|
+
let value = make_shareable(ruby, value)?;
|
|
310
|
+
|
|
311
|
+
let mut pool = Vec::with_capacity(size);
|
|
312
|
+
for _ in 0..size {
|
|
313
|
+
let value: Value = ruby.yield_value(())?;
|
|
314
|
+
pool.push(value_to_raw(value));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let rb_self: &Self = TryConvert::try_convert(value)?;
|
|
318
|
+
*rb_self.0.lock() = Some(FixedSizeObjectPool::new(pool, timeout_ms));
|
|
319
|
+
Ok(value)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fn checkout(ruby: &Ruby, rb_self: &Self) -> Option<magnus::RArray> {
|
|
323
|
+
rb_self
|
|
324
|
+
.0
|
|
325
|
+
.lock()
|
|
326
|
+
.as_ref()
|
|
327
|
+
.and_then(FixedSizeObjectPool::checkout)
|
|
328
|
+
.map(|item| {
|
|
329
|
+
ruby.ary_new_from_values(&[
|
|
330
|
+
unsafe { value_from_raw(item.rbobj) },
|
|
331
|
+
item.idx.into_value_with(ruby),
|
|
332
|
+
])
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
fn checkin(&self, idx: usize) {
|
|
337
|
+
if let Some(pool) = self.0.lock().as_ref() {
|
|
338
|
+
pool.checkin(idx);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
impl DataTypeFunctions for Pool {
|
|
344
|
+
fn mark(&self, marker: &magnus::gc::Marker) {
|
|
345
|
+
if let Some(pool) = self.0.lock().as_ref() {
|
|
346
|
+
pool.mark(|value| marker.mark(unsafe { value_from_raw(value) }));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
unsafe impl TypedData for Pool {
|
|
352
|
+
fn class(ruby: &Ruby) -> RClass {
|
|
353
|
+
static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
|
354
|
+
let class = ruby.define_module("Ratomic")
|
|
355
|
+
.unwrap()
|
|
356
|
+
.define_class("FixedSizeObjectPool", ruby.class_object())
|
|
357
|
+
.unwrap();
|
|
358
|
+
class.undef_default_alloc_func();
|
|
359
|
+
class
|
|
360
|
+
});
|
|
361
|
+
ruby.get_inner(&CLASS)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
fn data_type() -> &'static DataType {
|
|
365
|
+
static DATA_TYPE: DataType = data_type_builder!(Pool, "ratomic/pool")
|
|
366
|
+
.mark()
|
|
367
|
+
.frozen_shareable()
|
|
368
|
+
.build();
|
|
369
|
+
&DATA_TYPE
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#[magnus::init]
|
|
374
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
375
|
+
unsafe { rb_ext_ractor_safe(true) };
|
|
376
|
+
|
|
377
|
+
let root = ruby.define_module("Ratomic")?;
|
|
378
|
+
|
|
379
|
+
let counter = root.define_class("Counter", ruby.class_object())?;
|
|
380
|
+
counter.undef_default_alloc_func();
|
|
381
|
+
counter.define_singleton_method("new", method!(Counter::new, 0))?;
|
|
382
|
+
counter.define_method("increment", method!(Counter::increment, 1))?;
|
|
383
|
+
counter.define_method("decrement", method!(Counter::decrement, 1))?;
|
|
384
|
+
counter.define_method("read", method!(Counter::read, 0))?;
|
|
385
|
+
|
|
386
|
+
let hashmap = root.define_class("ConcurrentHashMap", ruby.class_object())?;
|
|
387
|
+
hashmap.undef_default_alloc_func();
|
|
388
|
+
hashmap.define_singleton_method("new", method!(HashMap::new, 0))?;
|
|
389
|
+
hashmap.define_method("get", method!(HashMap::get, 1))?;
|
|
390
|
+
hashmap.define_method("set", method!(HashMap::set, 2))?;
|
|
391
|
+
hashmap.define_method("clear", method!(HashMap::clear, 0))?;
|
|
392
|
+
hashmap.define_method("size", method!(HashMap::size, 0))?;
|
|
393
|
+
hashmap.define_method("fetch_and_modify", method!(HashMap::fetch_and_modify, 1))?;
|
|
394
|
+
|
|
395
|
+
let queue = root.define_class("Queue", ruby.class_object())?;
|
|
396
|
+
queue.undef_default_alloc_func();
|
|
397
|
+
queue.define_singleton_method("new", method!(Queue::new, 1))?;
|
|
398
|
+
queue.define_method("push", method!(Queue::push, 1))?;
|
|
399
|
+
queue.define_method("pop", method!(Queue::pop, 0))?;
|
|
400
|
+
queue.define_method("peek", method!(Queue::peek, 0))?;
|
|
401
|
+
queue.define_method("empty?", method!(Queue::is_empty, 0))?;
|
|
402
|
+
queue.define_method("length", method!(Queue::size, 0))?;
|
|
403
|
+
queue.define_method("size", method!(Queue::size, 0))?;
|
|
404
|
+
|
|
405
|
+
let pool = root.define_class("FixedSizeObjectPool", ruby.class_object())?;
|
|
406
|
+
pool.undef_default_alloc_func();
|
|
407
|
+
pool.define_singleton_method("new", method!(Pool::new, -1))?;
|
|
408
|
+
pool.define_method("checkout", method!(Pool::checkout, 0))?;
|
|
409
|
+
pool.define_method("checkin", method!(Pool::checkin, 1))?;
|
|
410
|
+
|
|
411
|
+
Ok(())
|
|
412
|
+
}
|