prosody 0.4.0 → 0.5.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/.cargo/config.toml +3 -0
- data/.release-please-manifest.json +1 -1
- data/AGENTS.md +395 -0
- data/ARCHITECTURE.md +2 -2
- data/CHANGELOG.md +15 -0
- data/CLAUDE.md +1 -0
- data/CONFIGURATION.md +167 -0
- data/Cargo.lock +660 -326
- data/Cargo.toml +2 -1
- data/README.md +290 -191
- data/examples/keyed_state.rb +15 -3
- data/examples/keyed_state_windowing.rb +9 -1
- data/ext/prosody/Cargo.toml +2 -1
- data/ext/prosody/src/admin.rs +1 -5
- data/ext/prosody/src/bridge/mod.rs +17 -32
- data/ext/prosody/src/client/config.rs +194 -89
- data/ext/prosody/src/client/mod.rs +167 -74
- data/ext/prosody/src/client/request.rs +132 -0
- data/ext/prosody/src/client/support.rs +122 -0
- data/ext/prosody/src/handler/context.rs +24 -20
- data/ext/prosody/src/handler/message.rs +50 -0
- data/ext/prosody/src/handler/mod.rs +112 -84
- data/ext/prosody/src/handler/state/mod.rs +488 -0
- data/ext/prosody/src/handler/state/registration.rs +104 -0
- data/ext/prosody/src/handler/state/scan.rs +218 -0
- data/ext/prosody/src/lib.rs +15 -3
- data/ext/prosody/src/published.rs +273 -0
- data/ext/prosody/src/scheduler/mod.rs +2 -2
- data/ext/prosody/src/scheduler/processor.rs +2 -2
- data/ext/prosody/src/scheduler/result.rs +7 -4
- data/ext/prosody/src/util.rs +86 -5
- data/lib/prosody/configuration.rb +49 -15
- data/lib/prosody/handler.rb +63 -10
- data/lib/prosody/native_stubs.rb +197 -31
- data/lib/prosody/request.rb +45 -0
- data/lib/prosody/state.rb +164 -41
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +1 -0
- data/sig/configuration.rbs +51 -15
- data/sig/handler.rbs +12 -4
- data/sig/prosody.rbs +43 -2
- data/sig/request.rbs +66 -0
- data/sig/state.rbs +165 -47
- data/steep_expectations.yml +10 -0
- data/typecheck/payload_types.rb +14 -3
- data/typecheck/payload_types.rbs +4 -2
- data/typecheck_negative/payload_types.rb +4 -0
- data/typecheck_negative/payload_types.rbs +1 -0
- metadata +12 -2
- data/ext/prosody/src/handler/state.rs +0 -1035
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
//! builders.
|
|
8
8
|
|
|
9
9
|
use magnus::{Error, Ruby, Value};
|
|
10
|
-
use prosody::
|
|
10
|
+
use prosody::PeerConfiguration;
|
|
11
|
+
use prosody::PeerEndpoint;
|
|
11
12
|
use prosody::cassandra::config::CassandraConfigurationBuilder;
|
|
12
13
|
use prosody::consumer::ConsumerConfigurationBuilder;
|
|
13
14
|
use prosody::consumer::KeyedStateConfiguration;
|
|
@@ -29,13 +30,15 @@ use prosody::state::descriptor::{
|
|
|
29
30
|
DequeDescriptor, MapDescriptor, StateDescriptor, deque_state, map_state, value_state,
|
|
30
31
|
};
|
|
31
32
|
use prosody::state::order_codec::Utf8KeyCodec;
|
|
33
|
+
use prosody::subsystem::SubsystemName;
|
|
32
34
|
use prosody::telemetry::emitter::TelemetryEmitterConfiguration;
|
|
33
35
|
use prosody::timers::duration::CompactDuration;
|
|
36
|
+
use prosody::{ByteSize, JsonCodec};
|
|
34
37
|
use serde::{Deserialize, Deserializer};
|
|
35
38
|
use serde_magnus::deserialize;
|
|
36
39
|
use serde_untagged::UntaggedEnumVisitor;
|
|
37
|
-
use std::
|
|
38
|
-
use std::num::
|
|
40
|
+
use std::net::SocketAddr;
|
|
41
|
+
use std::num::NonZeroUsize;
|
|
39
42
|
use std::path::PathBuf;
|
|
40
43
|
use std::time::Duration;
|
|
41
44
|
|
|
@@ -117,7 +120,7 @@ pub struct NativeConfiguration {
|
|
|
117
120
|
/// List of Cassandra contact nodes (hostnames or IPs)
|
|
118
121
|
cassandra_nodes: Option<Vec<String>>,
|
|
119
122
|
|
|
120
|
-
/// Keyspace
|
|
123
|
+
/// Keyspace used for persistent Prosody data in Cassandra.
|
|
121
124
|
cassandra_keyspace: Option<String>,
|
|
122
125
|
|
|
123
126
|
/// Preferred datacenter for Cassandra query routing
|
|
@@ -132,8 +135,8 @@ pub struct NativeConfiguration {
|
|
|
132
135
|
/// Password for authenticating with Cassandra
|
|
133
136
|
cassandra_password: Option<String>,
|
|
134
137
|
|
|
135
|
-
/// Retention period for
|
|
136
|
-
/// seconds
|
|
138
|
+
/// Retention period for persistent timer and deferral data in Cassandra,
|
|
139
|
+
/// in seconds.
|
|
137
140
|
cassandra_retention: Option<f32>,
|
|
138
141
|
|
|
139
142
|
/// Timer slab partitioning duration in seconds.
|
|
@@ -179,24 +182,24 @@ pub struct NativeConfiguration {
|
|
|
179
182
|
/// Maximum delay between deferred retries (in seconds).
|
|
180
183
|
defer_max_delay: Option<f32>,
|
|
181
184
|
|
|
182
|
-
/// Failure rate threshold for
|
|
185
|
+
/// Failure rate threshold for disabling deferral (0.0 to 1.0).
|
|
183
186
|
defer_failure_threshold: Option<f64>,
|
|
184
187
|
|
|
185
188
|
/// Sliding window duration (in seconds) for failure rate tracking.
|
|
186
189
|
defer_failure_window: Option<f32>,
|
|
187
190
|
|
|
188
|
-
///
|
|
189
|
-
|
|
191
|
+
/// Maximum messages retained by the shared Kafka loader.
|
|
192
|
+
loader_cache_size: Option<u32>,
|
|
190
193
|
|
|
191
194
|
/// Maximum number of deferred store entries kept in the write-through cache
|
|
192
195
|
/// per Cassandra defer store.
|
|
193
196
|
defer_store_cache_size: Option<u32>,
|
|
194
197
|
|
|
195
|
-
/// Timeout for Kafka seek operations (in seconds).
|
|
196
|
-
|
|
198
|
+
/// Timeout for Kafka loader seek operations (in seconds).
|
|
199
|
+
loader_seek_timeout: Option<f32>,
|
|
197
200
|
|
|
198
201
|
/// Messages to read sequentially before seeking.
|
|
199
|
-
|
|
202
|
+
loader_discard_threshold: Option<i64>,
|
|
200
203
|
|
|
201
204
|
// Timeout configuration
|
|
202
205
|
/// Fixed timeout duration for handler execution (in seconds).
|
|
@@ -216,15 +219,39 @@ pub struct NativeConfiguration {
|
|
|
216
219
|
/// Span linking for timer execution spans (`child` or `follows_from`).
|
|
217
220
|
timer_spans: Option<String>,
|
|
218
221
|
|
|
222
|
+
/// Address for the peer listener.
|
|
223
|
+
peer_bind_address: Option<String>,
|
|
224
|
+
|
|
225
|
+
/// gRPC connect URI that peers use for this client.
|
|
226
|
+
peer_advertised_connect: Option<String>,
|
|
227
|
+
|
|
228
|
+
/// Network name used to identify direct routes.
|
|
229
|
+
peer_network_name: Option<String>,
|
|
230
|
+
|
|
231
|
+
/// Maximum number of peer channels and registrations held in each cache.
|
|
232
|
+
peer_cache_capacity: Option<usize>,
|
|
233
|
+
|
|
234
|
+
/// Duration of each peer registration lease, in seconds.
|
|
235
|
+
peer_registration_ttl: Option<f64>,
|
|
236
|
+
|
|
219
237
|
// Keyed-state configuration
|
|
220
238
|
/// Keyed-state collections to register before subscribe.
|
|
221
239
|
state_collections: Option<Vec<StateCollectionConfig>>,
|
|
222
240
|
|
|
241
|
+
/// Subsystem under which published collections are advertised.
|
|
242
|
+
subsystem: Option<String>,
|
|
243
|
+
|
|
223
244
|
/// Root directory for the local keyed-state cache. Must not be empty.
|
|
224
245
|
state_cache_dir: Option<String>,
|
|
225
246
|
|
|
226
|
-
/// Capacity of the
|
|
227
|
-
|
|
247
|
+
/// Capacity of the owning keyed-state cache.
|
|
248
|
+
state_owned_cache_size: Option<String>,
|
|
249
|
+
|
|
250
|
+
/// Capacity of the published-state read-through cache.
|
|
251
|
+
state_read_cache_size: Option<String>,
|
|
252
|
+
|
|
253
|
+
/// Default cache policy for published-state reads.
|
|
254
|
+
state_read_cache: Option<ReadCacheConfig>,
|
|
228
255
|
|
|
229
256
|
/// Delay in whole seconds before the keyed-state recovery sweep.
|
|
230
257
|
///
|
|
@@ -236,7 +263,7 @@ pub struct NativeConfiguration {
|
|
|
236
263
|
/// Declares one keyed-state collection to register before subscribe.
|
|
237
264
|
#[derive(Clone, Debug, Deserialize)]
|
|
238
265
|
struct StateCollectionConfig {
|
|
239
|
-
/// The collection name
|
|
266
|
+
/// The collection name. Prosody requires it to be non-empty and unique.
|
|
240
267
|
name: String,
|
|
241
268
|
|
|
242
269
|
/// The collection kind: `"value"`, `"map"`, or `"deque"`.
|
|
@@ -252,8 +279,11 @@ struct StateCollectionConfig {
|
|
|
252
279
|
/// Optional opt-out of transactional staging.
|
|
253
280
|
read_uncommitted: Option<bool>,
|
|
254
281
|
|
|
255
|
-
///
|
|
256
|
-
|
|
282
|
+
/// Whether other consumer groups may read this JSON collection.
|
|
283
|
+
published: Option<bool>,
|
|
284
|
+
|
|
285
|
+
/// Optional map-only keyset bound (`0..=4096`). The binding rejects values
|
|
286
|
+
/// that cannot map to an unsigned integer. Prosody enforces the ceiling.
|
|
257
287
|
keyset_limit: Option<f64>,
|
|
258
288
|
|
|
259
289
|
/// Optional deque-only window capacity (`>= 1`). Runtime-only and not
|
|
@@ -262,6 +292,13 @@ struct StateCollectionConfig {
|
|
|
262
292
|
capacity: Option<f64>,
|
|
263
293
|
}
|
|
264
294
|
|
|
295
|
+
#[derive(Clone, Debug, Deserialize)]
|
|
296
|
+
#[serde(untagged)]
|
|
297
|
+
enum ReadCacheConfig {
|
|
298
|
+
Disabled(bool),
|
|
299
|
+
Ttl(f64),
|
|
300
|
+
}
|
|
301
|
+
|
|
265
302
|
/// Configuration for the health probe port.
|
|
266
303
|
///
|
|
267
304
|
/// This enum represents the three possible states for the probe port
|
|
@@ -906,11 +943,42 @@ fn whole_number_field(value: f64, field: &str, min: u32, max: u32) -> Result<u32
|
|
|
906
943
|
}
|
|
907
944
|
}
|
|
908
945
|
|
|
946
|
+
fn build_peer_config(config: &NativeConfiguration) -> Result<PeerConfiguration, String> {
|
|
947
|
+
let mut builder = PeerConfiguration::builder();
|
|
948
|
+
if let Some(value) = &config.peer_bind_address {
|
|
949
|
+
builder.bind_address(
|
|
950
|
+
value
|
|
951
|
+
.parse::<SocketAddr>()
|
|
952
|
+
.map_err(|error| format!("peer_bind_address: {error}"))?,
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
if let Some(value) = &config.peer_advertised_connect {
|
|
956
|
+
builder.advertised_connect(
|
|
957
|
+
PeerEndpoint::try_from(value.clone())
|
|
958
|
+
.map_err(|error| format!("peer_advertised_connect: {error}"))?,
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
if let Some(value) = &config.peer_network_name {
|
|
962
|
+
builder.network_name(value.clone());
|
|
963
|
+
}
|
|
964
|
+
if let Some(value) = config.peer_cache_capacity {
|
|
965
|
+
builder.peer_cache_capacity(value);
|
|
966
|
+
}
|
|
967
|
+
if let Some(value) = config.peer_registration_ttl {
|
|
968
|
+
builder.registration_ttl(
|
|
969
|
+
Duration::try_from_secs_f64(value)
|
|
970
|
+
.map_err(|_| "peer_registration_ttl: must be a valid duration".to_owned())?,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
builder.build().map_err(|error| error.to_string())
|
|
974
|
+
}
|
|
975
|
+
|
|
909
976
|
/// Applies the shared descriptor options (TTL, commit mode) fluently.
|
|
910
977
|
fn with_def<D: StateDescriptor>(
|
|
911
978
|
descriptor: D,
|
|
912
979
|
ttl_seconds: Option<u32>,
|
|
913
980
|
read_uncommitted: Option<bool>,
|
|
981
|
+
published: Option<bool>,
|
|
914
982
|
) -> D {
|
|
915
983
|
let mut descriptor = descriptor;
|
|
916
984
|
if let Some(ttl) = ttl_seconds {
|
|
@@ -919,6 +987,9 @@ fn with_def<D: StateDescriptor>(
|
|
|
919
987
|
if read_uncommitted == Some(true) {
|
|
920
988
|
descriptor = descriptor.read_uncommitted();
|
|
921
989
|
}
|
|
990
|
+
if let Some(published) = published {
|
|
991
|
+
descriptor = descriptor.published(published);
|
|
992
|
+
}
|
|
922
993
|
descriptor
|
|
923
994
|
}
|
|
924
995
|
|
|
@@ -944,24 +1015,18 @@ fn with_capacity<T>(
|
|
|
944
1015
|
}
|
|
945
1016
|
}
|
|
946
1017
|
|
|
947
|
-
///
|
|
948
|
-
///
|
|
1018
|
+
/// Maps one collection into its descriptor over the closed 3×2 (kind ×
|
|
1019
|
+
/// payload) matrix.
|
|
949
1020
|
///
|
|
950
1021
|
/// # Errors
|
|
951
1022
|
///
|
|
952
|
-
/// Returns a permanent-category error
|
|
953
|
-
///
|
|
1023
|
+
/// Returns a permanent-category error when a host value cannot be mapped into
|
|
1024
|
+
/// a Prosody type.
|
|
954
1025
|
fn register_state_collection(
|
|
955
1026
|
keyed: &mut KeyedStateConfiguration,
|
|
956
1027
|
index: usize,
|
|
957
1028
|
collection: &StateCollectionConfig,
|
|
958
1029
|
) -> Result<(), String> {
|
|
959
|
-
if collection.name.is_empty() {
|
|
960
|
-
return Err(format!(
|
|
961
|
-
"state_collections[{index}].name: must not be empty"
|
|
962
|
-
));
|
|
963
|
-
}
|
|
964
|
-
|
|
965
1030
|
let kind = parse_kind(index, &collection.kind)?;
|
|
966
1031
|
let payload = parse_payload(index, &collection.payload)?;
|
|
967
1032
|
|
|
@@ -969,46 +1034,14 @@ fn register_state_collection(
|
|
|
969
1034
|
Some(value) => Some(whole_number_field(
|
|
970
1035
|
value,
|
|
971
1036
|
&format!("state_collections[{index}].ttl_seconds"),
|
|
972
|
-
|
|
1037
|
+
0,
|
|
973
1038
|
u32::MAX,
|
|
974
1039
|
)?),
|
|
975
1040
|
None => None,
|
|
976
1041
|
};
|
|
977
1042
|
|
|
978
|
-
let keyset_limit =
|
|
979
|
-
|
|
980
|
-
if !matches!(kind, CollectionKind::Map) {
|
|
981
|
-
return Err(format!(
|
|
982
|
-
"state_collections[{index}].keyset_limit: only valid for map collections"
|
|
983
|
-
));
|
|
984
|
-
}
|
|
985
|
-
Some(whole_number_field(
|
|
986
|
-
value,
|
|
987
|
-
&format!("state_collections[{index}].keyset_limit"),
|
|
988
|
-
0,
|
|
989
|
-
4096,
|
|
990
|
-
)?)
|
|
991
|
-
}
|
|
992
|
-
None => None,
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
let capacity = match collection.capacity {
|
|
996
|
-
Some(value) => {
|
|
997
|
-
if !matches!(kind, CollectionKind::Deque) {
|
|
998
|
-
return Err(format!(
|
|
999
|
-
"state_collections[{index}].capacity: only valid for deque collections"
|
|
1000
|
-
));
|
|
1001
|
-
}
|
|
1002
|
-
let n = whole_number_field(
|
|
1003
|
-
value,
|
|
1004
|
-
&format!("state_collections[{index}].capacity"),
|
|
1005
|
-
1,
|
|
1006
|
-
u32::MAX,
|
|
1007
|
-
)?;
|
|
1008
|
-
NonZeroUsize::new(n as usize)
|
|
1009
|
-
}
|
|
1010
|
-
None => None,
|
|
1011
|
-
};
|
|
1043
|
+
let keyset_limit = keyset_limit(collection.keyset_limit, &kind, index)?;
|
|
1044
|
+
let capacity = capacity(collection.capacity, &kind, index)?;
|
|
1012
1045
|
|
|
1013
1046
|
let read_uncommitted = collection.read_uncommitted;
|
|
1014
1047
|
let name = collection.name.as_str();
|
|
@@ -1018,6 +1051,7 @@ fn register_state_collection(
|
|
|
1018
1051
|
value_state::<JsonCodec>(name),
|
|
1019
1052
|
ttl_seconds,
|
|
1020
1053
|
read_uncommitted,
|
|
1054
|
+
collection.published,
|
|
1021
1055
|
));
|
|
1022
1056
|
}
|
|
1023
1057
|
(CollectionKind::Map, CollectionPayload::Json) => {
|
|
@@ -1025,6 +1059,7 @@ fn register_state_collection(
|
|
|
1025
1059
|
map_state::<Utf8KeyCodec, JsonCodec>(name),
|
|
1026
1060
|
ttl_seconds,
|
|
1027
1061
|
read_uncommitted,
|
|
1062
|
+
collection.published,
|
|
1028
1063
|
);
|
|
1029
1064
|
let _ = keyed.register(with_keyset(descriptor, keyset_limit));
|
|
1030
1065
|
}
|
|
@@ -1033,6 +1068,7 @@ fn register_state_collection(
|
|
|
1033
1068
|
deque_state::<JsonCodec>(name),
|
|
1034
1069
|
ttl_seconds,
|
|
1035
1070
|
read_uncommitted,
|
|
1071
|
+
collection.published,
|
|
1036
1072
|
);
|
|
1037
1073
|
let _ = keyed.register(with_capacity(descriptor, capacity));
|
|
1038
1074
|
}
|
|
@@ -1041,6 +1077,7 @@ fn register_state_collection(
|
|
|
1041
1077
|
message_state::<KafkaLoader<JsonCodec>>(name),
|
|
1042
1078
|
ttl_seconds,
|
|
1043
1079
|
read_uncommitted,
|
|
1080
|
+
collection.published,
|
|
1044
1081
|
));
|
|
1045
1082
|
}
|
|
1046
1083
|
(CollectionKind::Map, CollectionPayload::Message) => {
|
|
@@ -1048,6 +1085,7 @@ fn register_state_collection(
|
|
|
1048
1085
|
message_map_state::<Utf8KeyCodec, KafkaLoader<JsonCodec>>(name),
|
|
1049
1086
|
ttl_seconds,
|
|
1050
1087
|
read_uncommitted,
|
|
1088
|
+
collection.published,
|
|
1051
1089
|
);
|
|
1052
1090
|
let _ = keyed.register(with_keyset(descriptor, keyset_limit));
|
|
1053
1091
|
}
|
|
@@ -1056,6 +1094,7 @@ fn register_state_collection(
|
|
|
1056
1094
|
message_deque_state::<KafkaLoader<JsonCodec>>(name),
|
|
1057
1095
|
ttl_seconds,
|
|
1058
1096
|
read_uncommitted,
|
|
1097
|
+
collection.published,
|
|
1059
1098
|
);
|
|
1060
1099
|
let _ = keyed.register(with_capacity(descriptor, capacity));
|
|
1061
1100
|
}
|
|
@@ -1064,46 +1103,113 @@ fn register_state_collection(
|
|
|
1064
1103
|
Ok(())
|
|
1065
1104
|
}
|
|
1066
1105
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1106
|
+
fn keyset_limit(
|
|
1107
|
+
value: Option<f64>,
|
|
1108
|
+
kind: &CollectionKind,
|
|
1109
|
+
index: usize,
|
|
1110
|
+
) -> Result<Option<u32>, String> {
|
|
1111
|
+
let Some(value) = value else {
|
|
1112
|
+
return Ok(None);
|
|
1113
|
+
};
|
|
1114
|
+
if !matches!(kind, CollectionKind::Map) {
|
|
1115
|
+
return Err(format!(
|
|
1116
|
+
"state_collections[{index}].keyset_limit: only valid for map collections"
|
|
1117
|
+
));
|
|
1118
|
+
}
|
|
1119
|
+
whole_number_field(
|
|
1120
|
+
value,
|
|
1121
|
+
&format!("state_collections[{index}].keyset_limit"),
|
|
1122
|
+
0,
|
|
1123
|
+
u32::MAX,
|
|
1124
|
+
)
|
|
1125
|
+
.map(Some)
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
fn capacity(
|
|
1129
|
+
value: Option<f64>,
|
|
1130
|
+
kind: &CollectionKind,
|
|
1131
|
+
index: usize,
|
|
1132
|
+
) -> Result<Option<NonZeroUsize>, String> {
|
|
1133
|
+
let Some(value) = value else {
|
|
1134
|
+
return Ok(None);
|
|
1135
|
+
};
|
|
1136
|
+
if !matches!(kind, CollectionKind::Deque) {
|
|
1137
|
+
return Err(format!(
|
|
1138
|
+
"state_collections[{index}].capacity: only valid for deque collections"
|
|
1139
|
+
));
|
|
1140
|
+
}
|
|
1141
|
+
let value = whole_number_field(
|
|
1142
|
+
value,
|
|
1143
|
+
&format!("state_collections[{index}].capacity"),
|
|
1144
|
+
1,
|
|
1145
|
+
u32::MAX,
|
|
1146
|
+
)?;
|
|
1147
|
+
Ok(NonZeroUsize::new(value as usize))
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/// Builds the `KeyedStateConfiguration` by mapping each declared collection.
|
|
1151
|
+
/// The normal Prosody construction path validates the result.
|
|
1069
1152
|
///
|
|
1070
1153
|
/// # Errors
|
|
1071
1154
|
///
|
|
1072
|
-
/// Returns an error if a
|
|
1155
|
+
/// Returns an error if a host value cannot be mapped.
|
|
1073
1156
|
fn build_keyed_state_config(
|
|
1074
1157
|
config: &NativeConfiguration,
|
|
1075
1158
|
) -> Result<KeyedStateConfiguration, String> {
|
|
1076
1159
|
let mut builder = KeyedStateConfiguration::builder();
|
|
1077
1160
|
|
|
1078
1161
|
if let Some(dir) = &config.state_cache_dir {
|
|
1079
|
-
if dir.is_empty() {
|
|
1080
|
-
return Err("state_cache_dir: must not be an empty string".to_owned());
|
|
1081
|
-
}
|
|
1082
1162
|
builder.cache_dir(PathBuf::from(dir));
|
|
1083
1163
|
}
|
|
1084
1164
|
|
|
1085
1165
|
if let Some(seconds) = config.state_recovery_delay {
|
|
1086
|
-
let seconds = whole_number_field(seconds, "state_recovery_delay",
|
|
1166
|
+
let seconds = whole_number_field(seconds, "state_recovery_delay", 0, u32::MAX)?;
|
|
1087
1167
|
builder.recovery_delay(CompactDuration::new(seconds));
|
|
1088
1168
|
}
|
|
1089
1169
|
|
|
1090
|
-
if let Some(
|
|
1091
|
-
let
|
|
1092
|
-
.
|
|
1093
|
-
|
|
1170
|
+
if let Some(size) = &config.state_owned_cache_size {
|
|
1171
|
+
let size = size
|
|
1172
|
+
.parse::<ByteSize>()
|
|
1173
|
+
.map_err(|error| format!("state_owned_cache_size: {error}"))?;
|
|
1174
|
+
builder.owned_cache_size(Some(size));
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if let Some(size) = &config.state_read_cache_size {
|
|
1178
|
+
let size = size
|
|
1179
|
+
.parse::<ByteSize>()
|
|
1180
|
+
.map_err(|error| format!("state_read_cache_size: {error}"))?;
|
|
1181
|
+
builder.read_cache_size(Some(size));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if let Some(cache) = &config.state_read_cache {
|
|
1185
|
+
match cache {
|
|
1186
|
+
ReadCacheConfig::Disabled(false) => {
|
|
1187
|
+
builder.read_cache_ttl(None);
|
|
1188
|
+
}
|
|
1189
|
+
ReadCacheConfig::Disabled(true) => {
|
|
1190
|
+
return Err(
|
|
1191
|
+
"state_read_cache: true is ambiguous; use a duration or false".to_owned(),
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
ReadCacheConfig::Ttl(seconds) => {
|
|
1195
|
+
let ttl = Duration::try_from_secs_f64(*seconds).map_err(|_| {
|
|
1196
|
+
"state_read_cache: duration must be finite and non-negative".to_owned()
|
|
1197
|
+
})?;
|
|
1198
|
+
builder.read_cache_ttl(Some(ttl));
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if let Some(subsystem) = &config.subsystem {
|
|
1204
|
+
builder.subsystem(Some(
|
|
1205
|
+
SubsystemName::try_new(subsystem).map_err(|error| error.to_string())?,
|
|
1206
|
+
));
|
|
1094
1207
|
}
|
|
1095
1208
|
|
|
1096
1209
|
let mut keyed = builder.build().map_err(|error| error.to_string())?;
|
|
1097
1210
|
|
|
1098
1211
|
if let Some(collections) = &config.state_collections {
|
|
1099
|
-
let mut seen = HashSet::with_capacity(collections.len());
|
|
1100
1212
|
for (index, collection) in collections.iter().enumerate() {
|
|
1101
|
-
if !seen.insert(collection.name.as_str()) {
|
|
1102
|
-
return Err(format!(
|
|
1103
|
-
"state_collections[{index}].name: duplicate collection name {:?}",
|
|
1104
|
-
collection.name
|
|
1105
|
-
));
|
|
1106
|
-
}
|
|
1107
1213
|
register_state_collection(&mut keyed, index, collection)?;
|
|
1108
1214
|
}
|
|
1109
1215
|
}
|
|
@@ -1155,24 +1261,22 @@ impl<'a> TryFrom<&'a NativeConfiguration> for ConsumerBuilders {
|
|
|
1155
1261
|
consumer.timer_spans(relation);
|
|
1156
1262
|
}
|
|
1157
1263
|
|
|
1158
|
-
//
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|| config.defer_seek_timeout.is_some()
|
|
1163
|
-
|| config.defer_discard_threshold.is_some()
|
|
1264
|
+
// Route the shared Kafka message loader settings onto the consumer.
|
|
1265
|
+
if config.loader_cache_size.is_some()
|
|
1266
|
+
|| config.loader_seek_timeout.is_some()
|
|
1267
|
+
|| config.loader_discard_threshold.is_some()
|
|
1164
1268
|
{
|
|
1165
1269
|
let mut loader = KafkaLoaderConfiguration::builder();
|
|
1166
1270
|
|
|
1167
|
-
if let Some(cache_size) = &config.
|
|
1271
|
+
if let Some(cache_size) = &config.loader_cache_size {
|
|
1168
1272
|
loader.cache_size(*cache_size as usize);
|
|
1169
1273
|
}
|
|
1170
1274
|
|
|
1171
|
-
if let Some(seek_timeout) = &config.
|
|
1275
|
+
if let Some(seek_timeout) = &config.loader_seek_timeout {
|
|
1172
1276
|
loader.seek_timeout(Duration::from_secs_f32(*seek_timeout));
|
|
1173
1277
|
}
|
|
1174
1278
|
|
|
1175
|
-
if let Some(discard_threshold) = &config.
|
|
1279
|
+
if let Some(discard_threshold) = &config.loader_discard_threshold {
|
|
1176
1280
|
loader.discard_threshold(*discard_threshold);
|
|
1177
1281
|
}
|
|
1178
1282
|
|
|
@@ -1190,6 +1294,7 @@ impl<'a> TryFrom<&'a NativeConfiguration> for ConsumerBuilders {
|
|
|
1190
1294
|
dedup: config.try_into()?,
|
|
1191
1295
|
emitter: config.try_into()?,
|
|
1192
1296
|
keyed_state: build_keyed_state_config(config)?,
|
|
1297
|
+
peer: build_peer_config(config)?,
|
|
1193
1298
|
})
|
|
1194
1299
|
}
|
|
1195
1300
|
}
|