outpunch-ruby 0.5.0 → 0.5.2
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/ext/outpunch/Cargo.toml +2 -2
- data/ext/outpunch/src/gvl.rs +32 -27
- data/ext/outpunch/src/server.rs +63 -48
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0fde3ed16a529184039dde0c3143e09b131961a13fb2adc3e165b2748260e5c
|
|
4
|
+
data.tar.gz: d65b4b5de98d43b4b53413c5e56b10a2c628032f4510057c104d1142a97e6869
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf33afb82f7144a1326cc1fcc908dc750514d9cf9f88e78561bc171d6ca9ecc00eab2ab9e9cef8281796285fd21c0db0a96cabde4dbddebf202f5a9fb30b94c6
|
|
7
|
+
data.tar.gz: b3a9a6e390b5feaa49a0de162975098cd43054a6ef0e7541e1a7aa13eaf26bb138ae309ed3e7852ee918ab9b4fa528b3a415958991d6195d558c0a0c8c3476f1
|
data/ext/outpunch/Cargo.toml
CHANGED
|
@@ -8,11 +8,11 @@ name = "outpunch"
|
|
|
8
8
|
crate-type = ["cdylib"]
|
|
9
9
|
|
|
10
10
|
[dependencies]
|
|
11
|
-
outpunch-client = { version = "0.5.
|
|
11
|
+
outpunch-client = { version = "0.5.2" }
|
|
12
12
|
magnus = { version = "0.8" }
|
|
13
13
|
rb-sys = { version = "0.9", features = ["stable-api-compiled-fallback"] }
|
|
14
14
|
tokio = { version = "1.50.0", features = ["full"] }
|
|
15
|
-
outpunch_core = { version = "0.5.
|
|
15
|
+
outpunch_core = { version = "0.5.2", package = "outpunch" }
|
|
16
16
|
|
|
17
17
|
[build-dependencies]
|
|
18
18
|
rb-sys-build = "0.9"
|
data/ext/outpunch/src/gvl.rs
CHANGED
|
@@ -30,38 +30,43 @@ unsafe extern "C" fn unblock(data: *mut c_void) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/// Run `func` with the GVL released. `func` receives a `Notify` that fires when Ruby
|
|
33
|
-
/// interrupts the thread (Thread#kill, Thread#
|
|
34
|
-
/// it stopped early because of that.
|
|
33
|
+
/// interrupts the thread (Thread#raise, Thread#kill, Timeout, Thread#wakeup, signals); it
|
|
34
|
+
/// returns `None` when it stopped early because of that.
|
|
35
35
|
///
|
|
36
|
-
///
|
|
37
|
-
|
|
36
|
+
/// `rb_thread_call_without_gvl2` does not call `func` at all while an interrupt is pending,
|
|
37
|
+
/// and the VM timer makes one pending whenever threads compete for the GVL. So when `func` did
|
|
38
|
+
/// not run, the interrupt is handled and `func` is called again.
|
|
39
|
+
///
|
|
40
|
+
/// Returns `Err` when Ruby has an exception to raise, `Ok(None)` when `func` was cancelled by an
|
|
41
|
+
/// interrupt that raised nothing (the caller decides whether to try again), `Ok(Some)` otherwise.
|
|
42
|
+
pub fn without_gvl<F, R>(ruby: &Ruby, mut func: F) -> Result<Option<R>, Error>
|
|
38
43
|
where
|
|
39
|
-
F:
|
|
44
|
+
F: FnMut(Arc<Notify>) -> Option<R>,
|
|
40
45
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
result: None,
|
|
48
|
-
};
|
|
46
|
+
loop {
|
|
47
|
+
let cancel = Arc::new(Notify::new());
|
|
48
|
+
let mut state = Call {
|
|
49
|
+
func: Some(|| func(cancel.clone())),
|
|
50
|
+
result: None,
|
|
51
|
+
};
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
unsafe {
|
|
54
|
+
rb_sys::rb_thread_call_without_gvl2(
|
|
55
|
+
Some(trampoline(&state)),
|
|
56
|
+
&mut state as *mut _ as *mut c_void,
|
|
57
|
+
Some(unblock),
|
|
58
|
+
Arc::as_ptr(&cancel) as *mut c_void,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
match state.result {
|
|
63
|
+
Some(Ok(Some(value))) => return Ok(Some(value)),
|
|
64
|
+
Some(Ok(None)) => {
|
|
65
|
+
ruby.thread_check_ints()?;
|
|
66
|
+
return Ok(None);
|
|
67
|
+
}
|
|
68
|
+
Some(Err(payload)) => panic::resume_unwind(payload),
|
|
69
|
+
None => ruby.thread_check_ints()?,
|
|
64
70
|
}
|
|
65
|
-
Some(Err(payload)) => panic::resume_unwind(payload),
|
|
66
71
|
}
|
|
67
72
|
}
|
data/ext/outpunch/src/server.rs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
|
+
use std::panic;
|
|
2
3
|
use std::sync::OnceLock;
|
|
3
4
|
use std::time::Duration;
|
|
4
5
|
|
|
@@ -9,6 +10,7 @@ use outpunch_core::server::{
|
|
|
9
10
|
};
|
|
10
11
|
use tokio::runtime::Runtime;
|
|
11
12
|
use tokio::sync::{Mutex, mpsc};
|
|
13
|
+
use tokio::task::{JoinError, JoinHandle};
|
|
12
14
|
|
|
13
15
|
use crate::gvl::without_gvl;
|
|
14
16
|
|
|
@@ -23,6 +25,35 @@ fn runtime() -> &'static Runtime {
|
|
|
23
25
|
})
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/// Wait for a task on the runtime with the GVL released. An interrupt that raises nothing
|
|
29
|
+
/// (Thread#wakeup, the VM timer) keeps waiting; one that raises returns `Err` and leaves the
|
|
30
|
+
/// task running for the caller to stop.
|
|
31
|
+
fn wait_for<T>(ruby: &Ruby, task: &mut JoinHandle<T>) -> Result<T, Error> {
|
|
32
|
+
loop {
|
|
33
|
+
let joined = without_gvl(ruby, |cancel| {
|
|
34
|
+
runtime().block_on(async {
|
|
35
|
+
tokio::select! {
|
|
36
|
+
joined = &mut *task => Some(joined),
|
|
37
|
+
_ = cancel.notified() => None,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
})?;
|
|
41
|
+
if let Some(joined) = joined {
|
|
42
|
+
return joined.map_err(|err| task_failed(ruby, err));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn task_failed(ruby: &Ruby, err: JoinError) -> Error {
|
|
48
|
+
if err.is_panic() {
|
|
49
|
+
panic::resume_unwind(err.into_panic());
|
|
50
|
+
}
|
|
51
|
+
Error::new(
|
|
52
|
+
ruby.exception_runtime_error(),
|
|
53
|
+
format!("outpunch task failed: {err}"),
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
26
57
|
fn outpunch_error(ruby: &Ruby, name: &str) -> Result<ExceptionClass, Error> {
|
|
27
58
|
ruby.class_object()
|
|
28
59
|
.const_get::<_, RModule>("Outpunch")?
|
|
@@ -61,12 +92,10 @@ impl Server {
|
|
|
61
92
|
|
|
62
93
|
fn is_connected(ruby: &Ruby, rb_self: &Self, service: String) -> Result<bool, Error> {
|
|
63
94
|
loop {
|
|
64
|
-
let
|
|
65
|
-
let service = service.clone();
|
|
66
|
-
let result = without_gvl(ruby, move |cancel| {
|
|
95
|
+
let result = without_gvl(ruby, |cancel| {
|
|
67
96
|
runtime().block_on(async {
|
|
68
97
|
tokio::select! {
|
|
69
|
-
connected =
|
|
98
|
+
connected = rb_self.inner.is_connected(&service) => Some(connected),
|
|
70
99
|
_ = cancel.notified() => None,
|
|
71
100
|
}
|
|
72
101
|
})
|
|
@@ -102,30 +131,20 @@ impl Server {
|
|
|
102
131
|
};
|
|
103
132
|
|
|
104
133
|
let server = rb_self.inner.clone();
|
|
105
|
-
let
|
|
106
|
-
|
|
107
|
-
tokio::select! {
|
|
108
|
-
result = server.try_handle_request(incoming) => Some(result),
|
|
109
|
-
_ = cancel.notified() => None,
|
|
110
|
-
}
|
|
111
|
-
})
|
|
112
|
-
})?;
|
|
134
|
+
let mut task = runtime().spawn(async move { server.try_handle_request(incoming).await });
|
|
135
|
+
let result = wait_for(ruby, &mut task).inspect_err(|_| task.abort())?;
|
|
113
136
|
|
|
114
137
|
match result {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"tunnel request interrupted",
|
|
118
|
-
)),
|
|
119
|
-
Some(Ok(response)) => response_hash(ruby, response),
|
|
120
|
-
Some(Err(TunnelError::NotConnected)) => Err(Error::new(
|
|
138
|
+
Ok(response) => response_hash(ruby, response),
|
|
139
|
+
Err(TunnelError::NotConnected) => Err(Error::new(
|
|
121
140
|
outpunch_error(ruby, "NotConnectedError")?,
|
|
122
141
|
format!("Service '{service}' not connected"),
|
|
123
142
|
)),
|
|
124
|
-
|
|
143
|
+
Err(TunnelError::Timeout) => Err(Error::new(
|
|
125
144
|
outpunch_error(ruby, "TunnelTimeoutError")?,
|
|
126
145
|
format!("Tunnel timeout for service '{service}'"),
|
|
127
146
|
)),
|
|
128
|
-
|
|
147
|
+
Err(err) => Err(Error::new(
|
|
129
148
|
outpunch_error(ruby, "TunnelError")?,
|
|
130
149
|
err.to_string(),
|
|
131
150
|
)),
|
|
@@ -150,18 +169,23 @@ pub struct Connection {
|
|
|
150
169
|
}
|
|
151
170
|
|
|
152
171
|
impl Connection {
|
|
153
|
-
/// Feed one WebSocket text message from the tunnel client into the core.
|
|
172
|
+
/// Feed one WebSocket text message from the tunnel client into the core. Blocks while the
|
|
173
|
+
/// core's queue is full.
|
|
154
174
|
fn push_message(ruby: &Ruby, rb_self: &Self, text: String) -> Result<(), Error> {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
175
|
+
// A cancelled send delivered nothing, so send again until it lands or Ruby raises.
|
|
176
|
+
loop {
|
|
177
|
+
let sent = without_gvl(ruby, |cancel| {
|
|
178
|
+
runtime().block_on(async {
|
|
179
|
+
tokio::select! {
|
|
180
|
+
_ = rb_self.inner.push_message(text.clone()) => Some(()),
|
|
181
|
+
_ = cancel.notified() => None,
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
})?;
|
|
185
|
+
if sent.is_some() {
|
|
186
|
+
return Ok(());
|
|
187
|
+
}
|
|
188
|
+
}
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
/// Block until the core has a message for the tunnel client. Returns nil once the
|
|
@@ -187,25 +211,16 @@ impl Connection {
|
|
|
187
211
|
self.inner.close();
|
|
188
212
|
}
|
|
189
213
|
|
|
190
|
-
/// Run the connection lifecycle (auth, relay, cleanup). Blocks until the
|
|
191
|
-
///
|
|
214
|
+
/// Run the connection lifecycle (auth, relay, cleanup). Blocks until the connection ends.
|
|
215
|
+
/// An exception raised into this thread (Thread#raise, Thread#kill, Timeout) closes the
|
|
216
|
+
/// connection and then propagates.
|
|
192
217
|
fn run(ruby: &Ruby, rb_self: &Self) -> Result<(), Error> {
|
|
193
218
|
let inner = rb_self.inner.clone();
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
_ = &mut run => {}
|
|
200
|
-
_ = cancel.notified() => {
|
|
201
|
-
inner.close();
|
|
202
|
-
run.await;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
Some(())
|
|
206
|
-
})
|
|
207
|
-
})?;
|
|
208
|
-
Ok(())
|
|
219
|
+
let mut task = runtime().spawn(async move { inner.run().await });
|
|
220
|
+
wait_for(ruby, &mut task).inspect_err(|_| {
|
|
221
|
+
rb_self.inner.close();
|
|
222
|
+
let _ = runtime().block_on(task);
|
|
223
|
+
})
|
|
209
224
|
}
|
|
210
225
|
}
|
|
211
226
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: outpunch-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TechyCorp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|