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
|
@@ -1,76 +1,97 @@
|
|
|
1
1
|
//! Read-only published-state handles for Ruby.
|
|
2
|
+
//!
|
|
3
|
+
//! Every reader method runs through [`Reads`], which refuses a forked child
|
|
4
|
+
//! and joins the caller's OpenTelemetry context. A failed read raises the
|
|
5
|
+
//! same typed state errors as an owned handle.
|
|
2
6
|
|
|
3
7
|
use crate::bridge::Bridge;
|
|
4
8
|
use crate::handler::{
|
|
5
|
-
NativeJsonDequeScan, NativeJsonMapScan, NativeMapKeyScan,
|
|
6
|
-
published_deque_scan, published_map_key_scan, published_map_scan,
|
|
9
|
+
NativeJsonDequeScan, NativeJsonMapScan, NativeMapKeyScan, key_query, position_query, run_state,
|
|
7
10
|
};
|
|
11
|
+
use crate::util::ForkGuard;
|
|
8
12
|
use crate::{ROOT_MOD, id};
|
|
9
|
-
use magnus::
|
|
10
|
-
use magnus::{Error, Module, Ruby, StaticSymbol, Value, method};
|
|
13
|
+
use magnus::{Error, Module, RHash, Ruby, StaticSymbol, Value, method};
|
|
11
14
|
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
12
|
-
use prosody::
|
|
15
|
+
use prosody::consumer::event_context::ErasedStateError;
|
|
13
16
|
use prosody::high_level::erased::{
|
|
14
|
-
|
|
17
|
+
SharedDequeReader, SharedMapReader, SharedSetReader, SharedValueReader,
|
|
15
18
|
};
|
|
16
|
-
use
|
|
19
|
+
use serde_json::Value as JsonValue;
|
|
17
20
|
use serde_magnus::serialize;
|
|
18
21
|
use std::sync::Arc;
|
|
19
|
-
use tracing::Span;
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
/// The bridge, trace propagator, and fork guard that every reader shares.
|
|
24
|
+
#[derive(Clone)]
|
|
25
|
+
pub(crate) struct Reads {
|
|
26
|
+
pub(crate) bridge: Bridge,
|
|
27
|
+
pub(crate) propagator: Arc<TextMapCompositePropagator>,
|
|
28
|
+
pub(crate) fork: ForkGuard,
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
impl Reads {
|
|
32
|
+
/// Waits for one read in the caller's trace.
|
|
33
|
+
///
|
|
34
|
+
/// # Errors
|
|
35
|
+
///
|
|
36
|
+
/// Raises `RuntimeError` after fork, and a typed state error when the
|
|
37
|
+
/// read fails.
|
|
38
|
+
fn read<F, T>(&self, ruby: &Ruby, read: F) -> Result<T, Error>
|
|
39
|
+
where
|
|
40
|
+
F: Future<Output = Result<T, ErasedStateError>> + Send + 'static,
|
|
41
|
+
T: Send + 'static,
|
|
42
|
+
{
|
|
43
|
+
self.fork.check(ruby)?;
|
|
44
|
+
run_state(ruby, &self.bridge, &self.propagator, read)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Waits for one optional JSON read and returns the value or `nil`.
|
|
48
|
+
///
|
|
49
|
+
/// # Errors
|
|
50
|
+
///
|
|
51
|
+
/// See [`Reads::read`].
|
|
52
|
+
fn read_json<F>(&self, ruby: &Ruby, read: F) -> Result<Value, Error>
|
|
53
|
+
where
|
|
54
|
+
F: Future<Output = Result<Option<JsonValue>, ErasedStateError>> + Send + 'static,
|
|
55
|
+
{
|
|
56
|
+
serialize(ruby, &self.read(ruby, read)?)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Returns the bridge and propagator for a new scan.
|
|
60
|
+
///
|
|
61
|
+
/// # Errors
|
|
62
|
+
///
|
|
63
|
+
/// Raises `RuntimeError` after fork.
|
|
64
|
+
fn scan_parts(&self, ruby: &Ruby) -> Result<(Bridge, Arc<TextMapCompositePropagator>), Error> {
|
|
65
|
+
self.fork.check(ruby)?;
|
|
66
|
+
Ok((self.bridge.clone(), Arc::clone(&self.propagator)))
|
|
29
67
|
}
|
|
30
68
|
}
|
|
31
69
|
|
|
32
70
|
#[magnus::wrap(class = "Prosody::NativePublishedValue")]
|
|
33
71
|
pub(crate) struct NativePublishedValue {
|
|
34
|
-
pub(crate) inner: SharedValueReader<
|
|
35
|
-
pub(crate)
|
|
72
|
+
pub(crate) inner: SharedValueReader<JsonValue>,
|
|
73
|
+
pub(crate) reads: Reads,
|
|
36
74
|
}
|
|
37
75
|
|
|
38
76
|
impl NativePublishedValue {
|
|
39
77
|
fn get(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
40
78
|
let inner = Arc::clone(&this.inner);
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
.wait_for(ruby, async move { inner.get(key).await }, Span::current())?
|
|
44
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
45
|
-
match value {
|
|
46
|
-
Some(value) => serialize(ruby, &value),
|
|
47
|
-
None => Ok(ruby.qnil().as_value()),
|
|
48
|
-
}
|
|
79
|
+
this.reads
|
|
80
|
+
.read_json(ruby, async move { inner.get(key).await })
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
#[magnus::wrap(class = "Prosody::NativePublishedMap")]
|
|
53
85
|
pub(crate) struct NativePublishedMap {
|
|
54
|
-
pub(crate) inner: SharedMapReader<
|
|
55
|
-
pub(crate)
|
|
56
|
-
pub(crate) propagator: Arc<TextMapCompositePropagator>,
|
|
86
|
+
pub(crate) inner: SharedMapReader<JsonValue>,
|
|
87
|
+
pub(crate) reads: Reads,
|
|
57
88
|
}
|
|
58
89
|
|
|
59
90
|
impl NativePublishedMap {
|
|
60
91
|
fn get(ruby: &Ruby, this: &Self, key: String, map_key: String) -> Result<Value, Error> {
|
|
61
92
|
let inner = Arc::clone(&this.inner);
|
|
62
|
-
|
|
63
|
-
.
|
|
64
|
-
.wait_for(
|
|
65
|
-
ruby,
|
|
66
|
-
async move { inner.get(key, map_key).await },
|
|
67
|
-
Span::current(),
|
|
68
|
-
)?
|
|
69
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
70
|
-
match value {
|
|
71
|
-
Some(value) => serialize(ruby, &value),
|
|
72
|
-
None => Ok(ruby.qnil().as_value()),
|
|
73
|
-
}
|
|
93
|
+
this.reads
|
|
94
|
+
.read_json(ruby, async move { inner.get(key, map_key).await })
|
|
74
95
|
}
|
|
75
96
|
|
|
76
97
|
fn get_many(
|
|
@@ -81,25 +102,34 @@ impl NativePublishedMap {
|
|
|
81
102
|
) -> Result<Value, Error> {
|
|
82
103
|
let inner = Arc::clone(&this.inner);
|
|
83
104
|
let values = this
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
ruby,
|
|
87
|
-
async move { inner.get_many(key, map_keys).await },
|
|
88
|
-
Span::current(),
|
|
89
|
-
)?
|
|
90
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
105
|
+
.reads
|
|
106
|
+
.read(ruby, async move { inner.get_many(key, map_keys).await })?;
|
|
91
107
|
serialize(ruby, &values)
|
|
92
108
|
}
|
|
93
109
|
|
|
110
|
+
fn contains_many(
|
|
111
|
+
ruby: &Ruby,
|
|
112
|
+
this: &Self,
|
|
113
|
+
key: String,
|
|
114
|
+
map_keys: Vec<String>,
|
|
115
|
+
) -> Result<Vec<bool>, Error> {
|
|
116
|
+
let inner = Arc::clone(&this.inner);
|
|
117
|
+
this.reads.read(
|
|
118
|
+
ruby,
|
|
119
|
+
async move { inner.contains_many(key, map_keys).await },
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fn is_empty(ruby: &Ruby, this: &Self, key: String) -> Result<bool, Error> {
|
|
124
|
+
let inner = Arc::clone(&this.inner);
|
|
125
|
+
this.reads
|
|
126
|
+
.read(ruby, async move { inner.is_empty(key).await })
|
|
127
|
+
}
|
|
128
|
+
|
|
94
129
|
fn contains_key(ruby: &Ruby, this: &Self, key: String, map_key: String) -> Result<bool, Error> {
|
|
95
130
|
let inner = Arc::clone(&this.inner);
|
|
96
|
-
this.
|
|
97
|
-
.
|
|
98
|
-
ruby,
|
|
99
|
-
async move { inner.contains_key(key, map_key).await },
|
|
100
|
-
Span::current(),
|
|
101
|
-
)?
|
|
102
|
-
.map_err(|error| read_error(ruby, &error))
|
|
131
|
+
this.reads
|
|
132
|
+
.read(ruby, async move { inner.contains_key(key, map_key).await })
|
|
103
133
|
}
|
|
104
134
|
|
|
105
135
|
fn scan(
|
|
@@ -107,23 +137,12 @@ impl NativePublishedMap {
|
|
|
107
137
|
this: &Self,
|
|
108
138
|
key: String,
|
|
109
139
|
direction: StaticSymbol,
|
|
140
|
+
options: RHash,
|
|
110
141
|
) -> Result<NativeJsonMapScan, Error> {
|
|
111
|
-
let
|
|
112
|
-
let
|
|
113
|
-
let
|
|
114
|
-
|
|
115
|
-
.wait_for(
|
|
116
|
-
ruby,
|
|
117
|
-
async move { inner.stream(key, direction).await },
|
|
118
|
-
Span::current(),
|
|
119
|
-
)?
|
|
120
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
121
|
-
published_map_scan(
|
|
122
|
-
ruby,
|
|
123
|
-
cursor,
|
|
124
|
-
this.bridge.clone(),
|
|
125
|
-
Arc::clone(&this.propagator),
|
|
126
|
-
)
|
|
142
|
+
let query = key_query(ruby, direction, options)?;
|
|
143
|
+
let (bridge, propagator) = this.reads.scan_parts(ruby)?;
|
|
144
|
+
let entries = this.inner.entries(key).with_query(query).stream();
|
|
145
|
+
Ok(NativeJsonMapScan::new(entries, bridge, propagator))
|
|
127
146
|
}
|
|
128
147
|
|
|
129
148
|
fn keys(
|
|
@@ -131,98 +150,95 @@ impl NativePublishedMap {
|
|
|
131
150
|
this: &Self,
|
|
132
151
|
key: String,
|
|
133
152
|
direction: StaticSymbol,
|
|
153
|
+
options: RHash,
|
|
134
154
|
) -> Result<NativeMapKeyScan, Error> {
|
|
135
|
-
let
|
|
155
|
+
let query = key_query(ruby, direction, options)?;
|
|
156
|
+
let (bridge, propagator) = this.reads.scan_parts(ruby)?;
|
|
157
|
+
let keys = this.inner.keys(key).with_query(query).stream();
|
|
158
|
+
Ok(NativeMapKeyScan::new(keys, bridge, propagator))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[magnus::wrap(class = "Prosody::NativePublishedSet")]
|
|
163
|
+
pub(crate) struct NativePublishedSet {
|
|
164
|
+
pub(crate) inner: SharedSetReader,
|
|
165
|
+
pub(crate) reads: Reads,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
impl NativePublishedSet {
|
|
169
|
+
fn contains(ruby: &Ruby, this: &Self, key: String, member: String) -> Result<bool, Error> {
|
|
136
170
|
let inner = Arc::clone(&this.inner);
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
this.reads
|
|
172
|
+
.read(ruby, async move { inner.contains(key, member).await })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
fn contains_many(
|
|
176
|
+
ruby: &Ruby,
|
|
177
|
+
this: &Self,
|
|
178
|
+
key: String,
|
|
179
|
+
members: Vec<String>,
|
|
180
|
+
) -> Result<Vec<bool>, Error> {
|
|
181
|
+
let inner = Arc::clone(&this.inner);
|
|
182
|
+
this.reads
|
|
183
|
+
.read(ruby, async move { inner.contains_many(key, members).await })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn is_empty(ruby: &Ruby, this: &Self, key: String) -> Result<bool, Error> {
|
|
187
|
+
let inner = Arc::clone(&this.inner);
|
|
188
|
+
this.reads
|
|
189
|
+
.read(ruby, async move { inner.is_empty(key).await })
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Opens a member cursor. Members are bare `String` keys, so the map key
|
|
193
|
+
/// cursor carries them.
|
|
194
|
+
fn keys(
|
|
195
|
+
ruby: &Ruby,
|
|
196
|
+
this: &Self,
|
|
197
|
+
key: String,
|
|
198
|
+
direction: StaticSymbol,
|
|
199
|
+
options: RHash,
|
|
200
|
+
) -> Result<NativeMapKeyScan, Error> {
|
|
201
|
+
let query = key_query(ruby, direction, options)?;
|
|
202
|
+
let (bridge, propagator) = this.reads.scan_parts(ruby)?;
|
|
203
|
+
let members = this.inner.keys(key).with_query(query).stream();
|
|
204
|
+
Ok(NativeMapKeyScan::new(members, bridge, propagator))
|
|
151
205
|
}
|
|
152
206
|
}
|
|
153
207
|
|
|
154
208
|
#[magnus::wrap(class = "Prosody::NativePublishedDeque")]
|
|
155
209
|
pub(crate) struct NativePublishedDeque {
|
|
156
|
-
pub(crate) inner: SharedDequeReader<
|
|
157
|
-
pub(crate)
|
|
158
|
-
pub(crate) propagator: Arc<TextMapCompositePropagator>,
|
|
210
|
+
pub(crate) inner: SharedDequeReader<JsonValue>,
|
|
211
|
+
pub(crate) reads: Reads,
|
|
159
212
|
}
|
|
160
213
|
|
|
161
214
|
impl NativePublishedDeque {
|
|
162
215
|
fn get(ruby: &Ruby, this: &Self, key: String, index: usize) -> Result<Value, Error> {
|
|
163
216
|
let inner = Arc::clone(&this.inner);
|
|
164
|
-
|
|
165
|
-
.
|
|
166
|
-
.wait_for(
|
|
167
|
-
ruby,
|
|
168
|
-
async move { inner.get(key, index).await },
|
|
169
|
-
Span::current(),
|
|
170
|
-
)?
|
|
171
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
172
|
-
match value {
|
|
173
|
-
Some(value) => serialize(ruby, &value),
|
|
174
|
-
None => Ok(ruby.qnil().as_value()),
|
|
175
|
-
}
|
|
217
|
+
this.reads
|
|
218
|
+
.read_json(ruby, async move { inner.get(key, index).await })
|
|
176
219
|
}
|
|
177
220
|
|
|
178
221
|
fn length(ruby: &Ruby, this: &Self, key: String) -> Result<usize, Error> {
|
|
179
222
|
let inner = Arc::clone(&this.inner);
|
|
180
|
-
this.
|
|
181
|
-
.wait_for(ruby, async move { inner.len(key).await }, Span::current())?
|
|
182
|
-
.map_err(|error| read_error(ruby, &error))
|
|
223
|
+
this.reads.read(ruby, async move { inner.len(key).await })
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
fn is_empty(ruby: &Ruby, this: &Self, key: String) -> Result<bool, Error> {
|
|
186
227
|
let inner = Arc::clone(&this.inner);
|
|
187
|
-
this.
|
|
188
|
-
.
|
|
189
|
-
ruby,
|
|
190
|
-
async move { inner.is_empty(key).await },
|
|
191
|
-
Span::current(),
|
|
192
|
-
)?
|
|
193
|
-
.map_err(|error| read_error(ruby, &error))
|
|
228
|
+
this.reads
|
|
229
|
+
.read(ruby, async move { inner.is_empty(key).await })
|
|
194
230
|
}
|
|
195
231
|
|
|
196
232
|
fn peek_front(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
197
233
|
let inner = Arc::clone(&this.inner);
|
|
198
|
-
|
|
199
|
-
.
|
|
200
|
-
.wait_for(
|
|
201
|
-
ruby,
|
|
202
|
-
async move { inner.peek_front(key).await },
|
|
203
|
-
Span::current(),
|
|
204
|
-
)?
|
|
205
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
206
|
-
match value {
|
|
207
|
-
Some(value) => serialize(ruby, &value),
|
|
208
|
-
None => Ok(ruby.qnil().as_value()),
|
|
209
|
-
}
|
|
234
|
+
this.reads
|
|
235
|
+
.read_json(ruby, async move { inner.peek_front(key).await })
|
|
210
236
|
}
|
|
211
237
|
|
|
212
238
|
fn peek_back(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
213
239
|
let inner = Arc::clone(&this.inner);
|
|
214
|
-
|
|
215
|
-
.
|
|
216
|
-
.wait_for(
|
|
217
|
-
ruby,
|
|
218
|
-
async move { inner.peek_back(key).await },
|
|
219
|
-
Span::current(),
|
|
220
|
-
)?
|
|
221
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
222
|
-
match value {
|
|
223
|
-
Some(value) => serialize(ruby, &value),
|
|
224
|
-
None => Ok(ruby.qnil().as_value()),
|
|
225
|
-
}
|
|
240
|
+
this.reads
|
|
241
|
+
.read_json(ruby, async move { inner.peek_back(key).await })
|
|
226
242
|
}
|
|
227
243
|
|
|
228
244
|
fn scan(
|
|
@@ -230,23 +246,12 @@ impl NativePublishedDeque {
|
|
|
230
246
|
this: &Self,
|
|
231
247
|
key: String,
|
|
232
248
|
direction: StaticSymbol,
|
|
249
|
+
options: RHash,
|
|
233
250
|
) -> Result<NativeJsonDequeScan, Error> {
|
|
234
|
-
let
|
|
235
|
-
let
|
|
236
|
-
let
|
|
237
|
-
|
|
238
|
-
.wait_for(
|
|
239
|
-
ruby,
|
|
240
|
-
async move { inner.stream(key, direction).await },
|
|
241
|
-
Span::current(),
|
|
242
|
-
)?
|
|
243
|
-
.map_err(|error| read_error(ruby, &error))?;
|
|
244
|
-
published_deque_scan(
|
|
245
|
-
ruby,
|
|
246
|
-
cursor,
|
|
247
|
-
this.bridge.clone(),
|
|
248
|
-
Arc::clone(&this.propagator),
|
|
249
|
-
)
|
|
251
|
+
let query = position_query(ruby, direction, options)?;
|
|
252
|
+
let (bridge, propagator) = this.reads.scan_parts(ruby)?;
|
|
253
|
+
let values = this.inner.values(key).with_query(query).stream();
|
|
254
|
+
Ok(NativeJsonDequeScan::new(values, bridge, propagator))
|
|
250
255
|
}
|
|
251
256
|
}
|
|
252
257
|
|
|
@@ -259,8 +264,22 @@ pub(crate) fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
259
264
|
map.define_method("get", method!(NativePublishedMap::get, 2))?;
|
|
260
265
|
map.define_method("get_many", method!(NativePublishedMap::get_many, 2))?;
|
|
261
266
|
map.define_method("contains_key", method!(NativePublishedMap::contains_key, 2))?;
|
|
262
|
-
map.define_method(
|
|
263
|
-
|
|
267
|
+
map.define_method(
|
|
268
|
+
"contains_many",
|
|
269
|
+
method!(NativePublishedMap::contains_many, 2),
|
|
270
|
+
)?;
|
|
271
|
+
map.define_method("is_empty", method!(NativePublishedMap::is_empty, 1))?;
|
|
272
|
+
map.define_method("scan", method!(NativePublishedMap::scan, 3))?;
|
|
273
|
+
map.define_method("keys", method!(NativePublishedMap::keys, 3))?;
|
|
274
|
+
|
|
275
|
+
let set = module.define_class(id!(ruby, "NativePublishedSet"), ruby.class_object())?;
|
|
276
|
+
set.define_method("contains", method!(NativePublishedSet::contains, 2))?;
|
|
277
|
+
set.define_method(
|
|
278
|
+
"contains_many",
|
|
279
|
+
method!(NativePublishedSet::contains_many, 2),
|
|
280
|
+
)?;
|
|
281
|
+
set.define_method("is_empty", method!(NativePublishedSet::is_empty, 1))?;
|
|
282
|
+
set.define_method("keys", method!(NativePublishedSet::keys, 3))?;
|
|
264
283
|
|
|
265
284
|
let deque = module.define_class(id!(ruby, "NativePublishedDeque"), ruby.class_object())?;
|
|
266
285
|
deque.define_method("get", method!(NativePublishedDeque::get, 2))?;
|
|
@@ -268,6 +287,6 @@ pub(crate) fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
268
287
|
deque.define_method("is_empty", method!(NativePublishedDeque::is_empty, 1))?;
|
|
269
288
|
deque.define_method("peek_front", method!(NativePublishedDeque::peek_front, 1))?;
|
|
270
289
|
deque.define_method("peek_back", method!(NativePublishedDeque::peek_back, 1))?;
|
|
271
|
-
deque.define_method("scan", method!(NativePublishedDeque::scan,
|
|
290
|
+
deque.define_method("scan", method!(NativePublishedDeque::scan, 3))?;
|
|
272
291
|
Ok(())
|
|
273
292
|
}
|
|
@@ -80,9 +80,10 @@ impl ResultSender {
|
|
|
80
80
|
return false;
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
// A result with no JSON form is a handler mistake, so it retries.
|
|
83
84
|
if is_success {
|
|
84
85
|
let result = deserialize(ruby, result)
|
|
85
|
-
.map_err(|error| ProcessingError::
|
|
86
|
+
.map_err(|error| ProcessingError::Transient(error.to_string()));
|
|
86
87
|
if result_tx.send(result).is_err() {
|
|
87
88
|
debug!("discarding result; receiver went away");
|
|
88
89
|
}
|
data/ext/prosody/src/util.rs
CHANGED
|
@@ -13,7 +13,11 @@ use prosody::tracing::{
|
|
|
13
13
|
TracingError, flush_telemetry as core_flush_telemetry, initialize_tracing,
|
|
14
14
|
shutdown_telemetry as core_shutdown_telemetry,
|
|
15
15
|
};
|
|
16
|
+
use std::fmt;
|
|
17
|
+
use std::io::{self, Write};
|
|
16
18
|
use std::mem::{ManuallyDrop, forget};
|
|
19
|
+
use std::process;
|
|
20
|
+
use std::time::Duration;
|
|
17
21
|
use tokio::runtime::{EnterGuard, Handle};
|
|
18
22
|
use tracing::{error, warn};
|
|
19
23
|
|
|
@@ -179,6 +183,65 @@ impl Drop for RubyDrop {
|
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
/// Writes one line to standard error.
|
|
187
|
+
///
|
|
188
|
+
/// Use it only where no logger exists: before tracing starts, or when the
|
|
189
|
+
/// Ruby logger itself fails.
|
|
190
|
+
pub(crate) fn report(message: fmt::Arguments<'_>) {
|
|
191
|
+
drop(writeln!(io::stderr().lock(), "{message}"));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Converts a Ruby number of seconds into a [`Duration`].
|
|
195
|
+
///
|
|
196
|
+
/// # Errors
|
|
197
|
+
///
|
|
198
|
+
/// Returns an error that names `option` if the value is negative, not
|
|
199
|
+
/// finite, or too large for a [`Duration`].
|
|
200
|
+
pub(crate) fn seconds(option: &str, value: f64) -> Result<Duration, String> {
|
|
201
|
+
Duration::try_from_secs_f64(value)
|
|
202
|
+
.map_err(|_| format!("{option}: must be a finite, non-negative number of seconds"))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// Detects the use of a native object in a forked child process.
|
|
206
|
+
///
|
|
207
|
+
/// The Tokio runtime and the bridge thread do not survive `fork`. A native
|
|
208
|
+
/// object that waits on them in a child waits forever, so it raises instead.
|
|
209
|
+
#[derive(Clone, Copy, Debug)]
|
|
210
|
+
pub struct ForkGuard {
|
|
211
|
+
/// The process that created the object.
|
|
212
|
+
pid: u32,
|
|
213
|
+
/// The Ruby class name that the error message shows.
|
|
214
|
+
class: &'static str,
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
impl ForkGuard {
|
|
218
|
+
/// Records the current process for an object of the Ruby `class`.
|
|
219
|
+
pub fn new(class: &'static str) -> Self {
|
|
220
|
+
Self {
|
|
221
|
+
pid: process::id(),
|
|
222
|
+
class,
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/// Checks that the current process created the object.
|
|
227
|
+
///
|
|
228
|
+
/// # Errors
|
|
229
|
+
///
|
|
230
|
+
/// Returns a `RuntimeError` in a forked child process.
|
|
231
|
+
pub fn check(self, ruby: &Ruby) -> Result<(), Error> {
|
|
232
|
+
if process::id() == self.pid {
|
|
233
|
+
return Ok(());
|
|
234
|
+
}
|
|
235
|
+
Err(Error::new(
|
|
236
|
+
ruby.exception_runtime_error(),
|
|
237
|
+
format!(
|
|
238
|
+
"{} cannot be used after fork. Create a new client in the child process.",
|
|
239
|
+
self.class
|
|
240
|
+
),
|
|
241
|
+
))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
182
245
|
/// Ensures a Tokio runtime context exists, entering one if necessary.
|
|
183
246
|
///
|
|
184
247
|
/// Only creates a runtime guard when not already in a runtime context, avoiding
|
|
@@ -207,14 +270,13 @@ pub fn ensure_runtime_context(ruby: &Ruby) -> Option<EnterGuard<'static>> {
|
|
|
207
270
|
let bridge = BRIDGE.get_or_init(|| Bridge::new(ruby));
|
|
208
271
|
|
|
209
272
|
// Initialize tracing for observability
|
|
210
|
-
#[allow(clippy::print_stderr, reason = "logger has not been initialized yet")]
|
|
211
273
|
TRACING_INIT.get_or_init(|| {
|
|
212
274
|
let maybe_logger = Logger::new(ruby, bridge.clone())
|
|
213
|
-
.inspect_err(|error|
|
|
275
|
+
.inspect_err(|error| report(format_args!("failed to create logger: {error:#}")))
|
|
214
276
|
.ok();
|
|
215
277
|
|
|
216
278
|
if let Err(error) = initialize_tracing(maybe_logger) {
|
|
217
|
-
|
|
279
|
+
report(format_args!("failed to initialize tracing: {error:#}"));
|
|
218
280
|
}
|
|
219
281
|
});
|
|
220
282
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosody
|
|
4
|
+
# Ruby-side lifecycle helpers for the native {Prosody::Client}.
|
|
5
|
+
class Client
|
|
6
|
+
# Creates a client, yields it, and shuts it down when the block exits.
|
|
7
|
+
# The client shuts down also when the block raises. Repeated shutdown
|
|
8
|
+
# calls wait for the same operation, so the block can also call
|
|
9
|
+
# {#shutdown}.
|
|
10
|
+
#
|
|
11
|
+
# @param config [Hash, Configuration] Client configuration
|
|
12
|
+
# @yieldparam client [Client] the new client
|
|
13
|
+
# @return [Object] the value of the block
|
|
14
|
+
# @raise [ArgumentError] if no block is given or the configuration is invalid
|
|
15
|
+
# @raise [RuntimeError] if client initialization or shutdown fails
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# Prosody::Client.open(bootstrap_servers: "localhost:9092") do |client|
|
|
19
|
+
# client.send_message("my-topic", "key", {"hello" => "world"})
|
|
20
|
+
# end
|
|
21
|
+
def self.open(config)
|
|
22
|
+
raise ArgumentError, "Prosody::Client.open requires a block" unless block_given?
|
|
23
|
+
|
|
24
|
+
client = new(config)
|
|
25
|
+
begin
|
|
26
|
+
yield client
|
|
27
|
+
ensure
|
|
28
|
+
client.shutdown
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -124,14 +124,18 @@ module Prosody
|
|
|
124
124
|
# Interval between offset commit operations (in seconds).
|
|
125
125
|
config_param :commit_interval, converter: ->(v) { duration_converter(v) }
|
|
126
126
|
|
|
127
|
+
# Interval between librdkafka statistics reports (in seconds).
|
|
128
|
+
# Env: PROSODY_STATISTICS_INTERVAL. Default: 5 seconds.
|
|
129
|
+
config_param :statistics_interval, converter: ->(v) { duration_converter(v) }
|
|
130
|
+
|
|
127
131
|
# Base delay for retry operations (in seconds).
|
|
128
132
|
config_param :retry_base, converter: ->(v) { duration_converter(v) }
|
|
129
133
|
|
|
130
134
|
# Maximum delay between retries (in seconds).
|
|
131
135
|
config_param :max_retry_delay, converter: ->(v) { duration_converter(v) }
|
|
132
136
|
|
|
133
|
-
#
|
|
134
|
-
# Must be at least 1. Default: 8192.
|
|
137
|
+
# Capacity of the producer idempotence cache and of the consumer
|
|
138
|
+
# deduplication cache. Must be at least 1. Default: 8192.
|
|
135
139
|
config_param :idempotence_cache_size, converter: ->(v) { Integer(v) }
|
|
136
140
|
|
|
137
141
|
# Version string for cache-busting deduplication hashes. Changing this
|
|
@@ -303,7 +307,7 @@ module Prosody
|
|
|
303
307
|
|
|
304
308
|
# Keyed-state collections to register before subscribe.
|
|
305
309
|
#
|
|
306
|
-
# Accepts an array of StateDefinition objects (from Prosody.value/map/deque
|
|
310
|
+
# Accepts an array of StateDefinition objects (from Prosody.value/map/set/deque
|
|
307
311
|
# and their message_* siblings) or already-serialized registration hashes.
|
|
308
312
|
# Duplicate names within the set are rejected by the native layer.
|
|
309
313
|
config_param :state_collections,
|
|
@@ -312,13 +316,15 @@ module Prosody
|
|
|
312
316
|
list.map { |d| d.respond_to?(:to_state_config) ? d.to_state_config : d }
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
# Subsystem under which published JSON collections are advertised.
|
|
319
|
+
# Subsystem under which published JSON and set collections are advertised.
|
|
316
320
|
# Uses PROSODY_SUBSYSTEM when omitted. Published collections require it.
|
|
317
321
|
config_param :subsystem, converter: lambda(&:to_s)
|
|
318
322
|
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
323
|
+
# Directory for the local keyed-state caches. Each consumer opens its
|
|
324
|
+
# cache in a new subdirectory and removes it when the consumer stops, so
|
|
325
|
+
# clients can share the directory. Falls back to the
|
|
326
|
+
# PROSODY_STATE_CACHE_DIR environment variable, then to
|
|
327
|
+
# <temp>/prosody/keyed-state. Must not be an empty string.
|
|
322
328
|
config_param :state_cache_dir, converter: lambda(&:to_s)
|
|
323
329
|
|
|
324
330
|
# Capacity of the owning keyed-state cache, such as "64 MiB". Uses
|
|
@@ -326,6 +332,13 @@ module Prosody
|
|
|
326
332
|
# selects its default.
|
|
327
333
|
config_param :state_owned_cache_size, converter: lambda(&:to_s)
|
|
328
334
|
|
|
335
|
+
# Bytes of in-memory writes the local keyed-state cache holds for each
|
|
336
|
+
# assigned partition before it flushes them to disk, such as "16 MiB".
|
|
337
|
+
# Memory use scales with the number of assigned partitions. Uses
|
|
338
|
+
# PROSODY_STATE_MEMTABLE_SIZE when omitted. Otherwise, the engine uses
|
|
339
|
+
# its default of 64 MiB.
|
|
340
|
+
config_param :state_memtable_size, converter: lambda(&:to_s)
|
|
341
|
+
|
|
329
342
|
# Capacity of the published-state read-through cache, such as "1 MiB".
|
|
330
343
|
# Uses PROSODY_STATE_READ_CACHE_SIZE when omitted. It then uses the owned
|
|
331
344
|
# cache size when set, or 1 MiB when both sizes are unset.
|
|
@@ -336,11 +349,6 @@ module Prosody
|
|
|
336
349
|
config_param :state_read_cache,
|
|
337
350
|
converter: ->(v) { (v == true || v == false) ? v : Float(v) }
|
|
338
351
|
|
|
339
|
-
# Delay in whole seconds between staging a provisional cell and the
|
|
340
|
-
# keyed-state recovery sweep. Every registered TTL must strictly exceed this.
|
|
341
|
-
# Must be a whole number of seconds >= 1 (validated natively).
|
|
342
|
-
config_param :state_recovery_delay, converter: ->(v) { duration_converter(v) }
|
|
343
|
-
|
|
344
352
|
# Operation mode of the client.
|
|
345
353
|
#
|
|
346
354
|
# Valid values:
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Prosody
|
|
4
|
+
# The demand a handler call serves, from +context.demand+.
|
|
5
|
+
#
|
|
6
|
+
# +kind+ is +:normal+ for a normal delivery and +:failure+ for a retry after
|
|
7
|
+
# a failure. +retries+ is the number of retries: 0 for a normal delivery and
|
|
8
|
+
# 1 on the first retry. After Prosody defers an event, the count starts
|
|
9
|
+
# again at 1. The count is an estimate. Keep an exact attempt count in
|
|
10
|
+
# keyed state if the handler needs one.
|
|
11
|
+
#
|
|
12
|
+
# @example Alert only on a retry
|
|
13
|
+
# def on_message(context, message)
|
|
14
|
+
# alert(message.key) if context.demand.failure?
|
|
15
|
+
# end
|
|
16
|
+
Demand = Data.define(:kind, :retries) do
|
|
17
|
+
# Whether this is a normal delivery.
|
|
18
|
+
#
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def normal? = kind == :normal
|
|
21
|
+
|
|
22
|
+
# Whether this is a retry after a failure.
|
|
23
|
+
#
|
|
24
|
+
# @return [Boolean]
|
|
25
|
+
def failure? = kind == :failure
|
|
26
|
+
end
|
|
27
|
+
end
|