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,323 @@
|
|
|
1
|
+
//! Conversion of the keyed-state options into a [`KeyedStateConfiguration`].
|
|
2
|
+
//!
|
|
3
|
+
//! This module parses each declared state collection and registers its
|
|
4
|
+
//! descriptor. It also maps the cache, read cache, and subsystem options.
|
|
5
|
+
|
|
6
|
+
use super::NativeConfiguration;
|
|
7
|
+
use crate::util::seconds;
|
|
8
|
+
use prosody::consumer::KeyedStateConfiguration;
|
|
9
|
+
use prosody::consumer::kafka_state::{message_deque_state, message_map_state, message_state};
|
|
10
|
+
use prosody::loader::KafkaLoader;
|
|
11
|
+
use prosody::state::ReadCachePolicy;
|
|
12
|
+
use prosody::state::descriptor::{StateDescriptor, deque_state, map_state, set_state, value_state};
|
|
13
|
+
use prosody::state::order_codec::Utf8KeyCodec;
|
|
14
|
+
use prosody::subsystem::SubsystemName;
|
|
15
|
+
use prosody::timers::duration::CompactDuration;
|
|
16
|
+
use prosody::{ByteSize, JsonCodec};
|
|
17
|
+
use serde::Deserialize;
|
|
18
|
+
use std::num::NonZeroUsize;
|
|
19
|
+
use std::path::PathBuf;
|
|
20
|
+
|
|
21
|
+
/// Declares one keyed-state collection to register before subscribe.
|
|
22
|
+
#[derive(Clone, Debug, Deserialize)]
|
|
23
|
+
pub(super) struct StateCollectionConfig {
|
|
24
|
+
/// The collection name. Prosody requires it to be non-empty and unique.
|
|
25
|
+
name: String,
|
|
26
|
+
|
|
27
|
+
/// The collection kind: `"value"`, `"map"`, `"set"`, or `"deque"`.
|
|
28
|
+
kind: String,
|
|
29
|
+
|
|
30
|
+
/// The item payload: `"json"` or `"message"`. A set stores membership
|
|
31
|
+
/// only, so it has no payload.
|
|
32
|
+
payload: Option<String>,
|
|
33
|
+
|
|
34
|
+
/// Optional per-write TTL in whole seconds.
|
|
35
|
+
ttl_seconds: Option<u32>,
|
|
36
|
+
|
|
37
|
+
/// Optional opt-out of transactional staging.
|
|
38
|
+
read_uncommitted: Option<bool>,
|
|
39
|
+
|
|
40
|
+
/// Whether other consumer groups may read this JSON or set collection.
|
|
41
|
+
published: Option<bool>,
|
|
42
|
+
|
|
43
|
+
/// Optional map or set keyset bound (`0..=4096`). Prosody enforces the
|
|
44
|
+
/// ceiling.
|
|
45
|
+
keyset_limit: Option<usize>,
|
|
46
|
+
|
|
47
|
+
/// Optional deque-only window capacity. Runtime-only and not persisted.
|
|
48
|
+
capacity: Option<NonZeroUsize>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// A read cache option: `false` disables the cache, and a number sets the
|
|
52
|
+
/// TTL in seconds.
|
|
53
|
+
#[derive(Clone, Copy, Debug, Deserialize)]
|
|
54
|
+
#[serde(untagged)]
|
|
55
|
+
pub(crate) enum ReadCacheConfig {
|
|
56
|
+
Disabled(bool),
|
|
57
|
+
Ttl(f64),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// The kind of a keyed-state collection.
|
|
61
|
+
enum CollectionKind {
|
|
62
|
+
/// A single-value collection.
|
|
63
|
+
Value,
|
|
64
|
+
/// A `String`-keyed ordered map.
|
|
65
|
+
Map,
|
|
66
|
+
/// A presence-only ordered set of `String` members.
|
|
67
|
+
Set,
|
|
68
|
+
/// A deque.
|
|
69
|
+
Deque,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// The item payload of a value, map, or deque collection.
|
|
73
|
+
enum CollectionPayload {
|
|
74
|
+
/// JSON values.
|
|
75
|
+
Json,
|
|
76
|
+
/// The full Kafka message the handler received.
|
|
77
|
+
Message,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Parses a collection-kind token.
|
|
81
|
+
///
|
|
82
|
+
/// # Errors
|
|
83
|
+
///
|
|
84
|
+
/// Returns a permanent-category error naming the field if the token is not
|
|
85
|
+
/// `"value"`, `"map"`, `"set"`, or `"deque"`.
|
|
86
|
+
fn parse_kind(index: usize, kind: &str) -> Result<CollectionKind, String> {
|
|
87
|
+
match kind {
|
|
88
|
+
"value" => Ok(CollectionKind::Value),
|
|
89
|
+
"map" => Ok(CollectionKind::Map),
|
|
90
|
+
"set" => Ok(CollectionKind::Set),
|
|
91
|
+
"deque" => Ok(CollectionKind::Deque),
|
|
92
|
+
other => Err(format!(
|
|
93
|
+
"state_collections[{index}].kind: expected \"value\", \"map\", \"set\", or \"deque\", \
|
|
94
|
+
got {other:?}"
|
|
95
|
+
)),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Parses a collection-payload token.
|
|
100
|
+
///
|
|
101
|
+
/// # Errors
|
|
102
|
+
///
|
|
103
|
+
/// Returns a permanent-category error naming the field if the token is not
|
|
104
|
+
/// `"json"` or `"message"`.
|
|
105
|
+
fn parse_payload(index: usize, payload: &str) -> Result<CollectionPayload, String> {
|
|
106
|
+
match payload {
|
|
107
|
+
"json" => Ok(CollectionPayload::Json),
|
|
108
|
+
"message" => Ok(CollectionPayload::Message),
|
|
109
|
+
other => Err(format!(
|
|
110
|
+
"state_collections[{index}].payload: expected \"json\" or \"message\", got {other:?}"
|
|
111
|
+
)),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Applies the options that every collection kind takes: TTL, commit mode,
|
|
116
|
+
/// and publication.
|
|
117
|
+
fn with_def<D: StateDescriptor>(descriptor: D, collection: &StateCollectionConfig) -> D {
|
|
118
|
+
let mut descriptor = descriptor;
|
|
119
|
+
if let Some(ttl) = collection.ttl_seconds {
|
|
120
|
+
descriptor = descriptor.ttl(CompactDuration::new(ttl));
|
|
121
|
+
}
|
|
122
|
+
if collection.read_uncommitted == Some(true) {
|
|
123
|
+
descriptor = descriptor.read_uncommitted();
|
|
124
|
+
}
|
|
125
|
+
if let Some(published) = collection.published {
|
|
126
|
+
descriptor = descriptor.published(published);
|
|
127
|
+
}
|
|
128
|
+
descriptor
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Maps one collection into its descriptor. Values, maps, and deques hold
|
|
132
|
+
/// JSON or messages. Sets have no payload.
|
|
133
|
+
///
|
|
134
|
+
/// # Errors
|
|
135
|
+
///
|
|
136
|
+
/// Returns a permanent-category error when a host value cannot be mapped into
|
|
137
|
+
/// a Prosody type.
|
|
138
|
+
fn register_state_collection(
|
|
139
|
+
keyed: &mut KeyedStateConfiguration,
|
|
140
|
+
index: usize,
|
|
141
|
+
collection: &StateCollectionConfig,
|
|
142
|
+
) -> Result<(), String> {
|
|
143
|
+
let kind = parse_kind(index, &collection.kind)?;
|
|
144
|
+
let payload = collection
|
|
145
|
+
.payload
|
|
146
|
+
.as_deref()
|
|
147
|
+
.map(|payload| parse_payload(index, payload))
|
|
148
|
+
.transpose()?;
|
|
149
|
+
let keyset_limit = keyset_limit(collection.keyset_limit, &kind, index)?;
|
|
150
|
+
let capacity = capacity(collection.capacity, &kind, index)?;
|
|
151
|
+
|
|
152
|
+
let name = collection.name.as_str();
|
|
153
|
+
match (kind, payload) {
|
|
154
|
+
(CollectionKind::Value, Some(CollectionPayload::Json)) => {
|
|
155
|
+
let _ = keyed.register(with_def(value_state::<JsonCodec>(name), collection));
|
|
156
|
+
}
|
|
157
|
+
(CollectionKind::Map, Some(CollectionPayload::Json)) => {
|
|
158
|
+
let mut descriptor = with_def(map_state::<Utf8KeyCodec, JsonCodec>(name), collection);
|
|
159
|
+
if let Some(limit) = keyset_limit {
|
|
160
|
+
descriptor = descriptor.keyset_limit(limit);
|
|
161
|
+
}
|
|
162
|
+
let _ = keyed.register(descriptor);
|
|
163
|
+
}
|
|
164
|
+
(CollectionKind::Deque, Some(CollectionPayload::Json)) => {
|
|
165
|
+
let mut descriptor = with_def(deque_state::<JsonCodec>(name), collection);
|
|
166
|
+
if let Some(capacity) = capacity {
|
|
167
|
+
descriptor = descriptor.capacity(capacity);
|
|
168
|
+
}
|
|
169
|
+
let _ = keyed.register(descriptor);
|
|
170
|
+
}
|
|
171
|
+
(CollectionKind::Value, Some(CollectionPayload::Message)) => {
|
|
172
|
+
let descriptor = message_state::<KafkaLoader<JsonCodec>>(name);
|
|
173
|
+
let _ = keyed.register(with_def(descriptor, collection));
|
|
174
|
+
}
|
|
175
|
+
(CollectionKind::Map, Some(CollectionPayload::Message)) => {
|
|
176
|
+
let descriptor = message_map_state::<Utf8KeyCodec, KafkaLoader<JsonCodec>>(name);
|
|
177
|
+
let mut descriptor = with_def(descriptor, collection);
|
|
178
|
+
if let Some(limit) = keyset_limit {
|
|
179
|
+
descriptor = descriptor.keyset_limit(limit);
|
|
180
|
+
}
|
|
181
|
+
let _ = keyed.register(descriptor);
|
|
182
|
+
}
|
|
183
|
+
(CollectionKind::Deque, Some(CollectionPayload::Message)) => {
|
|
184
|
+
let descriptor = message_deque_state::<KafkaLoader<JsonCodec>>(name);
|
|
185
|
+
let mut descriptor = with_def(descriptor, collection);
|
|
186
|
+
if let Some(capacity) = capacity {
|
|
187
|
+
descriptor = descriptor.capacity(capacity);
|
|
188
|
+
}
|
|
189
|
+
let _ = keyed.register(descriptor);
|
|
190
|
+
}
|
|
191
|
+
(CollectionKind::Set, None) => {
|
|
192
|
+
let mut descriptor = with_def(set_state::<Utf8KeyCodec>(name), collection);
|
|
193
|
+
if let Some(limit) = keyset_limit {
|
|
194
|
+
descriptor = descriptor.keyset_limit(limit);
|
|
195
|
+
}
|
|
196
|
+
let _ = keyed.register(descriptor);
|
|
197
|
+
}
|
|
198
|
+
(CollectionKind::Set, Some(_)) => {
|
|
199
|
+
return Err(format!(
|
|
200
|
+
"state_collections[{index}].payload: omit it for a set collection"
|
|
201
|
+
));
|
|
202
|
+
}
|
|
203
|
+
(CollectionKind::Value | CollectionKind::Map | CollectionKind::Deque, None) => {
|
|
204
|
+
return Err(format!(
|
|
205
|
+
"state_collections[{index}].payload: required for value, map, and deque \
|
|
206
|
+
collections"
|
|
207
|
+
));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Ok(())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Rejects a keyset limit on a collection that is not a map or a set.
|
|
215
|
+
fn keyset_limit(
|
|
216
|
+
value: Option<usize>,
|
|
217
|
+
kind: &CollectionKind,
|
|
218
|
+
index: usize,
|
|
219
|
+
) -> Result<Option<usize>, String> {
|
|
220
|
+
if value.is_some() && !matches!(kind, CollectionKind::Map | CollectionKind::Set) {
|
|
221
|
+
return Err(format!(
|
|
222
|
+
"state_collections[{index}].keyset_limit: only valid for map and set collections"
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
Ok(value)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/// Rejects a capacity on a collection that is not a deque.
|
|
229
|
+
fn capacity(
|
|
230
|
+
value: Option<NonZeroUsize>,
|
|
231
|
+
kind: &CollectionKind,
|
|
232
|
+
index: usize,
|
|
233
|
+
) -> Result<Option<NonZeroUsize>, String> {
|
|
234
|
+
if value.is_some() && !matches!(kind, CollectionKind::Deque) {
|
|
235
|
+
return Err(format!(
|
|
236
|
+
"state_collections[{index}].capacity: only valid for deque collections"
|
|
237
|
+
));
|
|
238
|
+
}
|
|
239
|
+
Ok(value)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Converts a read cache option into a core policy. An absent option inherits
|
|
243
|
+
/// the default.
|
|
244
|
+
///
|
|
245
|
+
/// # Errors
|
|
246
|
+
///
|
|
247
|
+
/// Returns an error that names `option` for `true` or an invalid duration.
|
|
248
|
+
pub(crate) fn read_cache_policy(
|
|
249
|
+
option: &str,
|
|
250
|
+
config: Option<ReadCacheConfig>,
|
|
251
|
+
) -> Result<ReadCachePolicy, String> {
|
|
252
|
+
match config {
|
|
253
|
+
None => Ok(ReadCachePolicy::Inherit),
|
|
254
|
+
Some(ReadCacheConfig::Disabled(false)) => Ok(ReadCachePolicy::Disabled),
|
|
255
|
+
Some(ReadCacheConfig::Disabled(true)) => Err(format!(
|
|
256
|
+
"{option}: true is ambiguous; use a duration or false"
|
|
257
|
+
)),
|
|
258
|
+
Some(ReadCacheConfig::Ttl(value)) => seconds(option, value).map(ReadCachePolicy::Ttl),
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Builds the `KeyedStateConfiguration` by mapping each declared collection.
|
|
263
|
+
/// The normal Prosody construction path validates the result.
|
|
264
|
+
///
|
|
265
|
+
/// # Errors
|
|
266
|
+
///
|
|
267
|
+
/// Returns an error if a host value cannot be mapped.
|
|
268
|
+
pub(super) fn build_keyed_state_config(
|
|
269
|
+
config: &NativeConfiguration,
|
|
270
|
+
) -> Result<KeyedStateConfiguration, String> {
|
|
271
|
+
let mut builder = KeyedStateConfiguration::builder();
|
|
272
|
+
|
|
273
|
+
if let Some(dir) = &config.state_cache_dir {
|
|
274
|
+
builder.cache_dir(PathBuf::from(dir));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if let Some(size) = &config.state_owned_cache_size {
|
|
278
|
+
let size = size
|
|
279
|
+
.parse::<ByteSize>()
|
|
280
|
+
.map_err(|error| format!("state_owned_cache_size: {error}"))?;
|
|
281
|
+
builder.owned_cache_size(Some(size));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if let Some(size) = &config.state_memtable_size {
|
|
285
|
+
let size = size
|
|
286
|
+
.parse::<ByteSize>()
|
|
287
|
+
.map_err(|error| format!("state_memtable_size: {error}"))?;
|
|
288
|
+
builder.memtable_size(Some(size));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if let Some(size) = &config.state_read_cache_size {
|
|
292
|
+
let size = size
|
|
293
|
+
.parse::<ByteSize>()
|
|
294
|
+
.map_err(|error| format!("state_read_cache_size: {error}"))?;
|
|
295
|
+
builder.read_cache_size(Some(size));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
match read_cache_policy("state_read_cache", config.state_read_cache)? {
|
|
299
|
+
ReadCachePolicy::Inherit => {}
|
|
300
|
+
ReadCachePolicy::Disabled => {
|
|
301
|
+
builder.read_cache_ttl(None);
|
|
302
|
+
}
|
|
303
|
+
ReadCachePolicy::Ttl(ttl) => {
|
|
304
|
+
builder.read_cache_ttl(Some(ttl));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if let Some(subsystem) = &config.subsystem {
|
|
309
|
+
builder.subsystem(Some(
|
|
310
|
+
SubsystemName::try_new(subsystem).map_err(|error| error.to_string())?,
|
|
311
|
+
));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let mut keyed = builder.build().map_err(|error| error.to_string())?;
|
|
315
|
+
|
|
316
|
+
if let Some(collections) = &config.state_collections {
|
|
317
|
+
for (index, collection) in collections.iter().enumerate() {
|
|
318
|
+
register_state_collection(&mut keyed, index, collection)?;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
Ok(keyed)
|
|
323
|
+
}
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
use crate::bridge::Bridge;
|
|
14
14
|
use crate::client::config::NativeConfiguration;
|
|
15
15
|
use crate::handler::RubyHandler;
|
|
16
|
-
use crate::published::{NativePublishedDeque, NativePublishedMap, NativePublishedValue};
|
|
17
16
|
use crate::tracing_util::extract_opentelemetry_context;
|
|
18
|
-
use crate::util::ensure_runtime_context;
|
|
17
|
+
use crate::util::{ForkGuard, ensure_runtime_context};
|
|
19
18
|
use crate::{BRIDGE, ROOT_MOD, id};
|
|
20
19
|
use educe::Educe;
|
|
21
20
|
use futures::FutureExt;
|
|
@@ -28,10 +27,9 @@ use magnus::{
|
|
|
28
27
|
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
29
28
|
use prosody::cassandra::config::CassandraConfigurationBuilder;
|
|
30
29
|
use prosody::high_level::ConsumerBuilders;
|
|
31
|
-
use prosody::high_level::erased::{
|
|
32
|
-
ErasedConsumerState, ErasedReadCache, SharedHighLevelClient, new_erased,
|
|
33
|
-
};
|
|
30
|
+
use prosody::high_level::erased::{ErasedConsumerState, SharedHighLevelClient, new_erased};
|
|
34
31
|
use prosody::high_level::mode::Mode;
|
|
32
|
+
use prosody::producer::ProducerConfigurationBuilder;
|
|
35
33
|
use prosody::propagator::new_propagator;
|
|
36
34
|
use prosody::requester::ResponseError;
|
|
37
35
|
use prosody::subsystem::SubsystemName;
|
|
@@ -45,11 +43,12 @@ use tracing_opentelemetry::OpenTelemetrySpanExt;
|
|
|
45
43
|
|
|
46
44
|
/// Configuration types and conversion between Ruby and Rust representations
|
|
47
45
|
mod config;
|
|
46
|
+
mod readers;
|
|
48
47
|
mod request;
|
|
49
48
|
mod support;
|
|
50
49
|
|
|
51
50
|
pub use support::init;
|
|
52
|
-
use support::{
|
|
51
|
+
use support::{response_error, shutdown, validate_handler};
|
|
53
52
|
|
|
54
53
|
type Shutdown = Shared<BoxFuture<'static, Result<(), Arc<str>>>>;
|
|
55
54
|
|
|
@@ -72,8 +71,8 @@ pub struct Client {
|
|
|
72
71
|
bridge: Bridge,
|
|
73
72
|
/// OpenTelemetry propagator for distributed tracing
|
|
74
73
|
propagator: Arc<TextMapCompositePropagator>,
|
|
75
|
-
///
|
|
76
|
-
|
|
74
|
+
/// Refuses use in a forked child process
|
|
75
|
+
fork: ForkGuard,
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
impl Client {
|
|
@@ -108,16 +107,12 @@ impl Client {
|
|
|
108
107
|
};
|
|
109
108
|
|
|
110
109
|
let config_hash: Value = config_obj.funcall(id!(ruby, "to_hash"), ())?;
|
|
111
|
-
let native_config =
|
|
110
|
+
let native_config: NativeConfiguration = deserialize(ruby, config_hash)?;
|
|
112
111
|
let config_ref = &native_config;
|
|
112
|
+
let arg_error = |error: String| Error::new(ruby.exception_arg_error(), error);
|
|
113
113
|
|
|
114
|
-
let mode: Mode = config_ref
|
|
115
|
-
|
|
116
|
-
.map_err(|error: String| Error::new(ruby.exception_arg_error(), error))?;
|
|
117
|
-
|
|
118
|
-
let consumer_builders: ConsumerBuilders = config_ref
|
|
119
|
-
.try_into()
|
|
120
|
-
.map_err(|error: String| Error::new(ruby.exception_arg_error(), error))?;
|
|
114
|
+
let mode: Mode = config_ref.try_into().map_err(arg_error)?;
|
|
115
|
+
let consumer_builders: ConsumerBuilders = config_ref.try_into().map_err(arg_error)?;
|
|
121
116
|
|
|
122
117
|
let bridge = BRIDGE
|
|
123
118
|
.get()
|
|
@@ -126,13 +121,20 @@ impl Client {
|
|
|
126
121
|
"Bridge not initialized",
|
|
127
122
|
))?
|
|
128
123
|
.clone();
|
|
129
|
-
let cassandra =
|
|
130
|
-
let mut producer =
|
|
124
|
+
let cassandra: CassandraConfigurationBuilder = config_ref.try_into().map_err(arg_error)?;
|
|
125
|
+
let mut producer: ProducerConfigurationBuilder =
|
|
126
|
+
config_ref.try_into().map_err(arg_error)?;
|
|
131
127
|
let client = bridge
|
|
132
128
|
.wait_for(
|
|
133
129
|
ruby,
|
|
134
130
|
async move {
|
|
135
|
-
new_erased(
|
|
131
|
+
Box::pin(new_erased(
|
|
132
|
+
mode,
|
|
133
|
+
&mut producer,
|
|
134
|
+
&consumer_builders,
|
|
135
|
+
&cassandra,
|
|
136
|
+
))
|
|
137
|
+
.await
|
|
136
138
|
},
|
|
137
139
|
Span::current(),
|
|
138
140
|
)?
|
|
@@ -143,21 +145,10 @@ impl Client {
|
|
|
143
145
|
inner: client,
|
|
144
146
|
bridge,
|
|
145
147
|
propagator: Arc::new(new_propagator()),
|
|
146
|
-
|
|
148
|
+
fork: ForkGuard::new("Prosody::Client"),
|
|
147
149
|
})
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
fn check_fork(ruby: &Ruby, this: &Self) -> Result<(), Error> {
|
|
151
|
-
if std::process::id() != this.pid {
|
|
152
|
-
return Err(Error::new(
|
|
153
|
-
ruby.exception_runtime_error(),
|
|
154
|
-
"Prosody::Client cannot be used after fork. Create a new client in the child \
|
|
155
|
-
process.",
|
|
156
|
-
));
|
|
157
|
-
}
|
|
158
|
-
Ok(())
|
|
159
|
-
}
|
|
160
|
-
|
|
161
152
|
/// Returns the current state of the consumer.
|
|
162
153
|
///
|
|
163
154
|
/// The consumer can be in one of four states:
|
|
@@ -181,7 +172,7 @@ impl Client {
|
|
|
181
172
|
/// build, with the full error message from the underlying
|
|
182
173
|
/// `ModeConfigurationError`.
|
|
183
174
|
pub fn consumer_state(ruby: &Ruby, this: &Self) -> Result<StaticSymbol, Error> {
|
|
184
|
-
|
|
175
|
+
this.fork.check(ruby)?;
|
|
185
176
|
let inner = this.inner.clone();
|
|
186
177
|
let state: Result<&'static str, String> = this.bridge.wait_for(
|
|
187
178
|
ruby,
|
|
@@ -227,7 +218,7 @@ impl Client {
|
|
|
227
218
|
key: String,
|
|
228
219
|
payload: Value,
|
|
229
220
|
) -> Result<(), Error> {
|
|
230
|
-
|
|
221
|
+
this.fork.check(ruby)?;
|
|
231
222
|
let _guard = ensure_runtime_context(ruby);
|
|
232
223
|
let client = this.inner.clone();
|
|
233
224
|
let value = deserialize(ruby, payload)?;
|
|
@@ -251,7 +242,7 @@ impl Client {
|
|
|
251
242
|
|
|
252
243
|
/// Sends an excise record for a key.
|
|
253
244
|
fn excise(ruby: &Ruby, this: &Self, topic: String, key: String) -> Result<(), Error> {
|
|
254
|
-
|
|
245
|
+
this.fork.check(ruby)?;
|
|
255
246
|
let _guard = ensure_runtime_context(ruby);
|
|
256
247
|
let client = this.inner.clone();
|
|
257
248
|
let context = extract_opentelemetry_context(ruby, &this.propagator)?;
|
|
@@ -274,7 +265,7 @@ impl Client {
|
|
|
274
265
|
///
|
|
275
266
|
/// Returns an error if the handler is incomplete or subscription fails.
|
|
276
267
|
fn subscribe(ruby: &Ruby, this: &Self, handler: Value) -> Result<(), Error> {
|
|
277
|
-
|
|
268
|
+
this.fork.check(ruby)?;
|
|
278
269
|
validate_handler(ruby, handler)?;
|
|
279
270
|
let _guard = ensure_runtime_context(ruby);
|
|
280
271
|
let wrapper = RubyHandler::new(this.bridge.clone(), ruby, handler)?;
|
|
@@ -304,8 +295,8 @@ impl Client {
|
|
|
304
295
|
/// # Returns
|
|
305
296
|
///
|
|
306
297
|
/// The number of assigned partitions as a u32.
|
|
307
|
-
pub fn
|
|
308
|
-
|
|
298
|
+
pub fn assigned_partition_count(ruby: &Ruby, this: &Self) -> Result<u32, Error> {
|
|
299
|
+
this.fork.check(ruby)?;
|
|
309
300
|
let inner = this.inner.clone();
|
|
310
301
|
this.bridge.wait_for(
|
|
311
302
|
ruby,
|
|
@@ -328,7 +319,7 @@ impl Client {
|
|
|
328
319
|
///
|
|
329
320
|
/// `true` if the consumer is stalled, `false` otherwise.
|
|
330
321
|
pub fn is_stalled(ruby: &Ruby, this: &Self) -> Result<bool, Error> {
|
|
331
|
-
|
|
322
|
+
this.fork.check(ruby)?;
|
|
332
323
|
let inner = this.inner.clone();
|
|
333
324
|
this.bridge.wait_for(
|
|
334
325
|
ruby,
|
|
@@ -351,7 +342,7 @@ impl Client {
|
|
|
351
342
|
///
|
|
352
343
|
/// Returns an error if the unsubscribe operation fails.
|
|
353
344
|
fn unsubscribe(ruby: &Ruby, this: &Self) -> Result<(), Error> {
|
|
354
|
-
|
|
345
|
+
this.fork.check(ruby)?;
|
|
355
346
|
let _guard = ensure_runtime_context(ruby);
|
|
356
347
|
let client = this.inner.clone();
|
|
357
348
|
|
|
@@ -371,7 +362,7 @@ impl Client {
|
|
|
371
362
|
///
|
|
372
363
|
/// Returns an error if shutdown fails.
|
|
373
364
|
fn shutdown(ruby: &Ruby, this: &Self) -> Result<(), Error> {
|
|
374
|
-
|
|
365
|
+
this.fork.check(ruby)?;
|
|
375
366
|
let _guard = ensure_runtime_context(ruby);
|
|
376
367
|
let shutdown = this.shutdown.clone();
|
|
377
368
|
|
|
@@ -395,81 +386,4 @@ impl Client {
|
|
|
395
386
|
fn source_system(this: &Self) -> &str {
|
|
396
387
|
this.inner.source_system()
|
|
397
388
|
}
|
|
398
|
-
|
|
399
|
-
fn published_value(
|
|
400
|
-
ruby: &Ruby,
|
|
401
|
-
this: &Self,
|
|
402
|
-
subsystem: String,
|
|
403
|
-
name: String,
|
|
404
|
-
cache_seconds: Option<f64>,
|
|
405
|
-
cache_disabled: bool,
|
|
406
|
-
) -> Result<NativePublishedValue, Error> {
|
|
407
|
-
Self::check_fork(ruby, this)?;
|
|
408
|
-
let cache = read_cache(ruby, cache_seconds, cache_disabled)?;
|
|
409
|
-
let inner = this.inner.clone();
|
|
410
|
-
let reader = this
|
|
411
|
-
.bridge
|
|
412
|
-
.wait_for(
|
|
413
|
-
ruby,
|
|
414
|
-
async move { inner.value_state(subsystem, name, cache).await },
|
|
415
|
-
Span::current(),
|
|
416
|
-
)?
|
|
417
|
-
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
418
|
-
Ok(NativePublishedValue {
|
|
419
|
-
inner: reader,
|
|
420
|
-
bridge: this.bridge.clone(),
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
fn published_map(
|
|
425
|
-
ruby: &Ruby,
|
|
426
|
-
this: &Self,
|
|
427
|
-
subsystem: String,
|
|
428
|
-
name: String,
|
|
429
|
-
cache_seconds: Option<f64>,
|
|
430
|
-
cache_disabled: bool,
|
|
431
|
-
) -> Result<NativePublishedMap, Error> {
|
|
432
|
-
Self::check_fork(ruby, this)?;
|
|
433
|
-
let cache = read_cache(ruby, cache_seconds, cache_disabled)?;
|
|
434
|
-
let inner = this.inner.clone();
|
|
435
|
-
let reader = this
|
|
436
|
-
.bridge
|
|
437
|
-
.wait_for(
|
|
438
|
-
ruby,
|
|
439
|
-
async move { inner.map_state(subsystem, name, cache).await },
|
|
440
|
-
Span::current(),
|
|
441
|
-
)?
|
|
442
|
-
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
443
|
-
Ok(NativePublishedMap {
|
|
444
|
-
inner: reader,
|
|
445
|
-
bridge: this.bridge.clone(),
|
|
446
|
-
propagator: Arc::clone(&this.propagator),
|
|
447
|
-
})
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
fn published_deque(
|
|
451
|
-
ruby: &Ruby,
|
|
452
|
-
this: &Self,
|
|
453
|
-
subsystem: String,
|
|
454
|
-
name: String,
|
|
455
|
-
cache_seconds: Option<f64>,
|
|
456
|
-
cache_disabled: bool,
|
|
457
|
-
) -> Result<NativePublishedDeque, Error> {
|
|
458
|
-
Self::check_fork(ruby, this)?;
|
|
459
|
-
let cache = read_cache(ruby, cache_seconds, cache_disabled)?;
|
|
460
|
-
let inner = this.inner.clone();
|
|
461
|
-
let reader = this
|
|
462
|
-
.bridge
|
|
463
|
-
.wait_for(
|
|
464
|
-
ruby,
|
|
465
|
-
async move { inner.deque_state(subsystem, name, cache).await },
|
|
466
|
-
Span::current(),
|
|
467
|
-
)?
|
|
468
|
-
.map_err(|error| Error::new(ruby.exception_runtime_error(), error.to_string()))?;
|
|
469
|
-
Ok(NativePublishedDeque {
|
|
470
|
-
inner: reader,
|
|
471
|
-
bridge: this.bridge.clone(),
|
|
472
|
-
propagator: Arc::clone(&this.propagator),
|
|
473
|
-
})
|
|
474
|
-
}
|
|
475
389
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//! Published-state reader constructors for `Prosody::Client`.
|
|
2
|
+
//!
|
|
3
|
+
//! Each constructor opens a core reader through the bridge and wraps it in the
|
|
4
|
+
//! matching native reader class.
|
|
5
|
+
|
|
6
|
+
use super::config::read_cache_policy;
|
|
7
|
+
use super::{Client, RubyHandler};
|
|
8
|
+
use crate::handler::state_error;
|
|
9
|
+
use crate::published::{
|
|
10
|
+
NativePublishedDeque, NativePublishedMap, NativePublishedSet, NativePublishedValue, Reads,
|
|
11
|
+
};
|
|
12
|
+
use magnus::{Error, Ruby, Value};
|
|
13
|
+
use prosody::high_level::HighLevelClientError;
|
|
14
|
+
use prosody::high_level::erased::{ErasedReadCache, ErasedReaderBuildError, SharedHighLevelClient};
|
|
15
|
+
use serde_magnus::deserialize;
|
|
16
|
+
use std::fmt::Display;
|
|
17
|
+
use std::sync::Arc;
|
|
18
|
+
use tracing::Span;
|
|
19
|
+
|
|
20
|
+
/// Opens one published-state reader, and returns it with the client's
|
|
21
|
+
/// bridge, propagator, and fork guard. `open` receives the shared client and
|
|
22
|
+
/// the resolved cache policy. A state reader error raises the matching typed
|
|
23
|
+
/// state error. Any other open failure raises `RuntimeError`.
|
|
24
|
+
fn open_reader<F, Fut, R, E>(
|
|
25
|
+
ruby: &Ruby,
|
|
26
|
+
this: &Client,
|
|
27
|
+
read_cache: Value,
|
|
28
|
+
open: F,
|
|
29
|
+
) -> Result<(R, Reads), Error>
|
|
30
|
+
where
|
|
31
|
+
F: FnOnce(SharedHighLevelClient<RubyHandler>, ErasedReadCache) -> Fut,
|
|
32
|
+
Fut: Future<Output = Result<R, ErasedReaderBuildError<E>>> + Send + 'static,
|
|
33
|
+
R: Send,
|
|
34
|
+
E: Display + Send,
|
|
35
|
+
{
|
|
36
|
+
this.fork.check(ruby)?;
|
|
37
|
+
let cache = read_cache_policy("read_cache", deserialize(ruby, read_cache)?)
|
|
38
|
+
.map_err(|error| Error::new(ruby.exception_arg_error(), error))?;
|
|
39
|
+
let reader = this
|
|
40
|
+
.bridge
|
|
41
|
+
.wait_for(ruby, open(this.inner.clone(), cache), Span::current())?
|
|
42
|
+
.map_err(|error| match error {
|
|
43
|
+
ErasedReaderBuildError::Client(HighLevelClientError::StateReader(error)) => {
|
|
44
|
+
state_error(ruby, &error.into())
|
|
45
|
+
}
|
|
46
|
+
error => Error::new(ruby.exception_runtime_error(), error.to_string()),
|
|
47
|
+
})?;
|
|
48
|
+
let reads = Reads {
|
|
49
|
+
bridge: this.bridge.clone(),
|
|
50
|
+
propagator: Arc::clone(&this.propagator),
|
|
51
|
+
fork: this.fork,
|
|
52
|
+
};
|
|
53
|
+
Ok((reader, reads))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub(super) fn published_value(
|
|
57
|
+
ruby: &Ruby,
|
|
58
|
+
this: &Client,
|
|
59
|
+
subsystem: String,
|
|
60
|
+
name: String,
|
|
61
|
+
read_cache: Value,
|
|
62
|
+
) -> Result<NativePublishedValue, Error> {
|
|
63
|
+
let (inner, reads) = open_reader(ruby, this, read_cache, |client, cache| async move {
|
|
64
|
+
client.value_state(subsystem, name, cache).await
|
|
65
|
+
})?;
|
|
66
|
+
Ok(NativePublishedValue { inner, reads })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub(super) fn published_map(
|
|
70
|
+
ruby: &Ruby,
|
|
71
|
+
this: &Client,
|
|
72
|
+
subsystem: String,
|
|
73
|
+
name: String,
|
|
74
|
+
read_cache: Value,
|
|
75
|
+
) -> Result<NativePublishedMap, Error> {
|
|
76
|
+
let (inner, reads) = open_reader(ruby, this, read_cache, |client, cache| async move {
|
|
77
|
+
client.map_state(subsystem, name, cache).await
|
|
78
|
+
})?;
|
|
79
|
+
Ok(NativePublishedMap { inner, reads })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub(super) fn published_set(
|
|
83
|
+
ruby: &Ruby,
|
|
84
|
+
this: &Client,
|
|
85
|
+
subsystem: String,
|
|
86
|
+
name: String,
|
|
87
|
+
read_cache: Value,
|
|
88
|
+
) -> Result<NativePublishedSet, Error> {
|
|
89
|
+
let (inner, reads) = open_reader(ruby, this, read_cache, |client, cache| async move {
|
|
90
|
+
client.set_state(subsystem, name, cache).await
|
|
91
|
+
})?;
|
|
92
|
+
Ok(NativePublishedSet { inner, reads })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pub(super) fn published_deque(
|
|
96
|
+
ruby: &Ruby,
|
|
97
|
+
this: &Client,
|
|
98
|
+
subsystem: String,
|
|
99
|
+
name: String,
|
|
100
|
+
read_cache: Value,
|
|
101
|
+
) -> Result<NativePublishedDeque, Error> {
|
|
102
|
+
let (inner, reads) = open_reader(ruby, this, read_cache, |client, cache| async move {
|
|
103
|
+
client.deque_state(subsystem, name, cache).await
|
|
104
|
+
})?;
|
|
105
|
+
Ok(NativePublishedDeque { inner, reads })
|
|
106
|
+
}
|