prosody 0.5.1 → 0.6.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/.config/rail.toml +30 -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 +14 -0
- data/CONFIGURATION.md +20 -17
- data/Cargo.lock +300 -274
- data/Cargo.toml +11 -8
- data/README.md +104 -25
- data/examples/keyed_state.rb +10 -2
- data/examples/keyed_state.rbs +1 -0
- data/ext/prosody/Cargo.toml +2 -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/tracing_util.rs +0 -8
- data/ext/prosody/src/util.rs +65 -17
- 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
|
}
|
|
@@ -32,14 +32,6 @@ use std::collections::HashMap;
|
|
|
32
32
|
/// - OpenTelemetry module is not available in Ruby
|
|
33
33
|
/// - Context extraction or propagation fails
|
|
34
34
|
/// - Ruby-to-Rust type conversion fails
|
|
35
|
-
///
|
|
36
|
-
/// # Example
|
|
37
|
-
///
|
|
38
|
-
/// ```rust
|
|
39
|
-
/// let context = extract_opentelemetry_context(ruby, &propagator)?;
|
|
40
|
-
/// let span = info_span!("operation");
|
|
41
|
-
/// span.set_parent(context);
|
|
42
|
-
/// ```
|
|
43
35
|
pub fn extract_opentelemetry_context(
|
|
44
36
|
ruby: &Ruby,
|
|
45
37
|
propagator: &TextMapCompositePropagator,
|
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
|
|
|
@@ -25,13 +29,6 @@ use tracing::{error, warn};
|
|
|
25
29
|
///
|
|
26
30
|
/// This macro requires a `ruby: &Ruby` parameter to enforce that it can only
|
|
27
31
|
/// be used within a Ruby thread context, ensuring thread safety.
|
|
28
|
-
///
|
|
29
|
-
/// # Examples
|
|
30
|
-
///
|
|
31
|
-
/// ```
|
|
32
|
-
/// let method_name = id!(ruby, "to_s");
|
|
33
|
-
/// // Use method_name with Ruby function calls
|
|
34
|
-
/// ```
|
|
35
32
|
#[macro_export]
|
|
36
33
|
macro_rules! id {
|
|
37
34
|
($ruby:expr, $str:expr) => {{
|
|
@@ -179,6 +176,65 @@ impl Drop for RubyDrop {
|
|
|
179
176
|
}
|
|
180
177
|
}
|
|
181
178
|
|
|
179
|
+
/// Writes one line to standard error.
|
|
180
|
+
///
|
|
181
|
+
/// Use it only where no logger exists: before tracing starts, or when the
|
|
182
|
+
/// Ruby logger itself fails.
|
|
183
|
+
pub(crate) fn report(message: fmt::Arguments<'_>) {
|
|
184
|
+
drop(writeln!(io::stderr().lock(), "{message}"));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// Converts a Ruby number of seconds into a [`Duration`].
|
|
188
|
+
///
|
|
189
|
+
/// # Errors
|
|
190
|
+
///
|
|
191
|
+
/// Returns an error that names `option` if the value is negative, not
|
|
192
|
+
/// finite, or too large for a [`Duration`].
|
|
193
|
+
pub(crate) fn seconds(option: &str, value: f64) -> Result<Duration, String> {
|
|
194
|
+
Duration::try_from_secs_f64(value)
|
|
195
|
+
.map_err(|_| format!("{option}: must be a finite, non-negative number of seconds"))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Detects the use of a native object in a forked child process.
|
|
199
|
+
///
|
|
200
|
+
/// The Tokio runtime and the bridge thread do not survive `fork`. A native
|
|
201
|
+
/// object that waits on them in a child waits forever, so it raises instead.
|
|
202
|
+
#[derive(Clone, Copy, Debug)]
|
|
203
|
+
pub struct ForkGuard {
|
|
204
|
+
/// The process that created the object.
|
|
205
|
+
pid: u32,
|
|
206
|
+
/// The Ruby class name that the error message shows.
|
|
207
|
+
class: &'static str,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl ForkGuard {
|
|
211
|
+
/// Records the current process for an object of the Ruby `class`.
|
|
212
|
+
pub fn new(class: &'static str) -> Self {
|
|
213
|
+
Self {
|
|
214
|
+
pid: process::id(),
|
|
215
|
+
class,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Checks that the current process created the object.
|
|
220
|
+
///
|
|
221
|
+
/// # Errors
|
|
222
|
+
///
|
|
223
|
+
/// Returns a `RuntimeError` in a forked child process.
|
|
224
|
+
pub fn check(self, ruby: &Ruby) -> Result<(), Error> {
|
|
225
|
+
if process::id() == self.pid {
|
|
226
|
+
return Ok(());
|
|
227
|
+
}
|
|
228
|
+
Err(Error::new(
|
|
229
|
+
ruby.exception_runtime_error(),
|
|
230
|
+
format!(
|
|
231
|
+
"{} cannot be used after fork. Create a new client in the child process.",
|
|
232
|
+
self.class
|
|
233
|
+
),
|
|
234
|
+
))
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
182
238
|
/// Ensures a Tokio runtime context exists, entering one if necessary.
|
|
183
239
|
///
|
|
184
240
|
/// Only creates a runtime guard when not already in a runtime context, avoiding
|
|
@@ -193,13 +249,6 @@ impl Drop for RubyDrop {
|
|
|
193
249
|
///
|
|
194
250
|
/// `Some(EnterGuard)` if we entered a new runtime (hold the guard), or `None`
|
|
195
251
|
/// if already in a runtime context.
|
|
196
|
-
///
|
|
197
|
-
/// # Examples
|
|
198
|
-
///
|
|
199
|
-
/// ```rust
|
|
200
|
-
/// let _guard = ensure_runtime_context(ruby);
|
|
201
|
-
/// // Safe to perform async operations
|
|
202
|
-
/// ```
|
|
203
252
|
pub fn ensure_runtime_context(ruby: &Ruby) -> Option<EnterGuard<'static>> {
|
|
204
253
|
let guard = Handle::try_current().is_err().then(|| RUNTIME.enter());
|
|
205
254
|
|
|
@@ -207,14 +256,13 @@ pub fn ensure_runtime_context(ruby: &Ruby) -> Option<EnterGuard<'static>> {
|
|
|
207
256
|
let bridge = BRIDGE.get_or_init(|| Bridge::new(ruby));
|
|
208
257
|
|
|
209
258
|
// Initialize tracing for observability
|
|
210
|
-
#[allow(clippy::print_stderr, reason = "logger has not been initialized yet")]
|
|
211
259
|
TRACING_INIT.get_or_init(|| {
|
|
212
260
|
let maybe_logger = Logger::new(ruby, bridge.clone())
|
|
213
|
-
.inspect_err(|error|
|
|
261
|
+
.inspect_err(|error| report(format_args!("failed to create logger: {error:#}")))
|
|
214
262
|
.ok();
|
|
215
263
|
|
|
216
264
|
if let Err(error) = initialize_tracing(maybe_logger) {
|
|
217
|
-
|
|
265
|
+
report(format_args!("failed to initialize tracing: {error:#}"));
|
|
218
266
|
}
|
|
219
267
|
});
|
|
220
268
|
|
|
@@ -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
|