ratomic 0.4.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26ce16d80118e646fc2ba303d9571820f3fcaee5e16a0f4bdf759d213169c04a
4
- data.tar.gz: 0ed04ad6cbb1550954e66922436337927a365b492cdce1208f7dacedbb54b463
3
+ metadata.gz: 764ab083a8815f4f2bd0958be83d5209b10b21d8765dc19720fedd6d042eec89
4
+ data.tar.gz: 5bf02090ace17e0ca172bcc9f9f2a56f79030f0a92ecf669adecba72aa2ec641
5
5
  SHA512:
6
- metadata.gz: 29e4ac24a57f8d01f7fa4e16741358c82b7ce2c1ba599f31b6fe01f95f8670047c59d545d15697f61b5f3639a805027a03d91c1a12768558ee6bf8dd54b77ea8
7
- data.tar.gz: 2994138cff4ef33a4498e93fc841ed4f35a891157c805190a3afbd58d3451f609954b2af59c59d8352a2e21939f0114cac0a0745e9c31d0a03bd2e3a1174b1a4
6
+ metadata.gz: c7a3e86bfe551b27b28af6655bd7853c1be683d69e759d6b628388282e7443030fe8b56c7e036e1c25f8234de74052fc621747eb94484e84e979b771aaf7ebcc
7
+ data.tar.gz: 03e9c06d8c5771ab340e1219113b5f67c05da3e97c5ad2a5b7ddefb9e5b54103988eb6cb7c299e09a0808e27fec660b3eaea8a7a3fc200a5aa8dd84161faf484
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 0.4.1
4
10
 
5
11
  ### Documentation
@@ -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>(&self, key: VALUE, missing: VALUE, f: F) -> Result<VALUE, 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
- match self.map.entry(RubyHashEql(key)) {
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>
@@ -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(value_to_raw(key), qnil_raw(), |value| {
162
- proc.call::<_, Value>((unsafe { value_from_raw(value) },))
163
- .map(value_to_raw)
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
  }
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Ratomic
4
4
  # Current gem version string.
5
- VERSION = "0.4.1"
5
+ VERSION = "0.4.2"
6
6
  end
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.1
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.10
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: []