prosody 0.3.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 +14 -4
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +1 -0
- data/CONFIGURATION.md +167 -0
- data/Cargo.lock +1115 -645
- data/Cargo.toml +7 -6
- data/README.md +436 -146
- data/Rakefile +11 -1
- data/examples/keyed_state.rb +70 -0
- data/examples/keyed_state.rbs +18 -0
- data/examples/keyed_state_windowing.rb +55 -0
- data/examples/keyed_state_windowing.rbs +16 -0
- data/ext/prosody/Cargo.toml +1 -0
- data/ext/prosody/src/admin.rs +1 -5
- data/ext/prosody/src/bridge/mod.rs +17 -32
- data/ext/prosody/src/client/config.rs +501 -28
- 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 +150 -5
- data/ext/prosody/src/handler/message.rs +67 -0
- data/ext/prosody/src/handler/mod.rs +115 -85
- 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 +71 -11
- data/lib/prosody/handler.rb +65 -8
- data/lib/prosody/native_stubs.rb +550 -9
- data/lib/prosody/request.rb +45 -0
- data/lib/prosody/state.rb +816 -0
- data/lib/prosody/version.rb +1 -1
- data/lib/prosody.rb +6 -0
- data/release-please-config.json +4 -0
- data/sig/configuration.rbs +70 -11
- data/sig/handler.rbs +17 -5
- data/sig/processor.rbs +28 -12
- data/sig/prosody.rbs +53 -7
- data/sig/request.rbs +66 -0
- data/sig/sentry.rbs +6 -0
- data/sig/state.rbs +390 -0
- data/steep_expectations.yml +57 -0
- data/typecheck/payload_types.rb +54 -0
- data/typecheck/payload_types.rbs +22 -0
- data/typecheck_negative/payload_types.rb +20 -0
- data/typecheck_negative/payload_types.rbs +9 -0
- metadata +32 -9
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//! Concrete typed cursors for native state scans.
|
|
2
|
+
//!
|
|
3
|
+
//! Cancellation can discard an orphaned chunk. The attempt then closes the
|
|
4
|
+
//! cursor through the Ruby `ensure`, so no later operation observes that chunk.
|
|
5
|
+
|
|
6
|
+
use super::state_error;
|
|
7
|
+
use crate::bridge::{Bridge, QUEUE_CLASS};
|
|
8
|
+
use crate::handler::message::Message;
|
|
9
|
+
use crate::id;
|
|
10
|
+
use crate::tracing_util::extract_opentelemetry_context;
|
|
11
|
+
use crate::util::ThreadSafeValue;
|
|
12
|
+
use magnus::value::ReprValue;
|
|
13
|
+
use magnus::{Error, IntoValue, Ruby, Value};
|
|
14
|
+
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
15
|
+
use opentelemetry::trace::FutureExt;
|
|
16
|
+
use prosody::consumer::event_context::StateCursor;
|
|
17
|
+
use prosody::consumer::message::ConsumerMessage;
|
|
18
|
+
use serde_json::Value as JsonValue;
|
|
19
|
+
use serde_magnus::serialize;
|
|
20
|
+
use std::cell::RefCell;
|
|
21
|
+
use std::collections::VecDeque;
|
|
22
|
+
use std::num::NonZeroUsize;
|
|
23
|
+
use std::sync::Arc;
|
|
24
|
+
use tracing::Span;
|
|
25
|
+
|
|
26
|
+
#[allow(clippy::unwrap_used, reason = "256 is a nonzero literal")]
|
|
27
|
+
const SCAN_READY_CHUNK_SIZE: NonZeroUsize = NonZeroUsize::new(256).unwrap();
|
|
28
|
+
|
|
29
|
+
struct ScanInner<T> {
|
|
30
|
+
cursor: Arc<StateCursor<T>>,
|
|
31
|
+
buffer: VecDeque<T>,
|
|
32
|
+
done: bool,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
macro_rules! drive_scan {
|
|
36
|
+
($ruby:expr, $this:expr, $inner:expr, |$item:ident| $convert:block) => {{
|
|
37
|
+
loop {
|
|
38
|
+
if let Some($item) = $inner.buffer.pop_front() {
|
|
39
|
+
return $convert;
|
|
40
|
+
}
|
|
41
|
+
if $inner.done {
|
|
42
|
+
return Ok($ruby.qnil().as_value());
|
|
43
|
+
}
|
|
44
|
+
let cursor = Arc::clone(&$inner.cursor);
|
|
45
|
+
let context = extract_opentelemetry_context($ruby, &$this.propagator)?;
|
|
46
|
+
let chunk = $this
|
|
47
|
+
.bridge
|
|
48
|
+
.wait_for(
|
|
49
|
+
$ruby,
|
|
50
|
+
async move {
|
|
51
|
+
cursor
|
|
52
|
+
.next_ready_chunk(SCAN_READY_CHUNK_SIZE)
|
|
53
|
+
.with_context(context)
|
|
54
|
+
.await
|
|
55
|
+
},
|
|
56
|
+
Span::current(),
|
|
57
|
+
)?
|
|
58
|
+
.map_err(|error| state_error($ruby, &error))?;
|
|
59
|
+
match chunk {
|
|
60
|
+
Some(items) => $inner.buffer.extend(items),
|
|
61
|
+
None => {
|
|
62
|
+
$inner.done = true;
|
|
63
|
+
let cursor = Arc::clone(&$inner.cursor);
|
|
64
|
+
$this.bridge.wait_for(
|
|
65
|
+
$ruby,
|
|
66
|
+
async move { cursor.close().await },
|
|
67
|
+
Span::current(),
|
|
68
|
+
)?;
|
|
69
|
+
return Ok($ruby.qnil().as_value());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
macro_rules! native_scan {
|
|
77
|
+
($name:ident, $class:literal, $item:ty, |$ruby:ident, $item_name:ident| $convert:block) => {
|
|
78
|
+
/// Native cursor with one item type.
|
|
79
|
+
#[magnus::wrap(class = $class)]
|
|
80
|
+
pub struct $name {
|
|
81
|
+
inner: RefCell<ScanInner<$item>>,
|
|
82
|
+
lock: ThreadSafeValue,
|
|
83
|
+
bridge: Bridge,
|
|
84
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl $name {
|
|
88
|
+
pub(super) fn new(
|
|
89
|
+
ruby: &Ruby,
|
|
90
|
+
cursor: Box<StateCursor<$item>>,
|
|
91
|
+
bridge: Bridge,
|
|
92
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
93
|
+
) -> Result<Self, Error> {
|
|
94
|
+
let queue: Value = ruby.get_inner(&QUEUE_CLASS).funcall(id!(ruby, "new"), ())?;
|
|
95
|
+
let _: Value = queue.funcall(id!(ruby, "push"), (ruby.qnil(),))?;
|
|
96
|
+
Ok(Self {
|
|
97
|
+
inner: RefCell::new(ScanInner {
|
|
98
|
+
cursor: Arc::from(cursor),
|
|
99
|
+
buffer: VecDeque::new(),
|
|
100
|
+
done: false,
|
|
101
|
+
}),
|
|
102
|
+
lock: ThreadSafeValue::new(queue, bridge.clone()),
|
|
103
|
+
bridge,
|
|
104
|
+
propagator,
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn acquire(&self, ruby: &Ruby) -> Result<(), Error> {
|
|
109
|
+
let _: Value = self.lock.get(ruby).funcall(id!(ruby, "pop"), ())?;
|
|
110
|
+
Ok(())
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn release(&self, ruby: &Ruby) {
|
|
114
|
+
let _: Result<Value, Error> = self
|
|
115
|
+
.lock
|
|
116
|
+
.get(ruby)
|
|
117
|
+
.funcall(id!(ruby, "push"), (ruby.qnil(),));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
pub(super) fn next(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
121
|
+
this.acquire(ruby)?;
|
|
122
|
+
let out = Self::next_locked(ruby, this);
|
|
123
|
+
this.release(ruby);
|
|
124
|
+
out
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn next_locked(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
128
|
+
let inner = &mut *this.inner.borrow_mut();
|
|
129
|
+
drive_scan!(ruby, this, inner, |$item_name| {
|
|
130
|
+
let $ruby = ruby;
|
|
131
|
+
$convert
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pub(super) fn close(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
136
|
+
this.acquire(ruby)?;
|
|
137
|
+
let out = Self::close_locked(ruby, this);
|
|
138
|
+
this.release(ruby);
|
|
139
|
+
out
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn close_locked(ruby: &Ruby, this: &Self) -> Result<Value, Error> {
|
|
143
|
+
let inner = &mut *this.inner.borrow_mut();
|
|
144
|
+
inner.done = true;
|
|
145
|
+
inner.buffer.clear();
|
|
146
|
+
let cursor = Arc::clone(&inner.cursor);
|
|
147
|
+
this.bridge
|
|
148
|
+
.wait_for(ruby, async move { cursor.close().await }, Span::current())?;
|
|
149
|
+
Ok(ruby.qnil().as_value())
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
native_scan!(
|
|
156
|
+
NativeJsonDequeScan,
|
|
157
|
+
"Prosody::NativeJsonDequeScan",
|
|
158
|
+
JsonValue,
|
|
159
|
+
|ruby, item| { serialize(ruby, &item) }
|
|
160
|
+
);
|
|
161
|
+
native_scan!(
|
|
162
|
+
NativeJsonMapScan,
|
|
163
|
+
"Prosody::NativeJsonMapScan",
|
|
164
|
+
(String, JsonValue),
|
|
165
|
+
|ruby, item| {
|
|
166
|
+
let (key, value) = item;
|
|
167
|
+
let value: Value = serialize(ruby, &value)?;
|
|
168
|
+
Ok((key, value).into_value_with(ruby))
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
native_scan!(
|
|
172
|
+
NativeMessageDequeScan,
|
|
173
|
+
"Prosody::NativeMessageDequeScan",
|
|
174
|
+
ConsumerMessage<JsonValue>,
|
|
175
|
+
|ruby, item| { Ok(Message::from(item).into_value_with(ruby)) }
|
|
176
|
+
);
|
|
177
|
+
native_scan!(
|
|
178
|
+
NativeMessageMapScan,
|
|
179
|
+
"Prosody::NativeMessageMapScan",
|
|
180
|
+
(String, ConsumerMessage<JsonValue>),
|
|
181
|
+
|ruby, item| {
|
|
182
|
+
let (key, message) = item;
|
|
183
|
+
Ok((key, Message::from(message)).into_value_with(ruby))
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
native_scan!(
|
|
187
|
+
NativeMapKeyScan,
|
|
188
|
+
"Prosody::NativeMapKeyScan",
|
|
189
|
+
String,
|
|
190
|
+
|ruby, item| { Ok(item.into_value_with(ruby)) }
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
pub(crate) fn published_map_scan(
|
|
194
|
+
ruby: &Ruby,
|
|
195
|
+
cursor: Box<StateCursor<(String, JsonValue)>>,
|
|
196
|
+
bridge: Bridge,
|
|
197
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
198
|
+
) -> Result<NativeJsonMapScan, Error> {
|
|
199
|
+
NativeJsonMapScan::new(ruby, cursor, bridge, propagator)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
pub(crate) fn published_map_key_scan(
|
|
203
|
+
ruby: &Ruby,
|
|
204
|
+
cursor: Box<StateCursor<String>>,
|
|
205
|
+
bridge: Bridge,
|
|
206
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
207
|
+
) -> Result<NativeMapKeyScan, Error> {
|
|
208
|
+
NativeMapKeyScan::new(ruby, cursor, bridge, propagator)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
pub(crate) fn published_deque_scan(
|
|
212
|
+
ruby: &Ruby,
|
|
213
|
+
cursor: Box<StateCursor<JsonValue>>,
|
|
214
|
+
bridge: Bridge,
|
|
215
|
+
propagator: Arc<TextMapCompositePropagator>,
|
|
216
|
+
) -> Result<NativeJsonDequeScan, Error> {
|
|
217
|
+
NativeJsonDequeScan::new(ruby, cursor, bridge, propagator)
|
|
218
|
+
}
|
data/ext/prosody/src/lib.rs
CHANGED
|
@@ -17,6 +17,8 @@ use crate::bridge::Bridge;
|
|
|
17
17
|
use magnus::value::Lazy;
|
|
18
18
|
use magnus::{Error, RModule, Ruby};
|
|
19
19
|
use mimalloc::MiMalloc;
|
|
20
|
+
use std::io::{self, Write};
|
|
21
|
+
use std::process;
|
|
20
22
|
use std::sync::{LazyLock, OnceLock};
|
|
21
23
|
use tokio::runtime::Runtime;
|
|
22
24
|
|
|
@@ -26,6 +28,7 @@ mod client;
|
|
|
26
28
|
mod gvl;
|
|
27
29
|
mod handler;
|
|
28
30
|
mod logging;
|
|
31
|
+
mod published;
|
|
29
32
|
mod scheduler;
|
|
30
33
|
mod tracing_util;
|
|
31
34
|
mod util;
|
|
@@ -44,9 +47,16 @@ pub static TRACING_INIT: OnceLock<()> = OnceLock::new();
|
|
|
44
47
|
///
|
|
45
48
|
/// This runtime powers all async operations in the extension, including
|
|
46
49
|
/// message processing, scheduling, and communication with Ruby.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
}
|
|
59
|
+
});
|
|
50
60
|
|
|
51
61
|
/// Reference to the root Ruby module for this extension.
|
|
52
62
|
///
|
|
@@ -75,7 +85,9 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
75
85
|
admin::init(ruby)?;
|
|
76
86
|
bridge::init(ruby)?;
|
|
77
87
|
handler::init(ruby)?;
|
|
88
|
+
published::init(ruby)?;
|
|
78
89
|
client::init(ruby)?;
|
|
90
|
+
util::init(ruby)?;
|
|
79
91
|
|
|
80
92
|
Ok(())
|
|
81
93
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
//! Read-only published-state handles for Ruby.
|
|
2
|
+
|
|
3
|
+
use crate::bridge::Bridge;
|
|
4
|
+
use crate::handler::{
|
|
5
|
+
NativeJsonDequeScan, NativeJsonMapScan, NativeMapKeyScan, parse_direction,
|
|
6
|
+
published_deque_scan, published_map_key_scan, published_map_scan,
|
|
7
|
+
};
|
|
8
|
+
use crate::{ROOT_MOD, id};
|
|
9
|
+
use magnus::value::ReprValue;
|
|
10
|
+
use magnus::{Error, Module, Ruby, StaticSymbol, Value, method};
|
|
11
|
+
use opentelemetry::propagation::TextMapCompositePropagator;
|
|
12
|
+
use prosody::JsonCodec;
|
|
13
|
+
use prosody::high_level::erased::{
|
|
14
|
+
ErasedDirection, SharedDequeReader, SharedMapReader, SharedValueReader,
|
|
15
|
+
};
|
|
16
|
+
use prosody::state::Direction;
|
|
17
|
+
use serde_magnus::serialize;
|
|
18
|
+
use std::sync::Arc;
|
|
19
|
+
use tracing::Span;
|
|
20
|
+
|
|
21
|
+
fn read_error(ruby: &Ruby, error: &impl ToString) -> Error {
|
|
22
|
+
Error::new(ruby.exception_runtime_error(), error.to_string())
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn erased_direction(direction: Direction) -> ErasedDirection {
|
|
26
|
+
match direction {
|
|
27
|
+
Direction::Forward => ErasedDirection::Forward,
|
|
28
|
+
Direction::Backward => ErasedDirection::Backward,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[magnus::wrap(class = "Prosody::NativePublishedValue")]
|
|
33
|
+
pub(crate) struct NativePublishedValue {
|
|
34
|
+
pub(crate) inner: SharedValueReader<JsonCodec>,
|
|
35
|
+
pub(crate) bridge: Bridge,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl NativePublishedValue {
|
|
39
|
+
fn get(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
40
|
+
let inner = Arc::clone(&this.inner);
|
|
41
|
+
let value = this
|
|
42
|
+
.bridge
|
|
43
|
+
.wait_for(ruby, async move { inner.get(key).await }, Span::current())?
|
|
44
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
45
|
+
match value {
|
|
46
|
+
Some(value) => serialize(ruby, &value),
|
|
47
|
+
None => Ok(ruby.qnil().as_value()),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[magnus::wrap(class = "Prosody::NativePublishedMap")]
|
|
53
|
+
pub(crate) struct NativePublishedMap {
|
|
54
|
+
pub(crate) inner: SharedMapReader<JsonCodec>,
|
|
55
|
+
pub(crate) bridge: Bridge,
|
|
56
|
+
pub(crate) propagator: Arc<TextMapCompositePropagator>,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
impl NativePublishedMap {
|
|
60
|
+
fn get(ruby: &Ruby, this: &Self, key: String, map_key: String) -> Result<Value, Error> {
|
|
61
|
+
let inner = Arc::clone(&this.inner);
|
|
62
|
+
let value = this
|
|
63
|
+
.bridge
|
|
64
|
+
.wait_for(
|
|
65
|
+
ruby,
|
|
66
|
+
async move { inner.get(key, map_key).await },
|
|
67
|
+
Span::current(),
|
|
68
|
+
)?
|
|
69
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
70
|
+
match value {
|
|
71
|
+
Some(value) => serialize(ruby, &value),
|
|
72
|
+
None => Ok(ruby.qnil().as_value()),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fn get_many(
|
|
77
|
+
ruby: &Ruby,
|
|
78
|
+
this: &Self,
|
|
79
|
+
key: String,
|
|
80
|
+
map_keys: Vec<String>,
|
|
81
|
+
) -> Result<Value, Error> {
|
|
82
|
+
let inner = Arc::clone(&this.inner);
|
|
83
|
+
let values = this
|
|
84
|
+
.bridge
|
|
85
|
+
.wait_for(
|
|
86
|
+
ruby,
|
|
87
|
+
async move { inner.get_many(key, map_keys).await },
|
|
88
|
+
Span::current(),
|
|
89
|
+
)?
|
|
90
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
91
|
+
serialize(ruby, &values)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn contains_key(ruby: &Ruby, this: &Self, key: String, map_key: String) -> Result<bool, Error> {
|
|
95
|
+
let inner = Arc::clone(&this.inner);
|
|
96
|
+
this.bridge
|
|
97
|
+
.wait_for(
|
|
98
|
+
ruby,
|
|
99
|
+
async move { inner.contains_key(key, map_key).await },
|
|
100
|
+
Span::current(),
|
|
101
|
+
)?
|
|
102
|
+
.map_err(|error| read_error(ruby, &error))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn scan(
|
|
106
|
+
ruby: &Ruby,
|
|
107
|
+
this: &Self,
|
|
108
|
+
key: String,
|
|
109
|
+
direction: StaticSymbol,
|
|
110
|
+
) -> Result<NativeJsonMapScan, Error> {
|
|
111
|
+
let direction = erased_direction(parse_direction(ruby, direction)?);
|
|
112
|
+
let inner = Arc::clone(&this.inner);
|
|
113
|
+
let cursor = this
|
|
114
|
+
.bridge
|
|
115
|
+
.wait_for(
|
|
116
|
+
ruby,
|
|
117
|
+
async move { inner.stream(key, direction).await },
|
|
118
|
+
Span::current(),
|
|
119
|
+
)?
|
|
120
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
121
|
+
published_map_scan(
|
|
122
|
+
ruby,
|
|
123
|
+
cursor,
|
|
124
|
+
this.bridge.clone(),
|
|
125
|
+
Arc::clone(&this.propagator),
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn keys(
|
|
130
|
+
ruby: &Ruby,
|
|
131
|
+
this: &Self,
|
|
132
|
+
key: String,
|
|
133
|
+
direction: StaticSymbol,
|
|
134
|
+
) -> Result<NativeMapKeyScan, Error> {
|
|
135
|
+
let direction = erased_direction(parse_direction(ruby, direction)?);
|
|
136
|
+
let inner = Arc::clone(&this.inner);
|
|
137
|
+
let cursor = this
|
|
138
|
+
.bridge
|
|
139
|
+
.wait_for(
|
|
140
|
+
ruby,
|
|
141
|
+
async move { inner.keys(key, direction).await },
|
|
142
|
+
Span::current(),
|
|
143
|
+
)?
|
|
144
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
145
|
+
published_map_key_scan(
|
|
146
|
+
ruby,
|
|
147
|
+
cursor,
|
|
148
|
+
this.bridge.clone(),
|
|
149
|
+
Arc::clone(&this.propagator),
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[magnus::wrap(class = "Prosody::NativePublishedDeque")]
|
|
155
|
+
pub(crate) struct NativePublishedDeque {
|
|
156
|
+
pub(crate) inner: SharedDequeReader<JsonCodec>,
|
|
157
|
+
pub(crate) bridge: Bridge,
|
|
158
|
+
pub(crate) propagator: Arc<TextMapCompositePropagator>,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl NativePublishedDeque {
|
|
162
|
+
fn get(ruby: &Ruby, this: &Self, key: String, index: usize) -> Result<Value, Error> {
|
|
163
|
+
let inner = Arc::clone(&this.inner);
|
|
164
|
+
let value = this
|
|
165
|
+
.bridge
|
|
166
|
+
.wait_for(
|
|
167
|
+
ruby,
|
|
168
|
+
async move { inner.get(key, index).await },
|
|
169
|
+
Span::current(),
|
|
170
|
+
)?
|
|
171
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
172
|
+
match value {
|
|
173
|
+
Some(value) => serialize(ruby, &value),
|
|
174
|
+
None => Ok(ruby.qnil().as_value()),
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn length(ruby: &Ruby, this: &Self, key: String) -> Result<usize, Error> {
|
|
179
|
+
let inner = Arc::clone(&this.inner);
|
|
180
|
+
this.bridge
|
|
181
|
+
.wait_for(ruby, async move { inner.len(key).await }, Span::current())?
|
|
182
|
+
.map_err(|error| read_error(ruby, &error))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fn is_empty(ruby: &Ruby, this: &Self, key: String) -> Result<bool, Error> {
|
|
186
|
+
let inner = Arc::clone(&this.inner);
|
|
187
|
+
this.bridge
|
|
188
|
+
.wait_for(
|
|
189
|
+
ruby,
|
|
190
|
+
async move { inner.is_empty(key).await },
|
|
191
|
+
Span::current(),
|
|
192
|
+
)?
|
|
193
|
+
.map_err(|error| read_error(ruby, &error))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fn peek_front(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
197
|
+
let inner = Arc::clone(&this.inner);
|
|
198
|
+
let value = this
|
|
199
|
+
.bridge
|
|
200
|
+
.wait_for(
|
|
201
|
+
ruby,
|
|
202
|
+
async move { inner.peek_front(key).await },
|
|
203
|
+
Span::current(),
|
|
204
|
+
)?
|
|
205
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
206
|
+
match value {
|
|
207
|
+
Some(value) => serialize(ruby, &value),
|
|
208
|
+
None => Ok(ruby.qnil().as_value()),
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn peek_back(ruby: &Ruby, this: &Self, key: String) -> Result<Value, Error> {
|
|
213
|
+
let inner = Arc::clone(&this.inner);
|
|
214
|
+
let value = this
|
|
215
|
+
.bridge
|
|
216
|
+
.wait_for(
|
|
217
|
+
ruby,
|
|
218
|
+
async move { inner.peek_back(key).await },
|
|
219
|
+
Span::current(),
|
|
220
|
+
)?
|
|
221
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
222
|
+
match value {
|
|
223
|
+
Some(value) => serialize(ruby, &value),
|
|
224
|
+
None => Ok(ruby.qnil().as_value()),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn scan(
|
|
229
|
+
ruby: &Ruby,
|
|
230
|
+
this: &Self,
|
|
231
|
+
key: String,
|
|
232
|
+
direction: StaticSymbol,
|
|
233
|
+
) -> Result<NativeJsonDequeScan, Error> {
|
|
234
|
+
let direction = erased_direction(parse_direction(ruby, direction)?);
|
|
235
|
+
let inner = Arc::clone(&this.inner);
|
|
236
|
+
let cursor = this
|
|
237
|
+
.bridge
|
|
238
|
+
.wait_for(
|
|
239
|
+
ruby,
|
|
240
|
+
async move { inner.stream(key, direction).await },
|
|
241
|
+
Span::current(),
|
|
242
|
+
)?
|
|
243
|
+
.map_err(|error| read_error(ruby, &error))?;
|
|
244
|
+
published_deque_scan(
|
|
245
|
+
ruby,
|
|
246
|
+
cursor,
|
|
247
|
+
this.bridge.clone(),
|
|
248
|
+
Arc::clone(&this.propagator),
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
pub(crate) fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
254
|
+
let module = ruby.get_inner(&ROOT_MOD);
|
|
255
|
+
let value = module.define_class(id!(ruby, "NativePublishedValue"), ruby.class_object())?;
|
|
256
|
+
value.define_method("get", method!(NativePublishedValue::get, 1))?;
|
|
257
|
+
|
|
258
|
+
let map = module.define_class(id!(ruby, "NativePublishedMap"), ruby.class_object())?;
|
|
259
|
+
map.define_method("get", method!(NativePublishedMap::get, 2))?;
|
|
260
|
+
map.define_method("get_many", method!(NativePublishedMap::get_many, 2))?;
|
|
261
|
+
map.define_method("contains_key", method!(NativePublishedMap::contains_key, 2))?;
|
|
262
|
+
map.define_method("scan", method!(NativePublishedMap::scan, 2))?;
|
|
263
|
+
map.define_method("keys", method!(NativePublishedMap::keys, 2))?;
|
|
264
|
+
|
|
265
|
+
let deque = module.define_class(id!(ruby, "NativePublishedDeque"), ruby.class_object())?;
|
|
266
|
+
deque.define_method("get", method!(NativePublishedDeque::get, 2))?;
|
|
267
|
+
deque.define_method("length", method!(NativePublishedDeque::length, 1))?;
|
|
268
|
+
deque.define_method("is_empty", method!(NativePublishedDeque::is_empty, 1))?;
|
|
269
|
+
deque.define_method("peek_front", method!(NativePublishedDeque::peek_front, 1))?;
|
|
270
|
+
deque.define_method("peek_back", method!(NativePublishedDeque::peek_back, 1))?;
|
|
271
|
+
deque.define_method("scan", method!(NativePublishedDeque::scan, 2))?;
|
|
272
|
+
Ok(())
|
|
273
|
+
}
|
|
@@ -14,7 +14,7 @@ use crate::bridge::{Bridge, BridgeError};
|
|
|
14
14
|
use crate::scheduler::handle::TaskHandle;
|
|
15
15
|
use crate::scheduler::processor::RubyProcessor;
|
|
16
16
|
use crate::scheduler::result::result_channel;
|
|
17
|
-
use magnus::{Error, Ruby};
|
|
17
|
+
use magnus::{Error, Ruby, Value};
|
|
18
18
|
use opentelemetry::propagation::{TextMapCompositePropagator, TextMapPropagator};
|
|
19
19
|
use prosody::propagator::new_propagator;
|
|
20
20
|
use std::collections::HashMap;
|
|
@@ -99,7 +99,7 @@ impl Scheduler {
|
|
|
99
99
|
function: F,
|
|
100
100
|
) -> Result<TaskHandle, SchedulerError>
|
|
101
101
|
where
|
|
102
|
-
F: FnOnce(&Ruby) -> Result<
|
|
102
|
+
F: FnOnce(&Ruby) -> Result<Value, Error> + Send + 'static,
|
|
103
103
|
{
|
|
104
104
|
let mut carrier: HashMap<String, String> = HashMap::with_capacity(2);
|
|
105
105
|
self.propagator
|
|
@@ -104,7 +104,7 @@ impl RubyProcessor {
|
|
|
104
104
|
function: F,
|
|
105
105
|
) -> Result<CancellationToken, Error>
|
|
106
106
|
where
|
|
107
|
-
F: FnOnce(&Ruby) -> Result<
|
|
107
|
+
F: FnOnce(&Ruby) -> Result<Value, Error> + Send + 'static,
|
|
108
108
|
{
|
|
109
109
|
if self.is_shutdown.load(Relaxed) {
|
|
110
110
|
return Err(Error::new(
|
|
@@ -122,7 +122,7 @@ impl RubyProcessor {
|
|
|
122
122
|
if let Some(function) = maybe_function.take() {
|
|
123
123
|
function(ruby)
|
|
124
124
|
} else {
|
|
125
|
-
Ok(())
|
|
125
|
+
Ok(ruby.qnil().as_value())
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
|
|
@@ -14,6 +14,7 @@ use magnus::block::Proc;
|
|
|
14
14
|
use magnus::value::ReprValue;
|
|
15
15
|
use magnus::{Error, Ruby, TryConvert, Value, kwargs};
|
|
16
16
|
use prosody::error::{ClassifyError, ErrorCategory};
|
|
17
|
+
use serde_magnus::deserialize;
|
|
17
18
|
use thiserror::Error;
|
|
18
19
|
use tokio::sync::oneshot;
|
|
19
20
|
use tracing::debug;
|
|
@@ -40,7 +41,7 @@ pub fn result_channel() -> (ResultSender, ResultReceiver) {
|
|
|
40
41
|
#[educe(Debug)]
|
|
41
42
|
pub struct ResultSender {
|
|
42
43
|
#[educe(Debug(ignore))]
|
|
43
|
-
result_tx: AtomicTake<oneshot::Sender<Result<
|
|
44
|
+
result_tx: AtomicTake<oneshot::Sender<Result<serde_json::Value, ProcessingError>>>,
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/// Receives task results in Rust from Ruby.
|
|
@@ -51,7 +52,7 @@ pub struct ResultSender {
|
|
|
51
52
|
#[educe(Debug)]
|
|
52
53
|
pub struct ResultReceiver {
|
|
53
54
|
#[educe(Debug(ignore))]
|
|
54
|
-
result_rx: oneshot::Receiver<Result<
|
|
55
|
+
result_rx: oneshot::Receiver<Result<serde_json::Value, ProcessingError>>,
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
impl ResultSender {
|
|
@@ -80,7 +81,9 @@ impl ResultSender {
|
|
|
80
81
|
};
|
|
81
82
|
|
|
82
83
|
if is_success {
|
|
83
|
-
|
|
84
|
+
let result = deserialize(ruby, result)
|
|
85
|
+
.map_err(|error| ProcessingError::Permanent(error.to_string()));
|
|
86
|
+
if result_tx.send(result).is_err() {
|
|
84
87
|
debug!("discarding result; receiver went away");
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -153,7 +156,7 @@ impl ResultReceiver {
|
|
|
153
156
|
/// Returns a `ProcessingError` if:
|
|
154
157
|
/// - The task failed (with either a permanent or transient error)
|
|
155
158
|
/// - The channel was closed unexpectedly (e.g., the Ruby VM terminated)
|
|
156
|
-
pub async fn receive(self) -> Result<
|
|
159
|
+
pub async fn receive(self) -> Result<serde_json::Value, ProcessingError> {
|
|
157
160
|
self.result_rx.await.map_err(|_| ProcessingError::Closed)?
|
|
158
161
|
}
|
|
159
162
|
}
|