prosody 0.1.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 +7 -0
- data/.cargo/config.toml +2 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.standard.yml +9 -0
- data/.taplo.toml +6 -0
- data/ARCHITECTURE.md +591 -0
- data/CHANGELOG.md +92 -0
- data/Cargo.lock +3513 -0
- data/Cargo.toml +77 -0
- data/LICENSE +21 -0
- data/Makefile +36 -0
- data/README.md +946 -0
- data/Rakefile +26 -0
- data/ext/prosody/Cargo.toml +38 -0
- data/ext/prosody/extconf.rb +6 -0
- data/ext/prosody/src/admin.rs +171 -0
- data/ext/prosody/src/bridge/callback.rs +60 -0
- data/ext/prosody/src/bridge/mod.rs +332 -0
- data/ext/prosody/src/client/config.rs +819 -0
- data/ext/prosody/src/client/mod.rs +379 -0
- data/ext/prosody/src/gvl.rs +149 -0
- data/ext/prosody/src/handler/context.rs +436 -0
- data/ext/prosody/src/handler/message.rs +144 -0
- data/ext/prosody/src/handler/mod.rs +338 -0
- data/ext/prosody/src/handler/trigger.rs +93 -0
- data/ext/prosody/src/lib.rs +82 -0
- data/ext/prosody/src/logging.rs +353 -0
- data/ext/prosody/src/scheduler/cancellation.rs +67 -0
- data/ext/prosody/src/scheduler/handle.rs +50 -0
- data/ext/prosody/src/scheduler/mod.rs +169 -0
- data/ext/prosody/src/scheduler/processor.rs +166 -0
- data/ext/prosody/src/scheduler/result.rs +197 -0
- data/ext/prosody/src/tracing_util.rs +56 -0
- data/ext/prosody/src/util.rs +219 -0
- data/lib/prosody/configuration.rb +333 -0
- data/lib/prosody/handler.rb +177 -0
- data/lib/prosody/native_stubs.rb +417 -0
- data/lib/prosody/processor.rb +321 -0
- data/lib/prosody/sentry.rb +36 -0
- data/lib/prosody/version.rb +10 -0
- data/lib/prosody.rb +42 -0
- data/release-please-config.json +10 -0
- data/sig/configuration.rbs +252 -0
- data/sig/handler.rbs +79 -0
- data/sig/processor.rbs +100 -0
- data/sig/prosody.rbs +171 -0
- data/sig/version.rbs +9 -0
- metadata +193 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
//! # Message Handler Module
|
|
2
|
+
//!
|
|
3
|
+
//! Provides functionality to handle Kafka messages in Ruby by bridging between
|
|
4
|
+
//! the Rust-based Prosody Kafka client and Ruby application code.
|
|
5
|
+
//!
|
|
6
|
+
//! This module implements a handler for Kafka consumer messages that:
|
|
7
|
+
//! 1. Receives messages from Kafka via the Prosody consumer
|
|
8
|
+
//! 2. Converts them to Ruby objects
|
|
9
|
+
//! 3. Schedules execution in the Ruby runtime
|
|
10
|
+
//! 4. Properly handles error cases and shutdown scenarios
|
|
11
|
+
|
|
12
|
+
use crate::bridge::{Bridge, BridgeError};
|
|
13
|
+
use crate::handler::context::Context;
|
|
14
|
+
use crate::handler::message::Message;
|
|
15
|
+
use crate::handler::trigger::Timer;
|
|
16
|
+
use crate::id;
|
|
17
|
+
use crate::scheduler::result::ProcessingError;
|
|
18
|
+
use crate::scheduler::{Scheduler, SchedulerError};
|
|
19
|
+
use crate::util::ThreadSafeValue;
|
|
20
|
+
use futures::pin_mut;
|
|
21
|
+
use magnus::value::ReprValue;
|
|
22
|
+
use magnus::{Error, Ruby, Value};
|
|
23
|
+
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
24
|
+
use prosody::consumer::event_context::EventContext;
|
|
25
|
+
use prosody::consumer::message::ConsumerMessage;
|
|
26
|
+
use prosody::consumer::middleware::FallibleHandler;
|
|
27
|
+
use prosody::consumer::{DemandType, Keyed};
|
|
28
|
+
use prosody::error::{ClassifyError, ErrorCategory};
|
|
29
|
+
use prosody::propagator::new_propagator;
|
|
30
|
+
use prosody::timers::{TimerType, Trigger as ProsodyTrigger};
|
|
31
|
+
use std::collections::HashMap;
|
|
32
|
+
use std::sync::Arc;
|
|
33
|
+
use thiserror::Error;
|
|
34
|
+
use tokio::select;
|
|
35
|
+
use tracing::{Instrument, info_span};
|
|
36
|
+
|
|
37
|
+
mod context;
|
|
38
|
+
mod message;
|
|
39
|
+
mod trigger;
|
|
40
|
+
|
|
41
|
+
/// A handler that bridges between Kafka messages and Ruby message processing
|
|
42
|
+
/// code.
|
|
43
|
+
///
|
|
44
|
+
/// This struct manages the execution of Ruby message handling code when Kafka
|
|
45
|
+
/// messages are received. It uses a scheduler to run the Ruby handler code
|
|
46
|
+
/// in the Ruby runtime environment, ensuring proper thread safety and error
|
|
47
|
+
/// handling.
|
|
48
|
+
#[derive(Clone, Debug)]
|
|
49
|
+
pub struct RubyHandler {
|
|
50
|
+
/// Bridge for communicating between Rust and Ruby
|
|
51
|
+
bridge: Bridge,
|
|
52
|
+
|
|
53
|
+
/// Scheduler for running Ruby code in the Ruby runtime
|
|
54
|
+
scheduler: Arc<Scheduler>,
|
|
55
|
+
|
|
56
|
+
/// Thread-safe reference to the Ruby handler instance
|
|
57
|
+
handler: Arc<ThreadSafeValue>,
|
|
58
|
+
|
|
59
|
+
/// OpenTelemetry propagator shared across contexts
|
|
60
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl RubyHandler {
|
|
64
|
+
/// Creates a new handler that will dispatch Kafka messages to a Ruby
|
|
65
|
+
/// handler instance.
|
|
66
|
+
///
|
|
67
|
+
/// # Arguments
|
|
68
|
+
///
|
|
69
|
+
/// * `bridge` - Bridge for communicating between Rust and Ruby threads
|
|
70
|
+
/// * `ruby` - Reference to the Ruby VM
|
|
71
|
+
/// * `handler_instance` - Ruby object that will handle Kafka messages
|
|
72
|
+
///
|
|
73
|
+
/// # Errors
|
|
74
|
+
///
|
|
75
|
+
/// Returns a `Magnus::Error` if creating the scheduler fails
|
|
76
|
+
pub fn new(bridge: Bridge, ruby: &Ruby, handler_instance: Value) -> Result<Self, Error> {
|
|
77
|
+
Ok(Self {
|
|
78
|
+
bridge: bridge.clone(),
|
|
79
|
+
handler: Arc::new(ThreadSafeValue::new(handler_instance, bridge.clone())),
|
|
80
|
+
scheduler: Arc::new(Scheduler::new(ruby, bridge)?),
|
|
81
|
+
propagator: Arc::new(new_propagator()),
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl FallibleHandler for RubyHandler {
|
|
87
|
+
type Error = RubyHandlerError;
|
|
88
|
+
|
|
89
|
+
/// Processes a Kafka message by dispatching it to the Ruby handler.
|
|
90
|
+
///
|
|
91
|
+
/// This method:
|
|
92
|
+
/// 1. Creates a unique task ID from the message metadata
|
|
93
|
+
/// 2. Schedules the handler execution in the Ruby runtime
|
|
94
|
+
/// 3. Waits for the result or responds to shutdown signals
|
|
95
|
+
/// 4. Handles cancellation if a shutdown is requested
|
|
96
|
+
///
|
|
97
|
+
/// # Arguments
|
|
98
|
+
///
|
|
99
|
+
/// * `context` - Kafka message context containing metadata and control
|
|
100
|
+
/// signals
|
|
101
|
+
/// * `message` - The Kafka message to be processed
|
|
102
|
+
///
|
|
103
|
+
/// # Errors
|
|
104
|
+
///
|
|
105
|
+
/// Returns a `RubyHandlerError` if any of the following fail:
|
|
106
|
+
/// - Scheduling the task
|
|
107
|
+
/// - Communication with the Ruby runtime
|
|
108
|
+
/// - The Ruby handler throws an exception
|
|
109
|
+
async fn on_message<C>(
|
|
110
|
+
&self,
|
|
111
|
+
context: C,
|
|
112
|
+
message: ConsumerMessage,
|
|
113
|
+
_demand_type: DemandType,
|
|
114
|
+
) -> Result<(), Self::Error>
|
|
115
|
+
where
|
|
116
|
+
C: EventContext,
|
|
117
|
+
{
|
|
118
|
+
// Create a new span for the on_message operation as a child of the message's
|
|
119
|
+
// span
|
|
120
|
+
let span = info_span!(
|
|
121
|
+
parent: message.span(),
|
|
122
|
+
"on_message",
|
|
123
|
+
topic = %message.topic(),
|
|
124
|
+
partition = message.partition(),
|
|
125
|
+
offset = message.offset(),
|
|
126
|
+
key = %message.key()
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Get a future that completes when cancellation is signaled
|
|
130
|
+
let cloned_context = context.clone();
|
|
131
|
+
let cancel_future = cloned_context.on_cancel();
|
|
132
|
+
|
|
133
|
+
// Clone the handler reference for use in the closure
|
|
134
|
+
let handler = self.handler.clone();
|
|
135
|
+
|
|
136
|
+
// Create a unique task ID for this message
|
|
137
|
+
let task_id = format!(
|
|
138
|
+
"{}/{}:{}",
|
|
139
|
+
message.topic(),
|
|
140
|
+
message.partition(),
|
|
141
|
+
message.offset()
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
let event_context = HashMap::from([
|
|
145
|
+
("event_type".into(), "message".into()),
|
|
146
|
+
("topic".into(), message.topic().to_string()),
|
|
147
|
+
("partition".into(), message.partition().to_string()),
|
|
148
|
+
("key".into(), message.key().to_string()),
|
|
149
|
+
("offset".into(), message.offset().to_string()),
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
// Convert the Kafka message and context to Ruby-compatible types
|
|
153
|
+
let context = Context::new(
|
|
154
|
+
context.boxed(),
|
|
155
|
+
self.bridge.clone(),
|
|
156
|
+
self.propagator.clone(),
|
|
157
|
+
);
|
|
158
|
+
let message: Message = message.into();
|
|
159
|
+
|
|
160
|
+
// Execute the entire message handling operation within the span
|
|
161
|
+
let cloned_span = span.clone();
|
|
162
|
+
async move {
|
|
163
|
+
// Schedule the task to run in Ruby
|
|
164
|
+
let task_handle = self
|
|
165
|
+
.scheduler
|
|
166
|
+
.schedule(task_id, &cloned_span, event_context, move |ruby| {
|
|
167
|
+
let _: Value = handler
|
|
168
|
+
.get(ruby)
|
|
169
|
+
.funcall(id!(ruby, "on_message"), (context, message))?;
|
|
170
|
+
|
|
171
|
+
Ok(())
|
|
172
|
+
})
|
|
173
|
+
.await?;
|
|
174
|
+
|
|
175
|
+
// Get the future that will complete when the task is done
|
|
176
|
+
let result_future = task_handle.result.receive();
|
|
177
|
+
pin_mut!(result_future);
|
|
178
|
+
|
|
179
|
+
// Wait for either task completion or shutdown signal
|
|
180
|
+
select! {
|
|
181
|
+
result = &mut result_future => {
|
|
182
|
+
result?;
|
|
183
|
+
}
|
|
184
|
+
() = cancel_future => {
|
|
185
|
+
// If cancellation requested, cancel the task and wait for it to complete
|
|
186
|
+
task_handle.cancellation_token.cancel(&self.bridge).await?;
|
|
187
|
+
result_future.await?;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Ok(())
|
|
192
|
+
}
|
|
193
|
+
.instrument(span)
|
|
194
|
+
.await
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async fn on_timer<C>(
|
|
198
|
+
&self,
|
|
199
|
+
context: C,
|
|
200
|
+
trigger: ProsodyTrigger,
|
|
201
|
+
_demand_type: DemandType,
|
|
202
|
+
) -> Result<(), Self::Error>
|
|
203
|
+
where
|
|
204
|
+
C: EventContext,
|
|
205
|
+
{
|
|
206
|
+
// Only process application timers; internal timers are handled by middleware
|
|
207
|
+
if trigger.timer_type != TimerType::Application {
|
|
208
|
+
return Ok(());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Create a new span for the on_timer operation as a child of the trigger's span
|
|
212
|
+
let span = info_span!(
|
|
213
|
+
parent: trigger.span(),
|
|
214
|
+
"on_timer",
|
|
215
|
+
key = %trigger.key,
|
|
216
|
+
time = %trigger.time
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Get a future that completes when cancellation is signaled
|
|
220
|
+
let cloned_context = context.clone();
|
|
221
|
+
let cancel_future = cloned_context.on_cancel();
|
|
222
|
+
|
|
223
|
+
// Clone the handler reference for use in the closure
|
|
224
|
+
let handler = self.handler.clone();
|
|
225
|
+
|
|
226
|
+
// Create a unique task ID for this timer
|
|
227
|
+
let task_id = format!("timer/{}/{}", trigger.key, trigger.time);
|
|
228
|
+
|
|
229
|
+
let event_context = HashMap::from([
|
|
230
|
+
("event_type".into(), "timer".into()),
|
|
231
|
+
("key".into(), trigger.key.to_string()),
|
|
232
|
+
("time".into(), trigger.time.to_string()),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
// Convert the timer trigger and context to Ruby-compatible types
|
|
236
|
+
let context = Context::new(
|
|
237
|
+
context.boxed(),
|
|
238
|
+
self.bridge.clone(),
|
|
239
|
+
self.propagator.clone(),
|
|
240
|
+
);
|
|
241
|
+
let timer: Timer = trigger.into();
|
|
242
|
+
|
|
243
|
+
// Execute the entire timer handling operation within the span
|
|
244
|
+
let cloned_span = span.clone();
|
|
245
|
+
async move {
|
|
246
|
+
// Schedule the task to run in Ruby
|
|
247
|
+
let task_handle = self
|
|
248
|
+
.scheduler
|
|
249
|
+
.schedule(task_id, &cloned_span, event_context, move |ruby| {
|
|
250
|
+
let _: Value = handler
|
|
251
|
+
.get(ruby)
|
|
252
|
+
.funcall(id!(ruby, "on_timer"), (context, timer))?;
|
|
253
|
+
|
|
254
|
+
Ok(())
|
|
255
|
+
})
|
|
256
|
+
.await?;
|
|
257
|
+
|
|
258
|
+
// Get the future that will complete when the task is done
|
|
259
|
+
let result_future = task_handle.result.receive();
|
|
260
|
+
pin_mut!(result_future);
|
|
261
|
+
|
|
262
|
+
// Wait for either task completion or shutdown signal
|
|
263
|
+
select! {
|
|
264
|
+
result = &mut result_future => {
|
|
265
|
+
result?;
|
|
266
|
+
}
|
|
267
|
+
() = cancel_future => {
|
|
268
|
+
// If cancellation requested, cancel the task and wait for it to complete
|
|
269
|
+
task_handle.cancellation_token.cancel(&self.bridge).await?;
|
|
270
|
+
result_future.await?;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
Ok(())
|
|
275
|
+
}
|
|
276
|
+
.instrument(span)
|
|
277
|
+
.await
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// Shuts down the handler.
|
|
281
|
+
///
|
|
282
|
+
/// This is a no-op for the Ruby handler since resources are managed
|
|
283
|
+
/// by the Ruby runtime through garbage collection.
|
|
284
|
+
async fn shutdown(self) {
|
|
285
|
+
// No cleanup required - Ruby handles resource cleanup via GC
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
impl ClassifyError for RubyHandlerError {
|
|
290
|
+
/// Categorizes errors for proper retry handling in the Kafka consumer.
|
|
291
|
+
///
|
|
292
|
+
/// Maps error types to their appropriate categories:
|
|
293
|
+
/// - Scheduler and Bridge errors are considered transient (retryable)
|
|
294
|
+
/// - Processing errors are categorized according to their own
|
|
295
|
+
/// classification
|
|
296
|
+
fn classify_error(&self) -> ErrorCategory {
|
|
297
|
+
match self {
|
|
298
|
+
RubyHandlerError::Scheduler(_) | RubyHandlerError::Bridge(_) => {
|
|
299
|
+
ErrorCategory::Transient
|
|
300
|
+
}
|
|
301
|
+
RubyHandlerError::Processing(error) => error.classify_error(),
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/// Errors that can occur when handling Kafka messages in Ruby.
|
|
307
|
+
#[derive(Debug, Error)]
|
|
308
|
+
pub enum RubyHandlerError {
|
|
309
|
+
/// Error from the task scheduler
|
|
310
|
+
#[error(transparent)]
|
|
311
|
+
Scheduler(#[from] SchedulerError),
|
|
312
|
+
|
|
313
|
+
/// Error communicating with the Ruby runtime
|
|
314
|
+
#[error(transparent)]
|
|
315
|
+
Bridge(#[from] BridgeError),
|
|
316
|
+
|
|
317
|
+
/// Error from the Ruby handler code
|
|
318
|
+
#[error(transparent)]
|
|
319
|
+
Processing(#[from] ProcessingError),
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// Initializes the handler module by registering Ruby classes for message
|
|
323
|
+
/// context and content.
|
|
324
|
+
///
|
|
325
|
+
/// # Arguments
|
|
326
|
+
///
|
|
327
|
+
/// * `ruby` - Reference to the Ruby VM
|
|
328
|
+
///
|
|
329
|
+
/// # Errors
|
|
330
|
+
///
|
|
331
|
+
/// Returns a `Magnus::Error` if class initialization fails
|
|
332
|
+
pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
333
|
+
context::init(ruby)?;
|
|
334
|
+
message::init(ruby)?;
|
|
335
|
+
trigger::init(ruby)?;
|
|
336
|
+
|
|
337
|
+
Ok(())
|
|
338
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//! Defines the Ruby-facing wrapper for timer triggers.
|
|
2
|
+
//!
|
|
3
|
+
//! This module implements a Ruby-accessible wrapper around the Prosody timer
|
|
4
|
+
//! trigger type, exposing trigger properties as Ruby objects.
|
|
5
|
+
|
|
6
|
+
use crate::{ROOT_MOD, id};
|
|
7
|
+
use educe::Educe;
|
|
8
|
+
use magnus::value::ReprValue;
|
|
9
|
+
use magnus::{Error, Module, RClass, Ruby, Value, method};
|
|
10
|
+
use prosody::timers::Trigger;
|
|
11
|
+
|
|
12
|
+
/// Ruby-accessible wrapper for timer triggers.
|
|
13
|
+
///
|
|
14
|
+
/// Provides Ruby bindings to access timer trigger data including key,
|
|
15
|
+
/// execution time, and tracing span information.
|
|
16
|
+
#[derive(Educe, Clone)]
|
|
17
|
+
#[educe(Debug)]
|
|
18
|
+
#[magnus::wrap(class = "Prosody::Timer", frozen_shareable)]
|
|
19
|
+
pub struct Timer {
|
|
20
|
+
/// The wrapped Prosody timer trigger
|
|
21
|
+
#[educe(Debug(ignore))]
|
|
22
|
+
inner: Trigger,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl Timer {
|
|
26
|
+
/// Returns the entity key identifying what this timer belongs to.
|
|
27
|
+
///
|
|
28
|
+
/// # Returns
|
|
29
|
+
///
|
|
30
|
+
/// A string slice containing the entity key.
|
|
31
|
+
fn key(&self) -> &str {
|
|
32
|
+
self.inner.key.as_ref()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Converts the trigger execution time to a Ruby Time object.
|
|
36
|
+
///
|
|
37
|
+
/// # Arguments
|
|
38
|
+
///
|
|
39
|
+
/// * `ruby` - Reference to the Ruby VM
|
|
40
|
+
/// * `this` - Reference to the Trigger instance
|
|
41
|
+
///
|
|
42
|
+
/// # Returns
|
|
43
|
+
///
|
|
44
|
+
/// A Ruby Time object representing when this timer should execute.
|
|
45
|
+
///
|
|
46
|
+
/// # Errors
|
|
47
|
+
///
|
|
48
|
+
/// Returns an error if the Ruby Time class cannot be accessed or if
|
|
49
|
+
/// creating the Time object fails.
|
|
50
|
+
fn time(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
51
|
+
// Direct access to epoch seconds - CompactDateTime has no sub-second precision
|
|
52
|
+
let epoch_seconds = i64::from(this.inner.time.epoch_seconds());
|
|
53
|
+
|
|
54
|
+
// Create Ruby Time with zero nanoseconds (CompactDateTime precision limit)
|
|
55
|
+
ruby.module_kernel()
|
|
56
|
+
.const_get::<_, RClass>(id!(ruby, "Time"))?
|
|
57
|
+
.funcall(id!(ruby, "at"), (epoch_seconds,))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl From<Trigger> for Timer {
|
|
62
|
+
/// Creates a new Timer wrapper from a Prosody `Trigger`.
|
|
63
|
+
///
|
|
64
|
+
/// # Arguments
|
|
65
|
+
///
|
|
66
|
+
/// * `value` - The Prosody `Trigger` to wrap
|
|
67
|
+
fn from(value: Trigger) -> Self {
|
|
68
|
+
Self { inner: value }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Initializes the `Prosody::Timer` Ruby class and defines its methods.
|
|
73
|
+
///
|
|
74
|
+
/// # Arguments
|
|
75
|
+
///
|
|
76
|
+
/// * `ruby` - Reference to the Ruby VM
|
|
77
|
+
///
|
|
78
|
+
/// # Returns
|
|
79
|
+
///
|
|
80
|
+
/// OK on successful initialization.
|
|
81
|
+
///
|
|
82
|
+
/// # Errors
|
|
83
|
+
///
|
|
84
|
+
/// Returns an error if class or method definition fails.
|
|
85
|
+
pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
86
|
+
let module = ruby.get_inner(&ROOT_MOD);
|
|
87
|
+
let class = module.define_class(id!(ruby, "Timer"), ruby.class_object())?;
|
|
88
|
+
|
|
89
|
+
class.define_method(id!(ruby, "key"), method!(Timer::key, 0))?;
|
|
90
|
+
class.define_method(id!(ruby, "time"), method!(Timer::time, 0))?;
|
|
91
|
+
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//! # Prosody Ruby Extension
|
|
2
|
+
//!
|
|
3
|
+
//! This crate provides Ruby bindings for the Prosody event processing library.
|
|
4
|
+
//! It bridges the Rust implementation of Prosody with Ruby, allowing Ruby
|
|
5
|
+
//! applications to use Prosody for event processing and messaging.
|
|
6
|
+
//!
|
|
7
|
+
//! The extension handles asynchronous communication between Rust and Ruby,
|
|
8
|
+
//! provides client functionality for interacting with message brokers,
|
|
9
|
+
//! manages message handling, and includes logging and scheduling capabilities.
|
|
10
|
+
|
|
11
|
+
// Temporarily removing allows to see what lints we have
|
|
12
|
+
|
|
13
|
+
#![allow(clippy::multiple_crate_versions, missing_docs)]
|
|
14
|
+
|
|
15
|
+
use crate::bridge::Bridge;
|
|
16
|
+
use magnus::value::Lazy;
|
|
17
|
+
use magnus::{Error, RModule, Ruby};
|
|
18
|
+
use std::sync::{LazyLock, OnceLock};
|
|
19
|
+
#[cfg(not(target_os = "windows"))]
|
|
20
|
+
use tikv_jemallocator::Jemalloc;
|
|
21
|
+
use tokio::runtime::Runtime;
|
|
22
|
+
|
|
23
|
+
mod admin;
|
|
24
|
+
mod bridge;
|
|
25
|
+
mod client;
|
|
26
|
+
mod gvl;
|
|
27
|
+
mod handler;
|
|
28
|
+
mod logging;
|
|
29
|
+
mod scheduler;
|
|
30
|
+
mod tracing_util;
|
|
31
|
+
mod util;
|
|
32
|
+
|
|
33
|
+
#[cfg(not(target_os = "windows"))]
|
|
34
|
+
#[global_allocator]
|
|
35
|
+
static GLOBAL: Jemalloc = Jemalloc;
|
|
36
|
+
|
|
37
|
+
/// Global instance of the Ruby-Rust communication bridge.
|
|
38
|
+
/// Initialized during extension startup and used throughout the library.
|
|
39
|
+
pub static BRIDGE: OnceLock<Bridge> = OnceLock::new();
|
|
40
|
+
|
|
41
|
+
/// Ensures tracing initialization occurs exactly once.
|
|
42
|
+
pub static TRACING_INIT: OnceLock<()> = OnceLock::new();
|
|
43
|
+
|
|
44
|
+
/// Global Tokio runtime for asynchronous operations.
|
|
45
|
+
///
|
|
46
|
+
/// This runtime powers all async operations in the extension, including
|
|
47
|
+
/// message processing, scheduling, and communication with Ruby.
|
|
48
|
+
#[allow(clippy::expect_used)]
|
|
49
|
+
static RUNTIME: LazyLock<Runtime> =
|
|
50
|
+
LazyLock::new(|| Runtime::new().expect("Failed to create Tokio runtime"));
|
|
51
|
+
|
|
52
|
+
/// Reference to the root Ruby module for this extension.
|
|
53
|
+
///
|
|
54
|
+
/// This is lazily initialized during extension startup and provides
|
|
55
|
+
/// access to the `Prosody` module in Ruby.
|
|
56
|
+
#[allow(clippy::expect_used)]
|
|
57
|
+
pub static ROOT_MOD: Lazy<RModule> = Lazy::new(|ruby| {
|
|
58
|
+
ruby.define_module("Prosody")
|
|
59
|
+
.expect("Failed to define Prosody module")
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/// Initializes the Prosody Ruby extension.
|
|
63
|
+
///
|
|
64
|
+
/// This function initializes the various components of the extension.
|
|
65
|
+
///
|
|
66
|
+
/// # Arguments
|
|
67
|
+
///
|
|
68
|
+
/// * `ruby` - Reference to the Ruby VM instance
|
|
69
|
+
///
|
|
70
|
+
/// # Errors
|
|
71
|
+
///
|
|
72
|
+
/// Returns a Magnus error if any initialization step fails, such as
|
|
73
|
+
/// defining Ruby classes or configuring components.
|
|
74
|
+
#[magnus::init]
|
|
75
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
76
|
+
admin::init(ruby)?;
|
|
77
|
+
bridge::init(ruby)?;
|
|
78
|
+
handler::init(ruby)?;
|
|
79
|
+
client::init(ruby)?;
|
|
80
|
+
|
|
81
|
+
Ok(())
|
|
82
|
+
}
|