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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.config/rail.toml +26 -0
  3. data/.release-please-manifest.json +1 -1
  4. data/.ruby-version +1 -1
  5. data/.taplo.toml +1 -1
  6. data/AGENTS.md +29 -15
  7. data/CHANGELOG.md +7 -0
  8. data/CONFIGURATION.md +19 -16
  9. data/Cargo.lock +254 -235
  10. data/Cargo.toml +11 -8
  11. data/README.md +100 -23
  12. data/examples/keyed_state.rb +10 -2
  13. data/examples/keyed_state.rbs +1 -0
  14. data/ext/prosody/Cargo.toml +1 -1
  15. data/ext/prosody/src/admin.rs +36 -36
  16. data/ext/prosody/src/bridge/mod.rs +5 -10
  17. data/ext/prosody/src/client/config/connections.rs +170 -0
  18. data/ext/prosody/src/client/config/middleware.rs +184 -0
  19. data/ext/prosody/src/client/config/mod.rs +396 -0
  20. data/ext/prosody/src/client/config/state.rs +323 -0
  21. data/ext/prosody/src/client/mod.rs +31 -117
  22. data/ext/prosody/src/client/readers.rs +106 -0
  23. data/ext/prosody/src/client/request.rs +2 -2
  24. data/ext/prosody/src/client/support.rs +13 -32
  25. data/ext/prosody/src/gvl.rs +8 -6
  26. data/ext/prosody/src/handler/{context.rs → context/mod.rs} +36 -150
  27. data/ext/prosody/src/handler/context/vending.rs +138 -0
  28. data/ext/prosody/src/handler/message.rs +36 -0
  29. data/ext/prosody/src/handler/mod.rs +18 -8
  30. data/ext/prosody/src/handler/state/deque.rs +144 -0
  31. data/ext/prosody/src/handler/state/mod.rs +163 -268
  32. data/ext/prosody/src/handler/state/query.rs +264 -0
  33. data/ext/prosody/src/handler/state/registration.rs +15 -86
  34. data/ext/prosody/src/handler/state/scan.rs +33 -141
  35. data/ext/prosody/src/handler/state/set.rs +98 -0
  36. data/ext/prosody/src/lib.rs +54 -36
  37. data/ext/prosody/src/logging.rs +6 -6
  38. data/ext/prosody/src/published.rs +171 -152
  39. data/ext/prosody/src/scheduler/result.rs +2 -1
  40. data/ext/prosody/src/util.rs +65 -3
  41. data/lib/prosody/client.rb +32 -0
  42. data/lib/prosody/configuration.rb +20 -12
  43. data/lib/prosody/demand.rb +27 -0
  44. data/lib/prosody/native_stubs/client.rb +157 -0
  45. data/lib/prosody/native_stubs/context.rb +178 -0
  46. data/lib/prosody/native_stubs/message.rb +133 -0
  47. data/lib/prosody/native_stubs.rb +11 -956
  48. data/lib/prosody/state/deque.rb +253 -0
  49. data/lib/prosody/state/map.rb +313 -0
  50. data/lib/prosody/state/set.rb +121 -0
  51. data/lib/prosody/state/value.rb +58 -0
  52. data/lib/prosody/state.rb +157 -677
  53. data/lib/prosody/version.rb +1 -1
  54. data/lib/prosody.rb +2 -1
  55. data/sig/configuration.rbs +19 -10
  56. data/sig/prosody.rbs +37 -4
  57. data/sig/published.rbs +102 -0
  58. data/sig/state.rbs +109 -122
  59. data/typecheck/payload_types.rb +12 -0
  60. data/typecheck/payload_types.rbs +1 -0
  61. metadata +30 -11
  62. data/ext/prosody/src/client/config.rs +0 -1300
@@ -0,0 +1,98 @@
1
+ //! Native handle for a presence-only ordered set of `String` members.
2
+ //!
3
+ //! A set stores membership only. Member traversal reuses the map key cursor,
4
+ //! since both yield bare `String` keys.
5
+
6
+ use super::{NativeMapKeyScan, key_query, outcome_symbol, run_state, wrapped_class};
7
+ use crate::bridge::Bridge;
8
+ use magnus::{Error, Module, RHash, RModule, Ruby, StaticSymbol, method};
9
+ use opentelemetry::propagation::TextMapCompositePropagator;
10
+ use prosody::consumer::event_context::DynSetState;
11
+ use std::sync::Arc;
12
+
13
+ /// Native set handle, wrapped by `Prosody::SetState`.
14
+ #[magnus::wrap(class = "Prosody::NativeSetState")]
15
+ pub struct NativeSetState {
16
+ state: Arc<dyn DynSetState>,
17
+ bridge: Bridge,
18
+ propagator: Arc<TextMapCompositePropagator>,
19
+ }
20
+
21
+ impl NativeSetState {
22
+ pub(crate) fn new(
23
+ state: Arc<dyn DynSetState>,
24
+ bridge: Bridge,
25
+ propagator: Arc<TextMapCompositePropagator>,
26
+ ) -> Self {
27
+ Self {
28
+ state,
29
+ bridge,
30
+ propagator,
31
+ }
32
+ }
33
+
34
+ fn contains(ruby: &Ruby, this: &Self, member: String) -> Result<bool, Error> {
35
+ run_op!(ruby, this, &this.state, contains(member))
36
+ }
37
+
38
+ fn contains_many(ruby: &Ruby, this: &Self, members: Vec<String>) -> Result<Vec<bool>, Error> {
39
+ run_op!(ruby, this, &this.state, contains_many(members))
40
+ }
41
+
42
+ fn is_empty(ruby: &Ruby, this: &Self) -> Result<bool, Error> {
43
+ run_op!(ruby, this, &this.state, is_empty())
44
+ }
45
+
46
+ fn insert(ruby: &Ruby, this: &Self, member: String) -> Result<(), Error> {
47
+ run_op!(ruby, this, &this.state, insert(member))
48
+ }
49
+
50
+ fn remove(ruby: &Ruby, this: &Self, member: String) -> Result<(), Error> {
51
+ run_op!(ruby, this, &this.state, remove(member))
52
+ }
53
+
54
+ fn clear(ruby: &Ruby, this: &Self) -> Result<(), Error> {
55
+ run_op!(ruby, this, &this.state, clear())
56
+ }
57
+
58
+ fn keys(
59
+ ruby: &Ruby,
60
+ this: &Self,
61
+ direction: StaticSymbol,
62
+ options: RHash,
63
+ ) -> Result<NativeMapKeyScan, Error> {
64
+ let query = key_query(ruby, direction, options)?;
65
+ Ok(NativeMapKeyScan::new(
66
+ this.state.keys().with_query(query).stream(),
67
+ this.bridge.clone(),
68
+ Arc::clone(&this.propagator),
69
+ ))
70
+ }
71
+
72
+ fn commit(ruby: &Ruby, this: &Self) -> Result<StaticSymbol, Error> {
73
+ let outcome = run_op!(ruby, this, &this.state, commit())?;
74
+ Ok(outcome_symbol(ruby, outcome))
75
+ }
76
+
77
+ fn rollback(ruby: &Ruby, this: &Self) -> Result<StaticSymbol, Error> {
78
+ let state = Arc::clone(&this.state);
79
+ let outcome = run_state(ruby, &this.bridge, &this.propagator, async move {
80
+ Ok(state.rollback().await)
81
+ })?;
82
+ Ok(outcome_symbol(ruby, outcome))
83
+ }
84
+
85
+ pub(super) fn register(ruby: &Ruby, module: RModule) -> Result<(), Error> {
86
+ let class = wrapped_class(ruby, module, "Prosody::NativeSetState")?;
87
+ class.define_method("contains", method!(NativeSetState::contains, 1))?;
88
+ class.define_method("contains_many", method!(NativeSetState::contains_many, 1))?;
89
+ class.define_method("is_empty", method!(NativeSetState::is_empty, 0))?;
90
+ class.define_method("insert", method!(NativeSetState::insert, 1))?;
91
+ class.define_method("remove", method!(NativeSetState::remove, 1))?;
92
+ class.define_method("clear", method!(NativeSetState::clear, 0))?;
93
+ class.define_method("keys", method!(NativeSetState::keys, 2))?;
94
+ class.define_method("commit", method!(NativeSetState::commit, 0))?;
95
+ class.define_method("rollback", method!(NativeSetState::rollback, 0))?;
96
+ Ok(())
97
+ }
98
+ }
@@ -8,19 +8,20 @@
8
8
  //! provides client functionality for interacting with message brokers,
9
9
  //! manages message handling, and includes logging and scheduling capabilities.
10
10
 
11
- // Temporarily removing allows to see what lints we have
12
-
13
- #![allow(clippy::multiple_crate_versions, missing_docs)]
11
+ #![expect(
12
+ clippy::multiple_crate_versions,
13
+ reason = "the dependency graph resolves some crates at two versions"
14
+ )]
14
15
  #![recursion_limit = "256"]
15
16
 
16
17
  use crate::bridge::Bridge;
18
+ use crate::util::report;
19
+ use magnus::RModule;
17
20
  use magnus::value::Lazy;
18
- use magnus::{Error, RModule, Ruby};
19
- use mimalloc::MiMalloc;
20
- use std::io::{self, Write};
21
+ use rustfs_mimalloc::MiMalloc;
21
22
  use std::process;
22
23
  use std::sync::{LazyLock, OnceLock};
23
- use tokio::runtime::Runtime;
24
+ use tokio::runtime::{Builder, Runtime};
24
25
 
25
26
  mod admin;
26
27
  mod bridge;
@@ -33,6 +34,13 @@ mod scheduler;
33
34
  mod tracing_util;
34
35
  mod util;
35
36
 
37
+ /// Stack size of each Tokio worker thread.
38
+ ///
39
+ /// Core futures are large, most of all in debug builds. A timer write that
40
+ /// polls through the Cassandra driver can overflow the Tokio default of
41
+ /// 2 MiB. The size applies to every build.
42
+ const WORKER_STACK_SIZE: usize = 8 * 1024 * 1024;
43
+
36
44
  #[global_allocator]
37
45
  static GLOBAL: MiMalloc = MiMalloc;
38
46
 
@@ -47,14 +55,18 @@ pub static TRACING_INIT: OnceLock<()> = OnceLock::new();
47
55
  ///
48
56
  /// This runtime powers all async operations in the extension, including
49
57
  /// message processing, scheduling, and communication with Ruby.
50
- static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| match Runtime::new() {
51
- Ok(runtime) => runtime,
52
- Err(error) => {
53
- drop(writeln!(
54
- io::stderr().lock(),
55
- "failed to create Tokio runtime: {error:#}"
56
- ));
57
- process::abort();
58
+ static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
59
+ let runtime = Builder::new_multi_thread()
60
+ .enable_all()
61
+ .thread_stack_size(WORKER_STACK_SIZE)
62
+ .build();
63
+
64
+ match runtime {
65
+ Ok(runtime) => runtime,
66
+ Err(error) => {
67
+ report(format_args!("failed to create Tokio runtime: {error:#}"));
68
+ process::abort();
69
+ }
58
70
  }
59
71
  });
60
72
 
@@ -62,32 +74,38 @@ static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| match Runtime::new() {
62
74
  ///
63
75
  /// This is lazily initialized during extension startup and provides
64
76
  /// access to the `Prosody` module in Ruby.
65
- #[allow(clippy::expect_used)]
77
+ #[expect(
78
+ clippy::expect_used,
79
+ reason = "magnus Lazy takes an infallible initializer"
80
+ )]
66
81
  pub static ROOT_MOD: Lazy<RModule> = Lazy::new(|ruby| {
67
82
  ruby.define_module("Prosody")
68
83
  .expect("Failed to define Prosody module")
69
84
  });
70
85
 
71
- /// Initializes the Prosody Ruby extension.
72
- ///
73
- /// This function initializes the various components of the extension.
86
+ /// The extension entry point that Ruby calls when it loads the library.
74
87
  ///
75
- /// # Arguments
76
- ///
77
- /// * `ruby` - Reference to the Ruby VM instance
78
- ///
79
- /// # Errors
80
- ///
81
- /// Returns a Magnus error if any initialization step fails, such as
82
- /// defining Ruby classes or configuring components.
83
- #[magnus::init]
84
- fn init(ruby: &Ruby) -> Result<(), Error> {
85
- admin::init(ruby)?;
86
- bridge::init(ruby)?;
87
- handler::init(ruby)?;
88
- published::init(ruby)?;
89
- client::init(ruby)?;
90
- util::init(ruby)?;
88
+ /// `#[magnus::init]` generates the exported `Init_prosody` function beside
89
+ /// `init`. The module is private, so that generated function is not public
90
+ /// API.
91
+ mod entry {
92
+ use magnus::{Error, Ruby};
91
93
 
92
- Ok(())
94
+ /// Initializes the Prosody Ruby extension.
95
+ ///
96
+ /// # Errors
97
+ ///
98
+ /// Returns a Magnus error if any initialization step fails, such as
99
+ /// defining Ruby classes or configuring components.
100
+ #[magnus::init]
101
+ fn init(ruby: &Ruby) -> Result<(), Error> {
102
+ crate::admin::init(ruby)?;
103
+ crate::bridge::init(ruby)?;
104
+ crate::handler::init(ruby)?;
105
+ crate::published::init(ruby)?;
106
+ crate::client::init(ruby)?;
107
+ crate::util::init(ruby)?;
108
+
109
+ Ok(())
110
+ }
93
111
  }
@@ -8,12 +8,10 @@
8
8
  //! It uses bump allocation for efficient string formatting and concurrent
9
9
  //! processing for high-throughput logging scenarios.
10
10
 
11
- #![allow(clippy::print_stderr)]
12
-
13
11
  use crate::ROOT_MOD;
14
12
  use crate::bridge::Bridge;
15
13
  use crate::id;
16
- use crate::util::ThreadSafeValue;
14
+ use crate::util::{ThreadSafeValue, report};
17
15
  use bumpalo::Bump;
18
16
  use bumpalo::collections::string::String as BumpString;
19
17
  use educe::Educe;
@@ -113,12 +111,12 @@ async fn log_event(bridge: Bridge, logger: Arc<ThreadSafeValue>, (level, msg): (
113
111
  };
114
112
 
115
113
  if let Err(error) = logger.get(ruby).funcall::<_, _, Value>(method, (msg,)) {
116
- eprintln!("failed to log to Ruby: {error:#}");
114
+ report(format_args!("failed to log to Ruby: {error:#}"));
117
115
  }
118
116
  })
119
117
  .await
120
118
  {
121
- eprintln!("failed to log to Ruby: {error:#}");
119
+ report(format_args!("failed to log to Ruby: {error:#}"));
122
120
  }
123
121
  }
124
122
 
@@ -150,7 +148,9 @@ impl<S: Subscriber> Layer<S> for Logger {
150
148
  event.record(&mut visitor);
151
149
 
152
150
  if let Err(error) = self.tx.send((*metadata.level(), visitor.to_string())) {
153
- eprintln!("failed to send log message: {error:#}; message: {visitor}");
151
+ report(format_args!(
152
+ "failed to send log message: {error:#}; message: {visitor}"
153
+ ));
154
154
  }
155
155
  });
156
156
  }