prosody 0.5.1 → 0.6.0

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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.config/rail.toml +26 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.ruby-version +1 -1
  5. data/.taplo.toml +1 -1
  6. data/AGENTS.md +29 -15
  7. data/CHANGELOG.md +7 -0
  8. data/CONFIGURATION.md +19 -16
  9. data/Cargo.lock +254 -235
  10. data/Cargo.toml +11 -8
  11. data/README.md +100 -23
  12. data/examples/keyed_state.rb +10 -2
  13. data/examples/keyed_state.rbs +1 -0
  14. data/ext/prosody/Cargo.toml +1 -1
  15. data/ext/prosody/src/admin.rs +36 -36
  16. data/ext/prosody/src/bridge/mod.rs +5 -10
  17. data/ext/prosody/src/client/config/connections.rs +170 -0
  18. data/ext/prosody/src/client/config/middleware.rs +184 -0
  19. data/ext/prosody/src/client/config/mod.rs +396 -0
  20. data/ext/prosody/src/client/config/state.rs +323 -0
  21. data/ext/prosody/src/client/mod.rs +31 -117
  22. data/ext/prosody/src/client/readers.rs +106 -0
  23. data/ext/prosody/src/client/request.rs +2 -2
  24. data/ext/prosody/src/client/support.rs +13 -32
  25. data/ext/prosody/src/gvl.rs +8 -6
  26. data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
  27. data/ext/prosody/src/handler/context/vending.rs +138 -0
  28. data/ext/prosody/src/handler/message.rs +36 -0
  29. data/ext/prosody/src/handler/mod.rs +18 -8
  30. data/ext/prosody/src/handler/state/deque.rs +144 -0
  31. data/ext/prosody/src/handler/state/mod.rs +163 -268
  32. data/ext/prosody/src/handler/state/query.rs +264 -0
  33. data/ext/prosody/src/handler/state/registration.rs +15 -86
  34. data/ext/prosody/src/handler/state/scan.rs +33 -141
  35. data/ext/prosody/src/handler/state/set.rs +98 -0
  36. data/ext/prosody/src/lib.rs +54 -36
  37. data/ext/prosody/src/logging.rs +6 -6
  38. data/ext/prosody/src/published.rs +171 -152
  39. data/ext/prosody/src/scheduler/result.rs +2 -1
  40. data/ext/prosody/src/util.rs +65 -3
  41. data/lib/prosody/client.rb +32 -0
  42. data/lib/prosody/configuration.rb +20 -12
  43. data/lib/prosody/demand.rb +27 -0
  44. data/lib/prosody/native_stubs/client.rb +157 -0
  45. data/lib/prosody/native_stubs/context.rb +178 -0
  46. data/lib/prosody/native_stubs/message.rb +133 -0
  47. data/lib/prosody/native_stubs.rb +11 -956
  48. data/lib/prosody/state/deque.rb +253 -0
  49. data/lib/prosody/state/map.rb +313 -0
  50. data/lib/prosody/state/set.rb +121 -0
  51. data/lib/prosody/state/value.rb +58 -0
  52. data/lib/prosody/state.rb +157 -677
  53. data/lib/prosody/version.rb +1 -1
  54. data/lib/prosody.rb +2 -1
  55. data/sig/configuration.rbs +19 -10
  56. data/sig/prosody.rbs +37 -4
  57. data/sig/published.rbs +102 -0
  58. data/sig/state.rbs +109 -122
  59. data/typecheck/payload_types.rb +12 -0
  60. data/typecheck/payload_types.rbs +1 -0
  61. metadata +30 -11
  62. data/ext/prosody/src/client/config.rs +0 -1300
@@ -0,0 +1,264 @@
1
+ //! Translation of Ruby query keywords into core query settings.
2
+ //!
3
+ //! Map and set queries select string keys. Deque queries select positions that
4
+ //! count from the front. Every option narrows the selection, and `nil` leaves
5
+ //! an option unset. `from`, `after`, `to`, and `before` apply in iteration
6
+ //! order, so a reverse query starts at the high end. `range` bounds keys or
7
+ //! positions in ascending terms and applies in either direction.
8
+ //!
9
+ //! A bad option raises `ArgumentError` or `TypeError`, as Ruby does for a bad
10
+ //! keyword argument.
11
+
12
+ use super::transient_state_error;
13
+ use magnus::r_hash::ForEach;
14
+ use magnus::value::ReprValue;
15
+ use magnus::{Error, Integer, RHash, Range, Ruby, StaticSymbol, Symbol, TryConvert, Value};
16
+ use prosody::state::{DequeQuery, Direction, ErasedKeyQuery};
17
+ use std::num::NonZeroUsize;
18
+ use std::ops::Bound;
19
+
20
+ /// Raw option values from a Ruby keyword hash. `nil` values are absent.
21
+ #[derive(Default)]
22
+ struct Keywords {
23
+ from: Option<Value>,
24
+ after: Option<Value>,
25
+ to: Option<Value>,
26
+ before: Option<Value>,
27
+ range: Option<Value>,
28
+ prefix: Option<Value>,
29
+ limit: Option<Value>,
30
+ }
31
+
32
+ /// Translated edges and limit. `T` is a key or a position.
33
+ ///
34
+ /// `start` and `end` are in iteration order. `range` is in ascending terms.
35
+ struct Bounds<T> {
36
+ start: Bound<T>,
37
+ end: Bound<T>,
38
+ range: Option<(Bound<T>, Bound<T>)>,
39
+ limit: Option<NonZeroUsize>,
40
+ }
41
+
42
+ impl Keywords {
43
+ /// Collects the keyword hash. Deques have no `prefix`.
44
+ fn collect(ruby: &Ruby, options: RHash, prefix_allowed: bool) -> Result<Self, Error> {
45
+ let mut keywords = Self::default();
46
+ options.foreach(|keyword: Symbol, value: Value| {
47
+ let name = keyword.name()?;
48
+ let slot = match name.as_ref() {
49
+ "from" => &mut keywords.from,
50
+ "after" => &mut keywords.after,
51
+ "to" => &mut keywords.to,
52
+ "before" => &mut keywords.before,
53
+ "range" => &mut keywords.range,
54
+ "prefix" if prefix_allowed => &mut keywords.prefix,
55
+ "limit" => &mut keywords.limit,
56
+ other => return Err(argument_error(ruby, format!("unknown keyword: :{other}"))),
57
+ };
58
+ *slot = (!value.is_nil()).then_some(value);
59
+ Ok(ForEach::Continue)
60
+ })?;
61
+ Ok(keywords)
62
+ }
63
+
64
+ /// Translates the edges, range, and limit with `convert`.
65
+ fn bounds<T, F>(self, ruby: &Ruby, convert: F) -> Result<Bounds<T>, Error>
66
+ where
67
+ F: Fn(&'static str, Value) -> Result<T, Error>,
68
+ {
69
+ let start = edge(ruby, ("from", self.from), ("after", self.after), &convert)?;
70
+ let end = edge(ruby, ("to", self.to), ("before", self.before), &convert)?;
71
+ let range = self
72
+ .range
73
+ .map(|value| range(ruby, value, &convert))
74
+ .transpose()?;
75
+ let limit = self.limit.map(|value| limit(ruby, value)).transpose()?;
76
+ Ok(Bounds {
77
+ start,
78
+ end,
79
+ range,
80
+ limit,
81
+ })
82
+ }
83
+ }
84
+
85
+ /// Builds a map or set query from a direction token and keywords.
86
+ ///
87
+ /// # Errors
88
+ ///
89
+ /// Raises `ArgumentError` for an unknown keyword, both edges of one side, or a
90
+ /// limit that is not a positive `Integer`. Raises
91
+ /// `TypeError` for a key that is not a `String` or a range that is not a
92
+ /// `Range`. An invalid direction token raises a transient state error.
93
+ pub(crate) fn key_query(
94
+ ruby: &Ruby,
95
+ direction: StaticSymbol,
96
+ options: RHash,
97
+ ) -> Result<ErasedKeyQuery, Error> {
98
+ let query = ErasedKeyQuery::new().direction(parse_direction(ruby, direction)?);
99
+ let keywords = Keywords::collect(ruby, options, true)?;
100
+ let prefix = keywords.prefix.map(String::try_convert).transpose()?;
101
+ let bounds = keywords.bounds(ruby, |_, value| String::try_convert(value))?;
102
+
103
+ let mut query = match bounds.start {
104
+ Bound::Included(key) => query.from(key),
105
+ Bound::Excluded(key) => query.after(key),
106
+ Bound::Unbounded => query,
107
+ };
108
+ query = match bounds.end {
109
+ Bound::Included(key) => query.to(key),
110
+ Bound::Excluded(key) => query.before(key),
111
+ Bound::Unbounded => query,
112
+ };
113
+ if let Some(range) = bounds.range {
114
+ query = query.range(range);
115
+ }
116
+ if let Some(prefix) = prefix {
117
+ query = query.prefix(prefix);
118
+ }
119
+ if let Some(limit) = bounds.limit {
120
+ query = query.limit(limit);
121
+ }
122
+ Ok(query)
123
+ }
124
+
125
+ /// Builds a deque query from a direction token and keywords.
126
+ ///
127
+ /// Positions count from the front and must be non-negative. Use a reverse
128
+ /// traversal to read from the back.
129
+ ///
130
+ /// # Errors
131
+ ///
132
+ /// Raises `ArgumentError` for an unknown keyword, both edges of one side, a
133
+ /// position that is not a non-negative `Integer`, or a limit that is not a
134
+ /// positive `Integer`. Raises `TypeError` for a range that
135
+ /// is not a `Range`.
136
+ pub(crate) fn position_query(
137
+ ruby: &Ruby,
138
+ direction: StaticSymbol,
139
+ options: RHash,
140
+ ) -> Result<DequeQuery, Error> {
141
+ let query = DequeQuery::new().direction(parse_direction(ruby, direction)?);
142
+ let bounds = Keywords::collect(ruby, options, false)?
143
+ .bounds(ruby, |keyword, value| position(ruby, keyword, value))?;
144
+
145
+ let mut query = match bounds.start {
146
+ Bound::Included(position) => query.from(position),
147
+ Bound::Excluded(position) => query.after(position),
148
+ Bound::Unbounded => query,
149
+ };
150
+ query = match bounds.end {
151
+ Bound::Included(position) => query.to(position),
152
+ Bound::Excluded(position) => query.before(position),
153
+ Bound::Unbounded => query,
154
+ };
155
+ if let Some(range) = bounds.range {
156
+ query = query.range(range);
157
+ }
158
+ if let Some(limit) = bounds.limit {
159
+ query = query.limit(limit);
160
+ }
161
+ Ok(query)
162
+ }
163
+
164
+ /// Parses a traversal direction token into the core [`Direction`].
165
+ ///
166
+ /// An invalid token is a caller mistake and rejects transient.
167
+ fn parse_direction(ruby: &Ruby, direction: StaticSymbol) -> Result<Direction, Error> {
168
+ match direction.name()? {
169
+ "forward" => Ok(Direction::Forward),
170
+ "backward" => Ok(Direction::Backward),
171
+ other => Err(transient_state_error(
172
+ ruby,
173
+ format!("direction: expected :forward or :backward, got :{other}"),
174
+ )),
175
+ }
176
+ }
177
+
178
+ /// Translates one side of the selection. The inclusive and exclusive edges of
179
+ /// one side are exclusive: an options hash has no call order to pick one.
180
+ fn edge<T, F>(
181
+ ruby: &Ruby,
182
+ (inclusive_name, inclusive): (&'static str, Option<Value>),
183
+ (exclusive_name, exclusive): (&'static str, Option<Value>),
184
+ convert: &F,
185
+ ) -> Result<Bound<T>, Error>
186
+ where
187
+ F: Fn(&'static str, Value) -> Result<T, Error>,
188
+ {
189
+ match (inclusive, exclusive) {
190
+ (Some(_), Some(_)) => Err(argument_error(
191
+ ruby,
192
+ format!("{inclusive_name}: and {exclusive_name}: are exclusive; pass one of them"),
193
+ )),
194
+ (Some(value), None) => Ok(Bound::Included(convert(inclusive_name, value)?)),
195
+ (None, Some(value)) => Ok(Bound::Excluded(convert(exclusive_name, value)?)),
196
+ (None, None) => Ok(Bound::Unbounded),
197
+ }
198
+ }
199
+
200
+ /// Translates a Ruby `Range`. A `nil` end is unbounded. An exclusive range
201
+ /// excludes its end. A descending range is a valid empty range, so it selects
202
+ /// nothing.
203
+ fn range<T, F>(ruby: &Ruby, value: Value, convert: &F) -> Result<(Bound<T>, Bound<T>), Error>
204
+ where
205
+ F: Fn(&'static str, Value) -> Result<T, Error>,
206
+ {
207
+ let Some(range) = Range::from_value(value) else {
208
+ return Err(Error::new(
209
+ ruby.exception_type_error(),
210
+ format!("range: expected a Range, got {}", value.inspect()),
211
+ ));
212
+ };
213
+
214
+ let low: Value = range.beg()?;
215
+ let low = if low.is_nil() {
216
+ Bound::Unbounded
217
+ } else {
218
+ Bound::Included(convert("range", low)?)
219
+ };
220
+ let high: Value = range.end()?;
221
+ let high = match (high.is_nil(), range.excl()) {
222
+ (true, _) => Bound::Unbounded,
223
+ (false, true) => Bound::Excluded(convert("range", high)?),
224
+ (false, false) => Bound::Included(convert("range", high)?),
225
+ };
226
+
227
+ Ok((low, high))
228
+ }
229
+
230
+ /// Translates a front-relative deque position.
231
+ fn position(ruby: &Ruby, keyword: &'static str, value: Value) -> Result<usize, Error> {
232
+ let invalid = || {
233
+ argument_error(
234
+ ruby,
235
+ format!(
236
+ "{keyword}: expected a non-negative Integer position, got {}",
237
+ value.inspect()
238
+ ),
239
+ )
240
+ };
241
+ let integer = Integer::from_value(value).ok_or_else(invalid)?;
242
+ integer.to_usize().map_err(|_| invalid())
243
+ }
244
+
245
+ /// Translates a result limit. It keeps its own Integer check: a helper shared
246
+ /// with `position` saves no lines.
247
+ fn limit(ruby: &Ruby, value: Value) -> Result<NonZeroUsize, Error> {
248
+ let invalid = || {
249
+ argument_error(
250
+ ruby,
251
+ format!(
252
+ "limit: expected a positive Integer, got {}",
253
+ value.inspect()
254
+ ),
255
+ )
256
+ };
257
+ let integer = Integer::from_value(value).ok_or_else(invalid)?;
258
+ let limit = integer.to_usize().map_err(|_| invalid())?;
259
+ NonZeroUsize::new(limit).ok_or_else(invalid)
260
+ }
261
+
262
+ fn argument_error(ruby: &Ruby, message: String) -> Error {
263
+ Error::new(ruby.exception_arg_error(), message)
264
+ }
@@ -3,67 +3,10 @@
3
3
  use super::{
4
4
  NativeJsonDequeScan, NativeJsonDequeState, NativeJsonMapScan, NativeJsonMapState,
5
5
  NativeJsonValueState, NativeMapKeyScan, NativeMessageDequeScan, NativeMessageDequeState,
6
- NativeMessageMapScan, NativeMessageMapState, NativeMessageValueState,
6
+ NativeMessageMapScan, NativeMessageMapState, NativeMessageValueState, NativeSetState,
7
7
  };
8
- use crate::{ROOT_MOD, id};
9
- use magnus::{Error, Module, Ruby, method};
10
-
11
- macro_rules! register_value {
12
- ($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
13
- let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
14
- class.define_method(id!($ruby, "get"), method!(<$type>::get, 0))?;
15
- class.define_method(id!($ruby, "set"), method!(<$type>::set, 1))?;
16
- class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
17
- class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
18
- class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
19
- }};
20
- }
21
-
22
- macro_rules! register_map {
23
- ($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
24
- let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
25
- class.define_method(id!($ruby, "get"), method!(<$type>::get, 1))?;
26
- class.define_method(
27
- id!($ruby, "contains_key"),
28
- method!(<$type>::contains_key, 1),
29
- )?;
30
- class.define_method(id!($ruby, "get_many"), method!(<$type>::get_many, 1))?;
31
- class.define_method(id!($ruby, "set"), method!(<$type>::set, 2))?;
32
- class.define_method(id!($ruby, "remove"), method!(<$type>::remove, 1))?;
33
- class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
34
- class.define_method(id!($ruby, "scan"), method!(<$type>::scan, 1))?;
35
- class.define_method(id!($ruby, "keys"), method!(<$type>::keys, 1))?;
36
- class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
37
- class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
38
- }};
39
- }
40
-
41
- macro_rules! register_deque {
42
- ($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
43
- let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
44
- class.define_method(id!($ruby, "len"), method!(<$type>::len, 0))?;
45
- class.define_method(id!($ruby, "is_empty"), method!(<$type>::is_empty, 0))?;
46
- class.define_method(id!($ruby, "get"), method!(<$type>::get, 1))?;
47
- class.define_method(id!($ruby, "peek_front"), method!(<$type>::peek_front, 0))?;
48
- class.define_method(id!($ruby, "peek_back"), method!(<$type>::peek_back, 0))?;
49
- class.define_method(id!($ruby, "push_back"), method!(<$type>::push_back, 1))?;
50
- class.define_method(id!($ruby, "push_front"), method!(<$type>::push_front, 1))?;
51
- class.define_method(id!($ruby, "pop_front"), method!(<$type>::pop_front, 0))?;
52
- class.define_method(id!($ruby, "pop_back"), method!(<$type>::pop_back, 0))?;
53
- class.define_method(id!($ruby, "clear"), method!(<$type>::clear, 0))?;
54
- class.define_method(id!($ruby, "scan"), method!(<$type>::scan, 1))?;
55
- class.define_method(id!($ruby, "commit"), method!(<$type>::commit, 0))?;
56
- class.define_method(id!($ruby, "rollback"), method!(<$type>::rollback, 0))?;
57
- }};
58
- }
59
-
60
- macro_rules! register_scan {
61
- ($ruby:expr, $module:expr, $name:literal, $type:ty) => {{
62
- let class = $module.define_class(id!($ruby, $name), $ruby.class_object())?;
63
- class.define_method(id!($ruby, "next"), method!(<$type>::next, 0))?;
64
- class.define_method(id!($ruby, "close"), method!(<$type>::close, 0))?;
65
- }};
66
- }
8
+ use crate::ROOT_MOD;
9
+ use magnus::{Error, Ruby};
67
10
 
68
11
  /// Registers the concrete native state classes.
69
12
  ///
@@ -73,32 +16,18 @@ macro_rules! register_scan {
73
16
  pub(crate) fn register(ruby: &Ruby) -> Result<(), Error> {
74
17
  let module = ruby.get_inner(&ROOT_MOD);
75
18
 
76
- register_value!(ruby, module, "NativeJsonValueState", NativeJsonValueState);
77
- register_value!(
78
- ruby,
79
- module,
80
- "NativeMessageValueState",
81
- NativeMessageValueState
82
- );
83
- register_map!(ruby, module, "NativeJsonMapState", NativeJsonMapState);
84
- register_map!(ruby, module, "NativeMessageMapState", NativeMessageMapState);
85
- register_deque!(ruby, module, "NativeJsonDequeState", NativeJsonDequeState);
86
- register_deque!(
87
- ruby,
88
- module,
89
- "NativeMessageDequeState",
90
- NativeMessageDequeState
91
- );
92
- register_scan!(ruby, module, "NativeJsonDequeScan", NativeJsonDequeScan);
93
- register_scan!(ruby, module, "NativeJsonMapScan", NativeJsonMapScan);
94
- register_scan!(
95
- ruby,
96
- module,
97
- "NativeMessageDequeScan",
98
- NativeMessageDequeScan
99
- );
100
- register_scan!(ruby, module, "NativeMessageMapScan", NativeMessageMapScan);
101
- register_scan!(ruby, module, "NativeMapKeyScan", NativeMapKeyScan);
19
+ NativeJsonValueState::register(ruby, module)?;
20
+ NativeMessageValueState::register(ruby, module)?;
21
+ NativeJsonMapState::register(ruby, module)?;
22
+ NativeMessageMapState::register(ruby, module)?;
23
+ NativeSetState::register(ruby, module)?;
24
+ NativeJsonDequeState::register(ruby, module)?;
25
+ NativeMessageDequeState::register(ruby, module)?;
26
+ NativeJsonDequeScan::register(ruby, module)?;
27
+ NativeJsonMapScan::register(ruby, module)?;
28
+ NativeMessageDequeScan::register(ruby, module)?;
29
+ NativeMessageMapScan::register(ruby, module)?;
30
+ NativeMapKeyScan::register(ruby, module)?;
102
31
 
103
32
  Ok(())
104
33
  }
@@ -3,150 +3,69 @@
3
3
  //! Cancellation can discard an orphaned chunk. The attempt then closes the
4
4
  //! cursor through the Ruby `ensure`, so no later operation observes that chunk.
5
5
 
6
- use super::state_error;
7
- use crate::bridge::{Bridge, QUEUE_CLASS};
6
+ use super::{run_state, wrapped_class};
7
+ use crate::bridge::Bridge;
8
8
  use crate::handler::message::Message;
9
- use crate::id;
10
- use crate::tracing_util::extract_opentelemetry_context;
11
- use crate::util::ThreadSafeValue;
12
- use magnus::value::ReprValue;
13
- use magnus::{Error, IntoValue, Ruby, Value};
9
+ use magnus::{Error, IntoValue, Module, RArray, RModule, Ruby, Value, method};
14
10
  use opentelemetry::propagation::TextMapCompositePropagator;
15
- use opentelemetry::trace::FutureExt;
16
11
  use prosody::consumer::event_context::StateCursor;
17
12
  use prosody::consumer::message::ConsumerMessage;
18
13
  use serde_json::Value as JsonValue;
19
14
  use serde_magnus::serialize;
20
- use std::cell::RefCell;
21
- use std::collections::VecDeque;
22
15
  use std::num::NonZeroUsize;
23
16
  use std::sync::Arc;
24
17
  use tracing::Span;
25
18
 
26
- #[allow(clippy::unwrap_used, reason = "256 is a nonzero literal")]
27
- const SCAN_READY_CHUNK_SIZE: NonZeroUsize = NonZeroUsize::new(256).unwrap();
28
-
29
- struct ScanInner<T> {
30
- cursor: Arc<StateCursor<T>>,
31
- buffer: VecDeque<T>,
32
- done: bool,
33
- }
34
-
35
- macro_rules! drive_scan {
36
- ($ruby:expr, $this:expr, $inner:expr, |$item:ident| $convert:block) => {{
37
- loop {
38
- if let Some($item) = $inner.buffer.pop_front() {
39
- return $convert;
40
- }
41
- if $inner.done {
42
- return Ok($ruby.qnil().as_value());
43
- }
44
- let cursor = Arc::clone(&$inner.cursor);
45
- let context = extract_opentelemetry_context($ruby, &$this.propagator)?;
46
- let chunk = $this
47
- .bridge
48
- .wait_for(
49
- $ruby,
50
- async move {
51
- cursor
52
- .next_ready_chunk(SCAN_READY_CHUNK_SIZE)
53
- .with_context(context)
54
- .await
55
- },
56
- Span::current(),
57
- )?
58
- .map_err(|error| state_error($ruby, &error))?;
59
- match chunk {
60
- Some(items) => $inner.buffer.extend(items),
61
- None => {
62
- $inner.done = true;
63
- let cursor = Arc::clone(&$inner.cursor);
64
- $this.bridge.wait_for(
65
- $ruby,
66
- async move { cursor.close().await },
67
- Span::current(),
68
- )?;
69
- return Ok($ruby.qnil().as_value());
70
- }
71
- }
72
- }
73
- }};
74
- }
19
+ const SCAN_READY_CHUNK_SIZE: NonZeroUsize = match NonZeroUsize::new(256) {
20
+ Some(size) => size,
21
+ None => NonZeroUsize::MIN,
22
+ };
75
23
 
76
24
  macro_rules! native_scan {
77
25
  ($name:ident, $class:literal, $item:ty, |$ruby:ident, $item_name:ident| $convert:block) => {
78
26
  /// Native cursor with one item type.
79
27
  #[magnus::wrap(class = $class)]
80
28
  pub struct $name {
81
- inner: RefCell<ScanInner<$item>>,
82
- lock: ThreadSafeValue,
29
+ cursor: Arc<StateCursor<$item>>,
83
30
  bridge: Bridge,
84
31
  propagator: Arc<TextMapCompositePropagator>,
85
32
  }
86
33
 
87
34
  impl $name {
88
- pub(super) fn new(
89
- ruby: &Ruby,
90
- cursor: Box<StateCursor<$item>>,
35
+ pub(crate) fn new(
36
+ cursor: StateCursor<$item>,
91
37
  bridge: Bridge,
92
38
  propagator: Arc<TextMapCompositePropagator>,
93
- ) -> Result<Self, Error> {
94
- let queue: Value = ruby.get_inner(&QUEUE_CLASS).funcall(id!(ruby, "new"), ())?;
95
- let _: Value = queue.funcall(id!(ruby, "push"), (ruby.qnil(),))?;
96
- Ok(Self {
97
- inner: RefCell::new(ScanInner {
98
- cursor: Arc::from(cursor),
99
- buffer: VecDeque::new(),
100
- done: false,
101
- }),
102
- lock: ThreadSafeValue::new(queue, bridge.clone()),
39
+ ) -> Self {
40
+ Self {
41
+ cursor: Arc::new(cursor),
103
42
  bridge,
104
43
  propagator,
105
- })
106
- }
107
-
108
- fn acquire(&self, ruby: &Ruby) -> Result<(), Error> {
109
- let _: Value = self.lock.get(ruby).funcall(id!(ruby, "pop"), ())?;
110
- Ok(())
111
- }
112
-
113
- fn release(&self, ruby: &Ruby) {
114
- let _: Result<Value, Error> = self
115
- .lock
116
- .get(ruby)
117
- .funcall(id!(ruby, "push"), (ruby.qnil(),));
118
- }
119
-
120
- pub(super) fn next(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
121
- this.acquire(ruby)?;
122
- let out = Self::next_locked(ruby, this);
123
- this.release(ruby);
124
- out
44
+ }
125
45
  }
126
46
 
127
- fn next_locked(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
128
- let inner = &mut *this.inner.borrow_mut();
129
- drive_scan!(ruby, this, inner, |$item_name| {
130
- let $ruby = ruby;
131
- $convert
132
- })
47
+ /// Returns the next ready chunk as an Array, or `nil` after the
48
+ /// end. Read failures raise the typed state errors.
49
+ fn next_chunk($ruby: &Ruby, this: &Self) -> Result<Option<RArray>, Error> {
50
+ let cursor = Arc::clone(&this.cursor);
51
+ run_state($ruby, &this.bridge, &this.propagator, async move {
52
+ cursor.next_ready_chunk(SCAN_READY_CHUNK_SIZE).await
53
+ })?
54
+ .map(|items| $ruby.ary_try_from_iter(items.into_iter().map(|$item_name| $convert)))
55
+ .transpose()
133
56
  }
134
57
 
135
- pub(super) fn close(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
136
- this.acquire(ruby)?;
137
- let out = Self::close_locked(ruby, this);
138
- this.release(ruby);
139
- out
58
+ fn close(ruby: &Ruby, this: &Self) -> Result<(), Error> {
59
+ let cursor = Arc::clone(&this.cursor);
60
+ this.bridge
61
+ .wait_for(ruby, async move { cursor.close().await }, Span::current())
140
62
  }
141
63
 
142
- fn close_locked(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
143
- let inner = &mut *this.inner.borrow_mut();
144
- inner.done = true;
145
- inner.buffer.clear();
146
- let cursor = Arc::clone(&inner.cursor);
147
- this.bridge
148
- .wait_for(ruby, async move { cursor.close().await }, Span::current())?;
149
- Ok(ruby.qnil().as_value())
64
+ pub(super) fn register(ruby: &Ruby, module: RModule) -> Result<(), Error> {
65
+ let class = wrapped_class(ruby, module, $class)?;
66
+ class.define_method("next_chunk", method!($name::next_chunk, 0))?;
67
+ class.define_method("close", method!($name::close, 0))?;
68
+ Ok(())
150
69
  }
151
70
  }
152
71
  };
@@ -156,7 +75,7 @@ native_scan!(
156
75
  NativeJsonDequeScan,
157
76
  "Prosody::NativeJsonDequeScan",
158
77
  JsonValue,
159
- |ruby, item| { serialize(ruby, &item) }
78
+ |ruby, item| { serialize::<_, Value>(ruby, &item) }
160
79
  );
161
80
  native_scan!(
162
81
  NativeJsonMapScan,
@@ -189,30 +108,3 @@ native_scan!(
189
108
  String,
190
109
  |ruby, item| { Ok(item.into_value_with(ruby)) }
191
110
  );
192
-
193
- pub(crate) fn published_map_scan(
194
- ruby: &Ruby,
195
- cursor: Box<StateCursor<(String, JsonValue)>>,
196
- bridge: Bridge,
197
- propagator: Arc<TextMapCompositePropagator>,
198
- ) -> Result<NativeJsonMapScan, Error> {
199
- NativeJsonMapScan::new(ruby, cursor, bridge, propagator)
200
- }
201
-
202
- pub(crate) fn published_map_key_scan(
203
- ruby: &Ruby,
204
- cursor: Box<StateCursor<String>>,
205
- bridge: Bridge,
206
- propagator: Arc<TextMapCompositePropagator>,
207
- ) -> Result<NativeMapKeyScan, Error> {
208
- NativeMapKeyScan::new(ruby, cursor, bridge, propagator)
209
- }
210
-
211
- pub(crate) fn published_deque_scan(
212
- ruby: &Ruby,
213
- cursor: Box<StateCursor<JsonValue>>,
214
- bridge: Bridge,
215
- propagator: Arc<TextMapCompositePropagator>,
216
- ) -> Result<NativeJsonDequeScan, Error> {
217
- NativeJsonDequeScan::new(ruby, cursor, bridge, propagator)
218
- }