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.
- checksums.yaml +4 -4
- data/.config/rail.toml +26 -0
- data/.release-please-manifest.json +1 -1
- data/.ruby-version +1 -1
- data/.taplo.toml +1 -1
- data/AGENTS.md +29 -15
- data/CHANGELOG.md +7 -0
- data/CONFIGURATION.md +19 -16
- data/Cargo.lock +254 -235
- data/Cargo.toml +11 -8
- data/README.md +100 -23
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/ext/prosody/Cargo.toml +1 -1
- data/ext/prosody/src/admin.rs +36 -36
- data/ext/prosody/src/bridge/mod.rs +5 -10
- data/ext/prosody/src/client/config/connections.rs +170 -0
- data/ext/prosody/src/client/config/middleware.rs +184 -0
- data/ext/prosody/src/client/config/mod.rs +396 -0
- data/ext/prosody/src/client/config/state.rs +323 -0
- data/ext/prosody/src/client/mod.rs +31 -117
- data/ext/prosody/src/client/readers.rs +106 -0
- data/ext/prosody/src/client/request.rs +2 -2
- data/ext/prosody/src/client/support.rs +13 -32
- data/ext/prosody/src/gvl.rs +8 -6
- data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
- data/ext/prosody/src/handler/context/vending.rs +138 -0
- data/ext/prosody/src/handler/message.rs +36 -0
- data/ext/prosody/src/handler/mod.rs +18 -8
- data/ext/prosody/src/handler/state/deque.rs +144 -0
- data/ext/prosody/src/handler/state/mod.rs +163 -268
- data/ext/prosody/src/handler/state/query.rs +264 -0
- data/ext/prosody/src/handler/state/registration.rs +15 -86
- data/ext/prosody/src/handler/state/scan.rs +33 -141
- data/ext/prosody/src/handler/state/set.rs +98 -0
- data/ext/prosody/src/lib.rs +54 -36
- data/ext/prosody/src/logging.rs +6 -6
- data/ext/prosody/src/published.rs +171 -152
- data/ext/prosody/src/scheduler/result.rs +2 -1
- data/ext/prosody/src/util.rs +65 -3
- data/lib/prosody/client.rb +32 -0
- data/lib/prosody/configuration.rb +20 -12
- data/lib/prosody/demand.rb +27 -0
- data/lib/prosody/native_stubs/client.rb +157 -0
- data/lib/prosody/native_stubs/context.rb +178 -0
- data/lib/prosody/native_stubs/message.rb +133 -0
- data/lib/prosody/native_stubs.rb +11 -956
- data/lib/prosody/state/deque.rb +253 -0
- data/lib/prosody/state/map.rb +313 -0
- data/lib/prosody/state/set.rb +121 -0
- data/lib/prosody/state/value.rb +58 -0
- data/lib/prosody/state.rb +157 -677
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +2 -1
- data/sig/configuration.rbs +19 -10
- data/sig/prosody.rbs +37 -4
- data/sig/published.rbs +102 -0
- data/sig/state.rbs +109 -122
- data/typecheck/payload_types.rb +12 -0
- data/typecheck/payload_types.rbs +1 -0
- metadata +30 -11
- 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::
|
|
9
|
-
use magnus::{Error,
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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::
|
|
7
|
-
use crate::bridge::
|
|
6
|
+
use super::{run_state, wrapped_class};
|
|
7
|
+
use crate::bridge::Bridge;
|
|
8
8
|
use crate::handler::message::Message;
|
|
9
|
-
use
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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(
|
|
89
|
-
|
|
90
|
-
cursor: Box<StateCursor<$item>>,
|
|
35
|
+
pub(crate) fn new(
|
|
36
|
+
cursor: StateCursor<$item>,
|
|
91
37
|
bridge: Bridge,
|
|
92
38
|
propagator: Arc<TextMapCompositePropagator>,
|
|
93
|
-
) ->
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
this.
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
143
|
-
let
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
}
|