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
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
use crate::bridge::Bridge;
|
|
8
8
|
use crate::handler::state::{
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
NativeJsonDequeState, NativeJsonMapState, NativeJsonValueState, NativeMessageDequeState,
|
|
10
|
+
NativeMessageMapState, NativeMessageValueState, state_error,
|
|
11
11
|
};
|
|
12
12
|
use crate::tracing_util::extract_opentelemetry_context;
|
|
13
13
|
use crate::{ROOT_MOD, id};
|
|
@@ -334,13 +334,13 @@ impl Context {
|
|
|
334
334
|
/// Returns a permanent state error if the name is unregistered or its
|
|
335
335
|
/// registered identity mismatches.
|
|
336
336
|
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
337
|
-
fn value_state(ruby: &Ruby, this: &Self, name: String) -> Result<
|
|
337
|
+
fn value_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonValueState, Error> {
|
|
338
338
|
let handle = this
|
|
339
339
|
.inner
|
|
340
340
|
.value_state(&name)
|
|
341
341
|
.map_err(|error| state_error(ruby, &error))?;
|
|
342
|
-
Ok(
|
|
343
|
-
|
|
342
|
+
Ok(NativeJsonValueState::new(
|
|
343
|
+
Arc::from(handle),
|
|
344
344
|
this.bridge.clone(),
|
|
345
345
|
Arc::clone(&this.propagator),
|
|
346
346
|
))
|
|
@@ -352,13 +352,13 @@ impl Context {
|
|
|
352
352
|
///
|
|
353
353
|
/// See [`value_state`](Self::value_state).
|
|
354
354
|
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
355
|
-
fn map_state(ruby: &Ruby, this: &Self, name: String) -> Result<
|
|
355
|
+
fn map_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonMapState, Error> {
|
|
356
356
|
let handle = this
|
|
357
357
|
.inner
|
|
358
358
|
.map_state(&name)
|
|
359
359
|
.map_err(|error| state_error(ruby, &error))?;
|
|
360
|
-
Ok(
|
|
361
|
-
|
|
360
|
+
Ok(NativeJsonMapState::new(
|
|
361
|
+
Arc::from(handle),
|
|
362
362
|
this.bridge.clone(),
|
|
363
363
|
Arc::clone(&this.propagator),
|
|
364
364
|
))
|
|
@@ -370,13 +370,13 @@ impl Context {
|
|
|
370
370
|
///
|
|
371
371
|
/// See [`value_state`](Self::value_state).
|
|
372
372
|
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
373
|
-
fn deque_state(ruby: &Ruby, this: &Self, name: String) -> Result<
|
|
373
|
+
fn deque_state(ruby: &Ruby, this: &Self, name: String) -> Result<NativeJsonDequeState, Error> {
|
|
374
374
|
let handle = this
|
|
375
375
|
.inner
|
|
376
376
|
.deque_state(&name)
|
|
377
377
|
.map_err(|error| state_error(ruby, &error))?;
|
|
378
|
-
Ok(
|
|
379
|
-
|
|
378
|
+
Ok(NativeJsonDequeState::new(
|
|
379
|
+
Arc::from(handle),
|
|
380
380
|
this.bridge.clone(),
|
|
381
381
|
Arc::clone(&this.propagator),
|
|
382
382
|
))
|
|
@@ -392,13 +392,13 @@ impl Context {
|
|
|
392
392
|
ruby: &Ruby,
|
|
393
393
|
this: &Self,
|
|
394
394
|
name: String,
|
|
395
|
-
) -> Result<
|
|
395
|
+
) -> Result<NativeMessageValueState, Error> {
|
|
396
396
|
let handle = this
|
|
397
397
|
.inner
|
|
398
398
|
.message_value_state(&name)
|
|
399
399
|
.map_err(|error| state_error(ruby, &error))?;
|
|
400
|
-
Ok(
|
|
401
|
-
|
|
400
|
+
Ok(NativeMessageValueState::new(
|
|
401
|
+
Arc::from(handle),
|
|
402
402
|
this.bridge.clone(),
|
|
403
403
|
Arc::clone(&this.propagator),
|
|
404
404
|
))
|
|
@@ -410,13 +410,17 @@ impl Context {
|
|
|
410
410
|
///
|
|
411
411
|
/// See [`value_state`](Self::value_state).
|
|
412
412
|
#[allow(clippy::needless_pass_by_value, reason = "Magnus method argument type")]
|
|
413
|
-
fn message_map_state(
|
|
413
|
+
fn message_map_state(
|
|
414
|
+
ruby: &Ruby,
|
|
415
|
+
this: &Self,
|
|
416
|
+
name: String,
|
|
417
|
+
) -> Result<NativeMessageMapState, Error> {
|
|
414
418
|
let handle = this
|
|
415
419
|
.inner
|
|
416
420
|
.message_map_state(&name)
|
|
417
421
|
.map_err(|error| state_error(ruby, &error))?;
|
|
418
|
-
Ok(
|
|
419
|
-
|
|
422
|
+
Ok(NativeMessageMapState::new(
|
|
423
|
+
Arc::from(handle),
|
|
420
424
|
this.bridge.clone(),
|
|
421
425
|
Arc::clone(&this.propagator),
|
|
422
426
|
))
|
|
@@ -432,13 +436,13 @@ impl Context {
|
|
|
432
436
|
ruby: &Ruby,
|
|
433
437
|
this: &Self,
|
|
434
438
|
name: String,
|
|
435
|
-
) -> Result<
|
|
439
|
+
) -> Result<NativeMessageDequeState, Error> {
|
|
436
440
|
let handle = this
|
|
437
441
|
.inner
|
|
438
442
|
.message_deque_state(&name)
|
|
439
443
|
.map_err(|error| state_error(ruby, &error))?;
|
|
440
|
-
Ok(
|
|
441
|
-
|
|
444
|
+
Ok(NativeMessageDequeState::new(
|
|
445
|
+
Arc::from(handle),
|
|
442
446
|
this.bridge.clone(),
|
|
443
447
|
Arc::clone(&this.propagator),
|
|
444
448
|
))
|
|
@@ -24,6 +24,15 @@ pub struct Message {
|
|
|
24
24
|
inner: ConsumerMessage<serde_json::Value>,
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/// Ruby wrapper for an excise record.
|
|
28
|
+
#[derive(Educe, Clone)]
|
|
29
|
+
#[educe(Debug)]
|
|
30
|
+
#[magnus::wrap(class = "Prosody::ExciseMessage", frozen_shareable)]
|
|
31
|
+
pub struct ExciseMessage {
|
|
32
|
+
#[educe(Debug(ignore))]
|
|
33
|
+
inner: ConsumerMessage<()>,
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
impl Message {
|
|
28
37
|
/// Returns the topic name this message was published to.
|
|
29
38
|
///
|
|
@@ -122,6 +131,34 @@ impl Message {
|
|
|
122
131
|
}
|
|
123
132
|
}
|
|
124
133
|
|
|
134
|
+
impl ExciseMessage {
|
|
135
|
+
fn topic(&self) -> &'static str {
|
|
136
|
+
self.inner.topic().as_ref()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn partition(&self) -> i32 {
|
|
140
|
+
self.inner.partition()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
fn offset(&self) -> i64 {
|
|
144
|
+
self.inner.offset()
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
fn key(&self) -> &str {
|
|
148
|
+
self.inner.key()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fn timestamp(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
152
|
+
let epoch_micros = this.inner.timestamp().timestamp_micros();
|
|
153
|
+
ruby.module_kernel()
|
|
154
|
+
.const_get::<_, RClass>(id!(ruby, "Time"))?
|
|
155
|
+
.funcall(
|
|
156
|
+
id!(ruby, "at"),
|
|
157
|
+
(epoch_micros, ruby.to_symbol("microsecond")),
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
125
162
|
impl From<ConsumerMessage<serde_json::Value>> for Message {
|
|
126
163
|
/// Creates a new Message wrapper from a Prosody `ConsumerMessage`.
|
|
127
164
|
///
|
|
@@ -133,6 +170,12 @@ impl From<ConsumerMessage<serde_json::Value>> for Message {
|
|
|
133
170
|
}
|
|
134
171
|
}
|
|
135
172
|
|
|
173
|
+
impl From<ConsumerMessage<()>> for ExciseMessage {
|
|
174
|
+
fn from(inner: ConsumerMessage<()>) -> Self {
|
|
175
|
+
Self { inner }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
136
179
|
/// Initializes the `Prosody::Message` Ruby class and defines its methods.
|
|
137
180
|
///
|
|
138
181
|
/// # Arguments
|
|
@@ -157,5 +200,12 @@ pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
157
200
|
class.define_method(id!(ruby, "timestamp"), method!(Message::timestamp, 0))?;
|
|
158
201
|
class.define_method(id!(ruby, "payload"), method!(Message::payload, 0))?;
|
|
159
202
|
|
|
203
|
+
let class = module.define_class(id!(ruby, "ExciseMessage"), ruby.class_object())?;
|
|
204
|
+
class.define_method(id!(ruby, "topic"), method!(ExciseMessage::topic, 0))?;
|
|
205
|
+
class.define_method(id!(ruby, "partition"), method!(ExciseMessage::partition, 0))?;
|
|
206
|
+
class.define_method(id!(ruby, "offset"), method!(ExciseMessage::offset, 0))?;
|
|
207
|
+
class.define_method(id!(ruby, "key"), method!(ExciseMessage::key, 0))?;
|
|
208
|
+
class.define_method(id!(ruby, "timestamp"), method!(ExciseMessage::timestamp, 0))?;
|
|
209
|
+
|
|
160
210
|
Ok(())
|
|
161
211
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
use crate::bridge::{Bridge, BridgeError};
|
|
13
13
|
use crate::handler::context::Context;
|
|
14
|
-
use crate::handler::message::Message;
|
|
14
|
+
use crate::handler::message::{ExciseMessage, Message};
|
|
15
15
|
use crate::handler::trigger::Timer;
|
|
16
16
|
use crate::id;
|
|
17
17
|
use crate::scheduler::result::ProcessingError;
|
|
@@ -19,7 +19,7 @@ use crate::scheduler::{Scheduler, SchedulerError};
|
|
|
19
19
|
use crate::util::ThreadSafeValue;
|
|
20
20
|
use futures::pin_mut;
|
|
21
21
|
use magnus::value::ReprValue;
|
|
22
|
-
use magnus::{Error, Ruby, Value};
|
|
22
|
+
use magnus::{Error, IntoValue, Ruby, Value};
|
|
23
23
|
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
24
24
|
use opentelemetry::trace::Status;
|
|
25
25
|
use prosody::consumer::event_context::EventContext;
|
|
@@ -27,13 +27,14 @@ use prosody::consumer::message::ConsumerMessage;
|
|
|
27
27
|
use prosody::consumer::middleware::FallibleHandler;
|
|
28
28
|
use prosody::consumer::{DemandType, Keyed};
|
|
29
29
|
use prosody::error::{ClassifyError, ErrorCategory};
|
|
30
|
+
use prosody::high_level::{ClientHandler, JsonCodecs};
|
|
30
31
|
use prosody::propagator::new_propagator;
|
|
31
32
|
use prosody::timers::{TimerType, Trigger as ProsodyTrigger};
|
|
32
33
|
use std::collections::HashMap;
|
|
33
34
|
use std::sync::Arc;
|
|
34
35
|
use thiserror::Error;
|
|
35
36
|
use tokio::select;
|
|
36
|
-
use tracing::{Instrument, info_span};
|
|
37
|
+
use tracing::{Instrument, Span, info_span};
|
|
37
38
|
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
|
38
39
|
|
|
39
40
|
mod context;
|
|
@@ -41,6 +42,11 @@ mod message;
|
|
|
41
42
|
mod state;
|
|
42
43
|
mod trigger;
|
|
43
44
|
|
|
45
|
+
pub(crate) use state::{
|
|
46
|
+
NativeJsonDequeScan, NativeJsonMapScan, NativeMapKeyScan, parse_direction,
|
|
47
|
+
published_deque_scan, published_map_key_scan, published_map_scan,
|
|
48
|
+
};
|
|
49
|
+
|
|
44
50
|
/// A handler that bridges between Kafka messages and Ruby message processing
|
|
45
51
|
/// code.
|
|
46
52
|
///
|
|
@@ -84,11 +90,78 @@ impl RubyHandler {
|
|
|
84
90
|
propagator: Arc::new(new_propagator()),
|
|
85
91
|
})
|
|
86
92
|
}
|
|
93
|
+
|
|
94
|
+
async fn handle_record<C, P, M>(
|
|
95
|
+
&self,
|
|
96
|
+
context: C,
|
|
97
|
+
message: ConsumerMessage<P>,
|
|
98
|
+
method: &'static str,
|
|
99
|
+
event_type: &'static str,
|
|
100
|
+
span: Span,
|
|
101
|
+
) -> Result<serde_json::Value, RubyHandlerError>
|
|
102
|
+
where
|
|
103
|
+
C: EventContext<Payload = serde_json::Value>,
|
|
104
|
+
M: From<ConsumerMessage<P>> + IntoValue + Send + 'static,
|
|
105
|
+
P: Send + Sync + 'static,
|
|
106
|
+
{
|
|
107
|
+
let cancel_future = context.clone().on_cancel();
|
|
108
|
+
let handler = self.handler.clone();
|
|
109
|
+
let task_id = format!(
|
|
110
|
+
"{}/{}:{}",
|
|
111
|
+
message.topic(),
|
|
112
|
+
message.partition(),
|
|
113
|
+
message.offset()
|
|
114
|
+
);
|
|
115
|
+
let event_context = HashMap::from([
|
|
116
|
+
("event_type".into(), event_type.into()),
|
|
117
|
+
("topic".into(), message.topic().to_string()),
|
|
118
|
+
("partition".into(), message.partition().to_string()),
|
|
119
|
+
("key".into(), message.key().to_string()),
|
|
120
|
+
("offset".into(), message.offset().to_string()),
|
|
121
|
+
]);
|
|
122
|
+
let context = Context::new(
|
|
123
|
+
context.boxed(),
|
|
124
|
+
self.bridge.clone(),
|
|
125
|
+
self.propagator.clone(),
|
|
126
|
+
);
|
|
127
|
+
let response_requested = message.response_requested();
|
|
128
|
+
let message = M::from(message);
|
|
129
|
+
let cloned_span = span.clone();
|
|
130
|
+
|
|
131
|
+
async move {
|
|
132
|
+
let task_handle = self
|
|
133
|
+
.scheduler
|
|
134
|
+
.schedule(task_id, &cloned_span, event_context, move |ruby| {
|
|
135
|
+
let result = handler.get(ruby).funcall(method, (context, message))?;
|
|
136
|
+
Ok(if response_requested {
|
|
137
|
+
result
|
|
138
|
+
} else {
|
|
139
|
+
ruby.qnil().as_value()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
.await?;
|
|
143
|
+
let result_future = task_handle.result.receive();
|
|
144
|
+
pin_mut!(result_future);
|
|
145
|
+
let result = select! {
|
|
146
|
+
result = &mut result_future => {
|
|
147
|
+
result.inspect_err(|error| cloned_span.set_status(Status::error(error.to_string())))?
|
|
148
|
+
}
|
|
149
|
+
() = cancel_future => {
|
|
150
|
+
task_handle.cancellation_token.cancel(&self.bridge).await
|
|
151
|
+
.inspect_err(|error| cloned_span.set_status(Status::error(error.to_string())))?;
|
|
152
|
+
result_future.await?
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
Ok(result)
|
|
156
|
+
}
|
|
157
|
+
.instrument(span)
|
|
158
|
+
.await
|
|
159
|
+
}
|
|
87
160
|
}
|
|
88
161
|
|
|
89
162
|
impl FallibleHandler for RubyHandler {
|
|
90
163
|
type Error = RubyHandlerError;
|
|
91
|
-
type Output =
|
|
164
|
+
type Output = serde_json::Value;
|
|
92
165
|
type Payload = serde_json::Value;
|
|
93
166
|
|
|
94
167
|
/// Processes a Kafka message by dispatching it to the Ruby handler.
|
|
@@ -116,12 +189,10 @@ impl FallibleHandler for RubyHandler {
|
|
|
116
189
|
context: C,
|
|
117
190
|
message: ConsumerMessage<Self::Payload>,
|
|
118
191
|
_demand_type: DemandType,
|
|
119
|
-
) -> Result<
|
|
192
|
+
) -> Result<Self::Output, Self::Error>
|
|
120
193
|
where
|
|
121
194
|
C: EventContext<Payload = Self::Payload>,
|
|
122
195
|
{
|
|
123
|
-
// Create a new span for the on_message operation as a child of the message's
|
|
124
|
-
// span
|
|
125
196
|
let span = info_span!(
|
|
126
197
|
parent: message.span(),
|
|
127
198
|
"on_message",
|
|
@@ -130,75 +201,29 @@ impl FallibleHandler for RubyHandler {
|
|
|
130
201
|
offset = message.offset(),
|
|
131
202
|
key = %message.key()
|
|
132
203
|
);
|
|
204
|
+
self.handle_record::<_, _, Message>(context, message, "on_message", "message", span)
|
|
205
|
+
.await
|
|
206
|
+
}
|
|
133
207
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
message.
|
|
145
|
-
|
|
146
|
-
message.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
("event_type".into(), "message".into()),
|
|
151
|
-
("topic".into(), message.topic().to_string()),
|
|
152
|
-
("partition".into(), message.partition().to_string()),
|
|
153
|
-
("key".into(), message.key().to_string()),
|
|
154
|
-
("offset".into(), message.offset().to_string()),
|
|
155
|
-
]);
|
|
156
|
-
|
|
157
|
-
// Convert the Kafka message and context to Ruby-compatible types
|
|
158
|
-
let context = Context::new(
|
|
159
|
-
context.boxed(),
|
|
160
|
-
self.bridge.clone(),
|
|
161
|
-
self.propagator.clone(),
|
|
208
|
+
async fn on_excise<C>(
|
|
209
|
+
&self,
|
|
210
|
+
context: C,
|
|
211
|
+
message: ConsumerMessage<()>,
|
|
212
|
+
_demand_type: DemandType,
|
|
213
|
+
) -> Result<Self::Output, Self::Error>
|
|
214
|
+
where
|
|
215
|
+
C: EventContext<Payload = Self::Payload>,
|
|
216
|
+
{
|
|
217
|
+
let span = info_span!(
|
|
218
|
+
parent: message.span(),
|
|
219
|
+
"on_excise",
|
|
220
|
+
topic = %message.topic(),
|
|
221
|
+
partition = message.partition(),
|
|
222
|
+
offset = message.offset(),
|
|
223
|
+
key = %message.key()
|
|
162
224
|
);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// Execute the entire message handling operation within the span
|
|
166
|
-
let cloned_span = span.clone();
|
|
167
|
-
async move {
|
|
168
|
-
// Schedule the task to run in Ruby
|
|
169
|
-
let task_handle = self
|
|
170
|
-
.scheduler
|
|
171
|
-
.schedule(task_id, &cloned_span, event_context, move |ruby| {
|
|
172
|
-
let _: Value = handler
|
|
173
|
-
.get(ruby)
|
|
174
|
-
.funcall(id!(ruby, "on_message"), (context, message))?;
|
|
175
|
-
|
|
176
|
-
Ok(())
|
|
177
|
-
})
|
|
178
|
-
.await?;
|
|
179
|
-
|
|
180
|
-
// Get the future that will complete when the task is done
|
|
181
|
-
let result_future = task_handle.result.receive();
|
|
182
|
-
pin_mut!(result_future);
|
|
183
|
-
|
|
184
|
-
// Wait for either task completion or shutdown signal
|
|
185
|
-
select! {
|
|
186
|
-
result = &mut result_future => {
|
|
187
|
-
result.inspect_err(|e| cloned_span.set_status(Status::error(e.to_string())))?;
|
|
188
|
-
}
|
|
189
|
-
() = cancel_future => {
|
|
190
|
-
// A cancel() failure is a genuine bridge error; mark the span.
|
|
191
|
-
// The subsequent result_future error is expected cancellation, not a handler bug.
|
|
192
|
-
task_handle.cancellation_token.cancel(&self.bridge).await
|
|
193
|
-
.inspect_err(|e| cloned_span.set_status(Status::error(e.to_string())))?;
|
|
194
|
-
result_future.await?;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
Ok(())
|
|
199
|
-
}
|
|
200
|
-
.instrument(span)
|
|
201
|
-
.await
|
|
225
|
+
self.handle_record::<_, _, ExciseMessage>(context, message, "on_excise", "excise", span)
|
|
226
|
+
.await
|
|
202
227
|
}
|
|
203
228
|
|
|
204
229
|
async fn on_timer<C>(
|
|
@@ -206,13 +231,13 @@ impl FallibleHandler for RubyHandler {
|
|
|
206
231
|
context: C,
|
|
207
232
|
trigger: ProsodyTrigger,
|
|
208
233
|
_demand_type: DemandType,
|
|
209
|
-
) -> Result<
|
|
234
|
+
) -> Result<Self::Output, Self::Error>
|
|
210
235
|
where
|
|
211
236
|
C: EventContext<Payload = Self::Payload>,
|
|
212
237
|
{
|
|
213
238
|
// Only process application timers; internal timers are handled by middleware
|
|
214
239
|
if trigger.timer_type != TimerType::Application {
|
|
215
|
-
return Ok(
|
|
240
|
+
return Ok(serde_json::Value::Null);
|
|
216
241
|
}
|
|
217
242
|
|
|
218
243
|
// Create a new span for the on_timer operation as a child of the trigger's span
|
|
@@ -257,8 +282,7 @@ impl FallibleHandler for RubyHandler {
|
|
|
257
282
|
let _: Value = handler
|
|
258
283
|
.get(ruby)
|
|
259
284
|
.funcall(id!(ruby, "on_timer"), (context, timer))?;
|
|
260
|
-
|
|
261
|
-
Ok(())
|
|
285
|
+
Ok(ruby.qnil().as_value())
|
|
262
286
|
})
|
|
263
287
|
.await?;
|
|
264
288
|
|
|
@@ -267,20 +291,20 @@ impl FallibleHandler for RubyHandler {
|
|
|
267
291
|
pin_mut!(result_future);
|
|
268
292
|
|
|
269
293
|
// Wait for either task completion or shutdown signal
|
|
270
|
-
select! {
|
|
294
|
+
let result = select! {
|
|
271
295
|
result = &mut result_future => {
|
|
272
|
-
result.inspect_err(|e| cloned_span.set_status(Status::error(e.to_string())))
|
|
296
|
+
result.inspect_err(|e| cloned_span.set_status(Status::error(e.to_string())))?
|
|
273
297
|
}
|
|
274
298
|
() = cancel_future => {
|
|
275
299
|
// A cancel() failure is a genuine bridge error; mark the span.
|
|
276
300
|
// The subsequent result_future error is expected cancellation, not a handler bug.
|
|
277
301
|
task_handle.cancellation_token.cancel(&self.bridge).await
|
|
278
302
|
.inspect_err(|e| cloned_span.set_status(Status::error(e.to_string())))?;
|
|
279
|
-
result_future.await
|
|
303
|
+
result_future.await?
|
|
280
304
|
}
|
|
281
|
-
}
|
|
305
|
+
};
|
|
282
306
|
|
|
283
|
-
Ok(
|
|
307
|
+
Ok(result)
|
|
284
308
|
}
|
|
285
309
|
.instrument(span)
|
|
286
310
|
.await
|
|
@@ -295,6 +319,10 @@ impl FallibleHandler for RubyHandler {
|
|
|
295
319
|
}
|
|
296
320
|
}
|
|
297
321
|
|
|
322
|
+
impl ClientHandler for RubyHandler {
|
|
323
|
+
type Codecs = JsonCodecs;
|
|
324
|
+
}
|
|
325
|
+
|
|
298
326
|
impl ClassifyError for RubyHandlerError {
|
|
299
327
|
/// Categorizes errors for proper retry handling in the Kafka consumer.
|
|
300
328
|
///
|
|
@@ -341,7 +369,7 @@ pub enum RubyHandlerError {
|
|
|
341
369
|
pub fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
342
370
|
context::init(ruby)?;
|
|
343
371
|
message::init(ruby)?;
|
|
344
|
-
state::
|
|
372
|
+
state::register(ruby)?;
|
|
345
373
|
trigger::init(ruby)?;
|
|
346
374
|
|
|
347
375
|
Ok(())
|