itsi-server 0.1.1 → 0.1.11
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.
Potentially problematic release.
This version of itsi-server might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Cargo.lock +2926 -0
- data/Cargo.toml +7 -0
- data/Rakefile +8 -1
- data/exe/itsi +119 -29
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +13 -38
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/ext/itsi_rb_helpers/src/lib.rs +112 -9
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/extconf.rb +6 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +25 -4
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +136 -8
- data/ext/itsi_server/src/request/itsi_request.rs +258 -103
- data/ext/itsi_server/src/response/itsi_response.rs +357 -0
- data/ext/itsi_server/src/response/mod.rs +1 -0
- data/ext/itsi_server/src/server/bind.rs +65 -29
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_server.rs +245 -139
- data/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/ext/itsi_server/src/server/listener.rs +237 -137
- data/ext/itsi_server/src/server/mod.rs +7 -1
- data/ext/itsi_server/src/server/process_worker.rs +203 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +260 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +276 -0
- data/ext/itsi_server/src/server/signal.rs +74 -0
- data/ext/itsi_server/src/server/thread_worker.rs +399 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_tracing/Cargo.toml +4 -0
- data/ext/itsi_tracing/src/lib.rs +53 -6
- data/lib/itsi/index.html +91 -0
- data/lib/itsi/request.rb +38 -14
- data/lib/itsi/server/Itsi.rb +127 -0
- data/lib/itsi/server/config.rb +36 -0
- data/lib/itsi/server/options_dsl.rb +401 -0
- data/lib/itsi/server/rack/handler/itsi.rb +36 -0
- data/lib/itsi/server/rack_interface.rb +75 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +6 -0
- data/lib/itsi/server/signal_trap.rb +23 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +79 -9
- data/lib/itsi/stream_io.rb +38 -0
- metadata +49 -27
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -1,23 +1,45 @@
|
|
1
1
|
use std::{os::raw::c_void, ptr::null_mut};
|
2
2
|
|
3
|
+
use magnus::{
|
4
|
+
RArray, Ruby, Thread, Value,
|
5
|
+
block::Proc,
|
6
|
+
rb_sys::FromRawValue,
|
7
|
+
value::{LazyId, ReprValue},
|
8
|
+
};
|
3
9
|
use rb_sys::{
|
4
|
-
rb_thread_call_with_gvl, rb_thread_call_without_gvl, rb_thread_create,
|
10
|
+
rb_thread_call_with_gvl, rb_thread_call_without_gvl, rb_thread_create, rb_thread_schedule,
|
11
|
+
rb_thread_wakeup,
|
5
12
|
};
|
6
13
|
|
7
|
-
|
14
|
+
mod heap_value;
|
15
|
+
pub use heap_value::{HeapVal, HeapValue};
|
16
|
+
static ID_FORK: LazyId = LazyId::new("fork");
|
17
|
+
static ID_LIST: LazyId = LazyId::new("list");
|
18
|
+
static ID_EQ: LazyId = LazyId::new("==");
|
19
|
+
static ID_ALIVE: LazyId = LazyId::new("alive?");
|
20
|
+
static ID_THREAD_VARIABLE_GET: LazyId = LazyId::new("thread_variable_get");
|
21
|
+
static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
|
22
|
+
|
23
|
+
pub fn schedule_thread() {
|
24
|
+
unsafe {
|
25
|
+
rb_thread_schedule();
|
26
|
+
};
|
27
|
+
}
|
28
|
+
pub fn create_ruby_thread<F>(f: F) -> Thread
|
8
29
|
where
|
9
|
-
F: FnOnce()
|
30
|
+
F: FnOnce() + Send + 'static,
|
10
31
|
{
|
11
32
|
extern "C" fn trampoline<F>(ptr: *mut c_void) -> u64
|
12
33
|
where
|
13
|
-
F: FnOnce()
|
34
|
+
F: FnOnce(),
|
14
35
|
{
|
15
36
|
// Reconstruct the boxed Option<F> that holds our closure.
|
16
37
|
let boxed_closure: Box<Option<F>> = unsafe { Box::from_raw(ptr as *mut Option<F>) };
|
17
38
|
// Extract the closure. (The Option should be Some; panic otherwise.)
|
18
39
|
let closure = (*boxed_closure).expect("Closure already taken");
|
19
40
|
// Call the closure and return its result.
|
20
|
-
closure()
|
41
|
+
closure();
|
42
|
+
0
|
21
43
|
}
|
22
44
|
|
23
45
|
// Box the closure (wrapped in an Option) to create a stable pointer.
|
@@ -26,7 +48,10 @@ where
|
|
26
48
|
|
27
49
|
// Call rb_thread_create with our trampoline and boxed closure.
|
28
50
|
unsafe {
|
29
|
-
|
51
|
+
let thread = rb_thread_create(Some(trampoline::<F>), ptr);
|
52
|
+
rb_thread_wakeup(thread);
|
53
|
+
rb_thread_schedule();
|
54
|
+
Thread::from_value(Value::from_raw(thread)).unwrap()
|
30
55
|
}
|
31
56
|
}
|
32
57
|
|
@@ -67,18 +92,18 @@ where
|
|
67
92
|
|
68
93
|
pub fn call_with_gvl<F, R>(f: F) -> R
|
69
94
|
where
|
70
|
-
F: FnOnce() -> R,
|
95
|
+
F: FnOnce(Ruby) -> R,
|
71
96
|
{
|
72
97
|
extern "C" fn trampoline<F, R>(arg: *mut c_void) -> *mut c_void
|
73
98
|
where
|
74
|
-
F: FnOnce() -> R,
|
99
|
+
F: FnOnce(Ruby) -> R,
|
75
100
|
{
|
76
101
|
// 1) Reconstruct the Box that holds our closure
|
77
102
|
let closure_ptr = arg as *mut Option<F>;
|
78
103
|
let closure = unsafe { (*closure_ptr).take().expect("Closure already taken") };
|
79
104
|
|
80
105
|
// 2) Call the user’s closure
|
81
|
-
let result = closure();
|
106
|
+
let result = closure(Ruby::get().unwrap());
|
82
107
|
|
83
108
|
// 3) Box up the result so we can return a pointer to it
|
84
109
|
let boxed_result = Box::new(result);
|
@@ -96,3 +121,81 @@ where
|
|
96
121
|
let result_box = unsafe { Box::from_raw(raw_result_ptr as *mut R) };
|
97
122
|
*result_box
|
98
123
|
}
|
124
|
+
|
125
|
+
pub fn fork(after_fork: Option<HeapValue<Proc>>) -> Option<i32> {
|
126
|
+
let ruby = Ruby::get().unwrap();
|
127
|
+
let fork_result = ruby
|
128
|
+
.module_kernel()
|
129
|
+
.funcall::<_, _, Option<i32>>(*ID_FORK, ())
|
130
|
+
.unwrap();
|
131
|
+
if fork_result.is_none() {
|
132
|
+
if let Some(proc) = after_fork {
|
133
|
+
call_proc_and_log_errors(proc)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
fork_result
|
137
|
+
}
|
138
|
+
|
139
|
+
pub fn call_proc_and_log_errors(proc: HeapValue<Proc>) {
|
140
|
+
if let Err(e) = proc.call::<_, Value>(()) {
|
141
|
+
if let Some(value) = e.value() {
|
142
|
+
print_rb_backtrace(value);
|
143
|
+
} else {
|
144
|
+
eprintln!("Error occurred {:?}", e);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
pub fn kill_threads<T>(threads: Vec<T>)
|
150
|
+
where
|
151
|
+
T: ReprValue,
|
152
|
+
{
|
153
|
+
for thr in &threads {
|
154
|
+
let alive: bool = thr
|
155
|
+
.funcall(*ID_ALIVE, ())
|
156
|
+
.expect("Failed to check if thread is alive");
|
157
|
+
if !alive {
|
158
|
+
eprintln!("Thread killed");
|
159
|
+
break;
|
160
|
+
}
|
161
|
+
eprintln!("Killing thread {:?}", thr.as_value());
|
162
|
+
thr.funcall::<_, _, Value>("terminate", ())
|
163
|
+
.expect("Failed to kill thread");
|
164
|
+
}
|
165
|
+
}
|
166
|
+
|
167
|
+
pub fn terminate_non_fork_safe_threads() {
|
168
|
+
let ruby = Ruby::get().unwrap();
|
169
|
+
let thread_class = ruby.class_thread();
|
170
|
+
let current: Thread = ruby.thread_current();
|
171
|
+
let threads: RArray = thread_class
|
172
|
+
.funcall(*ID_LIST, ())
|
173
|
+
.expect("Failed to list Ruby threads");
|
174
|
+
|
175
|
+
let non_fork_safe_threads = threads
|
176
|
+
.into_iter()
|
177
|
+
.filter_map(|v| {
|
178
|
+
let v_thread = Thread::from_value(v).unwrap();
|
179
|
+
let non_fork_safe = !v_thread
|
180
|
+
.funcall::<_, _, bool>(*ID_EQ, (current,))
|
181
|
+
.unwrap_or(false)
|
182
|
+
&& !v_thread
|
183
|
+
.funcall::<_, _, bool>(*ID_THREAD_VARIABLE_GET, (ruby.sym_new("fork_safe"),))
|
184
|
+
.unwrap_or(false);
|
185
|
+
if non_fork_safe { Some(v_thread) } else { None }
|
186
|
+
})
|
187
|
+
.collect::<Vec<_>>();
|
188
|
+
|
189
|
+
kill_threads(non_fork_safe_threads);
|
190
|
+
}
|
191
|
+
|
192
|
+
pub fn print_rb_backtrace(rb_err: Value) {
|
193
|
+
let backtrace = rb_err
|
194
|
+
.funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
|
195
|
+
.unwrap_or_default();
|
196
|
+
|
197
|
+
eprintln!("Ruby exception {:?}", rb_err);
|
198
|
+
for line in backtrace {
|
199
|
+
eprintln!("{}", line);
|
200
|
+
}
|
201
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
[package]
|
2
|
+
name = "itsi-scheduler"
|
3
|
+
version = "0.1.0"
|
4
|
+
edition = "2021"
|
5
|
+
authors = ["Wouter Coppieters <wc@pico.net.nz>"]
|
6
|
+
license = "MIT"
|
7
|
+
publish = false
|
8
|
+
|
9
|
+
[lib]
|
10
|
+
crate-type = ["cdylib"]
|
11
|
+
|
12
|
+
[dependencies]
|
13
|
+
magnus = { version = "0.7.1", features = ["rb-sys", "bytes"] }
|
14
|
+
derive_more = { version = "2.0.1", features = ["debug"] }
|
15
|
+
itsi_tracing = { path = "../itsi_tracing" }
|
16
|
+
itsi_rb_helpers = { path = "../itsi_rb_helpers" }
|
17
|
+
itsi_error = { path = "../itsi_error" }
|
18
|
+
itsi_instrument_entry = { path = "../itsi_instrument_entry" }
|
19
|
+
parking_lot = "0.12.3"
|
20
|
+
mio = { version = "1.0.3", features = ["os-poll", "os-ext"] }
|
21
|
+
rb-sys = "0.9.105"
|
22
|
+
bytes = "1.10.1"
|
23
|
+
nix = "0.29.0"
|
24
|
+
tracing = "0.1.41"
|
@@ -0,0 +1,56 @@
|
|
1
|
+
use std::os::fd::RawFd;
|
2
|
+
|
3
|
+
use itsi_error::{ItsiError, Result};
|
4
|
+
use mio::Interest;
|
5
|
+
use nix::libc::{fcntl, poll, pollfd, F_GETFL, F_SETFL, O_NONBLOCK};
|
6
|
+
|
7
|
+
use super::Readiness;
|
8
|
+
|
9
|
+
pub fn set_nonblocking(fd: RawFd) -> itsi_error::Result<()> {
|
10
|
+
unsafe {
|
11
|
+
let flags = fcntl(fd, F_GETFL);
|
12
|
+
if flags < 0 {
|
13
|
+
return Err(ItsiError::ArgumentError(format!(
|
14
|
+
"fcntl(F_GETFL) error for fd {}: {}",
|
15
|
+
fd,
|
16
|
+
std::io::Error::last_os_error()
|
17
|
+
)));
|
18
|
+
}
|
19
|
+
let new_flags = flags | O_NONBLOCK;
|
20
|
+
if fcntl(fd, F_SETFL, new_flags) < 0 {
|
21
|
+
return Err(ItsiError::ArgumentError(format!(
|
22
|
+
"fcntl(F_SETFL) error for fd {}: {}",
|
23
|
+
fd,
|
24
|
+
std::io::Error::last_os_error()
|
25
|
+
)));
|
26
|
+
}
|
27
|
+
}
|
28
|
+
Ok(())
|
29
|
+
}
|
30
|
+
|
31
|
+
pub fn poll_readiness(fd: RawFd, events: i16) -> Option<Readiness> {
|
32
|
+
let mut pfd = pollfd {
|
33
|
+
fd,
|
34
|
+
events,
|
35
|
+
revents: 0,
|
36
|
+
};
|
37
|
+
let ret = unsafe { poll(&mut pfd as *mut pollfd, 1, 0) };
|
38
|
+
if ret > 0 {
|
39
|
+
return Some(Readiness(pfd.revents));
|
40
|
+
}
|
41
|
+
None
|
42
|
+
}
|
43
|
+
|
44
|
+
pub fn build_interest(events: i16) -> Result<Interest> {
|
45
|
+
let mut interest_opt = None;
|
46
|
+
if events & 1 != 0 {
|
47
|
+
interest_opt = Some(Interest::READABLE);
|
48
|
+
}
|
49
|
+
if events & 4 != 0 {
|
50
|
+
interest_opt = Some(match interest_opt {
|
51
|
+
Some(i) => i | Interest::WRITABLE,
|
52
|
+
None => Interest::WRITABLE,
|
53
|
+
});
|
54
|
+
}
|
55
|
+
interest_opt.ok_or_else(|| ItsiError::ArgumentError("No valid event specified".to_owned()))
|
56
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
use derive_more::Debug;
|
2
|
+
use mio::{event::Source, unix::SourceFd, Interest, Token};
|
3
|
+
use std::os::fd::RawFd;
|
4
|
+
|
5
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
6
|
+
pub struct IoWaiter {
|
7
|
+
pub fd: RawFd,
|
8
|
+
pub readiness: i16,
|
9
|
+
pub token: Token,
|
10
|
+
}
|
11
|
+
|
12
|
+
impl IoWaiter {
|
13
|
+
pub fn new(fd: RawFd, readiness: i16, token: Token) -> Self {
|
14
|
+
Self {
|
15
|
+
fd,
|
16
|
+
readiness,
|
17
|
+
token,
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
impl Source for IoWaiter {
|
23
|
+
fn register(
|
24
|
+
&mut self,
|
25
|
+
registry: &mio::Registry,
|
26
|
+
token: Token,
|
27
|
+
interests: Interest,
|
28
|
+
) -> std::io::Result<()> {
|
29
|
+
SourceFd(&self.fd).register(registry, token, interests)
|
30
|
+
}
|
31
|
+
|
32
|
+
fn reregister(
|
33
|
+
&mut self,
|
34
|
+
registry: &mio::Registry,
|
35
|
+
token: Token,
|
36
|
+
interests: Interest,
|
37
|
+
) -> std::io::Result<()> {
|
38
|
+
SourceFd(&self.fd).reregister(registry, token, interests)
|
39
|
+
}
|
40
|
+
|
41
|
+
fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> {
|
42
|
+
SourceFd(&self.fd).deregister(registry)
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
use std::{
|
2
|
+
cmp::Ordering,
|
3
|
+
time::{Duration, Instant},
|
4
|
+
};
|
5
|
+
|
6
|
+
use mio::Token;
|
7
|
+
|
8
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
9
|
+
pub struct Timer {
|
10
|
+
pub wake_time: Instant,
|
11
|
+
pub token: Token,
|
12
|
+
}
|
13
|
+
impl PartialOrd for Timer {
|
14
|
+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
15
|
+
Some(self.cmp(other))
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
impl Ord for Timer {
|
20
|
+
fn cmp(&self, other: &Self) -> Ordering {
|
21
|
+
// Reverse the order: a timer with an earlier wake_time should be considered greater.
|
22
|
+
other
|
23
|
+
.wake_time
|
24
|
+
.cmp(&self.wake_time)
|
25
|
+
.then_with(|| other.token.cmp(&self.token))
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
impl Timer {
|
30
|
+
pub fn new(wake_in: Duration, token: Token) -> Self {
|
31
|
+
Self {
|
32
|
+
wake_time: Instant::now() + wake_in,
|
33
|
+
token,
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
pub fn is_due(&self) -> bool {
|
38
|
+
self.wake_time <= Instant::now()
|
39
|
+
}
|
40
|
+
|
41
|
+
pub(crate) fn duration(&self) -> Option<Duration> {
|
42
|
+
self.wake_time.checked_duration_since(Instant::now())
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,308 @@
|
|
1
|
+
mod io_helpers;
|
2
|
+
mod io_waiter;
|
3
|
+
mod timer;
|
4
|
+
use io_helpers::{build_interest, poll_readiness, set_nonblocking};
|
5
|
+
use io_waiter::IoWaiter;
|
6
|
+
use itsi_error::ItsiError;
|
7
|
+
use itsi_rb_helpers::{call_without_gvl, create_ruby_thread};
|
8
|
+
use magnus::{
|
9
|
+
error::Result as MagnusResult,
|
10
|
+
value::{InnerValue, Opaque, ReprValue},
|
11
|
+
Module, RClass, Ruby, Value,
|
12
|
+
};
|
13
|
+
use mio::{Events, Poll, Token, Waker};
|
14
|
+
use parking_lot::{Mutex, RwLock};
|
15
|
+
use std::{
|
16
|
+
collections::{BinaryHeap, HashMap, VecDeque},
|
17
|
+
os::fd::RawFd,
|
18
|
+
sync::Arc,
|
19
|
+
time::Duration,
|
20
|
+
};
|
21
|
+
use timer::Timer;
|
22
|
+
use tracing::{debug, info, warn};
|
23
|
+
|
24
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
25
|
+
pub(crate) struct Readiness(i16);
|
26
|
+
|
27
|
+
impl std::fmt::Debug for ItsiScheduler {
|
28
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
29
|
+
f.debug_struct("ItsiScheduler").finish()
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
const WAKE_TOKEN: Token = Token(0);
|
34
|
+
|
35
|
+
#[magnus::wrap(class = "Itsi::Scheduler", free_immediately, size)]
|
36
|
+
pub(crate) struct ItsiScheduler {
|
37
|
+
timers: Mutex<BinaryHeap<Timer>>,
|
38
|
+
io_waiters: Mutex<HashMap<Token, IoWaiter>>,
|
39
|
+
registry: Mutex<HashMap<RawFd, VecDeque<IoWaiter>>>,
|
40
|
+
poll: Mutex<Poll>,
|
41
|
+
events: Mutex<Events>,
|
42
|
+
waker: Mutex<Waker>,
|
43
|
+
}
|
44
|
+
|
45
|
+
impl Default for ItsiScheduler {
|
46
|
+
fn default() -> Self {
|
47
|
+
let poll = Poll::new().unwrap();
|
48
|
+
let waker = Waker::new(poll.registry(), WAKE_TOKEN).unwrap();
|
49
|
+
let events = Events::with_capacity(1024);
|
50
|
+
|
51
|
+
ItsiScheduler {
|
52
|
+
timers: Mutex::new(BinaryHeap::new()),
|
53
|
+
io_waiters: Mutex::new(HashMap::new()),
|
54
|
+
registry: Mutex::new(HashMap::new()),
|
55
|
+
poll: Mutex::new(poll),
|
56
|
+
events: Mutex::new(events),
|
57
|
+
waker: Mutex::new(waker),
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
impl ItsiScheduler {
|
63
|
+
pub fn initialize(&self) {}
|
64
|
+
|
65
|
+
pub fn wake(&self) -> MagnusResult<()> {
|
66
|
+
self.waker.lock().wake().map_err(|_| {
|
67
|
+
magnus::Error::new(
|
68
|
+
magnus::exception::exception(),
|
69
|
+
"Failed to wake the scheduler",
|
70
|
+
)
|
71
|
+
})?;
|
72
|
+
Ok(())
|
73
|
+
}
|
74
|
+
pub fn register_io_wait(
|
75
|
+
&self,
|
76
|
+
io_obj: i32,
|
77
|
+
events: i16,
|
78
|
+
timeout: Option<f64>,
|
79
|
+
token: usize,
|
80
|
+
) -> MagnusResult<Option<i16>> {
|
81
|
+
debug!(
|
82
|
+
"Registering IO Wait for {:?}, {:?}, {:?}, {:?}",
|
83
|
+
io_obj, events, timeout, token
|
84
|
+
);
|
85
|
+
let fd: RawFd = io_obj;
|
86
|
+
|
87
|
+
let readiness = poll_readiness(fd, events).unwrap_or(Readiness(0));
|
88
|
+
if readiness == Readiness(events) {
|
89
|
+
return Ok(Some(readiness.0));
|
90
|
+
}
|
91
|
+
|
92
|
+
set_nonblocking(fd)?;
|
93
|
+
let interest = build_interest(events)?;
|
94
|
+
let token = Token(token);
|
95
|
+
let mut waiter = IoWaiter::new(fd, events, token);
|
96
|
+
self.io_waiters.lock().insert(token, waiter.clone());
|
97
|
+
let mut binding = self.registry.lock();
|
98
|
+
let queue = binding.entry(fd).or_default();
|
99
|
+
|
100
|
+
queue.push_back(waiter.clone());
|
101
|
+
|
102
|
+
if queue.len() == 1 {
|
103
|
+
self.poll
|
104
|
+
.lock()
|
105
|
+
.registry()
|
106
|
+
.register(&mut waiter, token, interest)
|
107
|
+
.map_err(|e| ItsiError::ArgumentError(format!("register error: {}", e)))?;
|
108
|
+
}
|
109
|
+
Ok(None)
|
110
|
+
}
|
111
|
+
|
112
|
+
pub fn start_timer(&self, timeout: Option<f64>, token: usize) {
|
113
|
+
if timeout.is_some_and(|t| t >= 0.0) {
|
114
|
+
let timer_entry = Timer::new(Duration::from_secs_f64(timeout.unwrap()), Token(token));
|
115
|
+
self.timers.lock().push(timer_entry);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
pub fn has_pending_io(&self) -> bool {
|
119
|
+
!self.timers.lock().is_empty() || !self.io_waiters.lock().is_empty()
|
120
|
+
}
|
121
|
+
|
122
|
+
pub fn class_info(msg: String) {
|
123
|
+
info!(msg);
|
124
|
+
}
|
125
|
+
|
126
|
+
pub fn info(&self, msg: String) {
|
127
|
+
info!(msg);
|
128
|
+
}
|
129
|
+
|
130
|
+
pub fn warn(&self, msg: String) {
|
131
|
+
warn!(msg);
|
132
|
+
}
|
133
|
+
|
134
|
+
pub fn debug(&self, msg: String) {
|
135
|
+
debug!(msg);
|
136
|
+
}
|
137
|
+
|
138
|
+
pub fn fetch_due_events(&self) -> MagnusResult<Option<Vec<(usize, i16)>>> {
|
139
|
+
call_without_gvl(|| {
|
140
|
+
let timeout = if let Some(timer) = self.timers.lock().peek() {
|
141
|
+
timer.duration().or(Some(Duration::ZERO))
|
142
|
+
} else {
|
143
|
+
None
|
144
|
+
};
|
145
|
+
let mut due_fibers: Option<Vec<(usize, i16)>> = None;
|
146
|
+
let mut io_waiters = self.io_waiters.lock();
|
147
|
+
if !io_waiters.is_empty() || timeout.is_none() {
|
148
|
+
let mut events = self.events.lock();
|
149
|
+
{
|
150
|
+
let mut poll = self.poll.lock();
|
151
|
+
poll.poll(&mut events, timeout)
|
152
|
+
.map_err(|e| ItsiError::ArgumentError(format!("poll error: {}", e)))?;
|
153
|
+
};
|
154
|
+
|
155
|
+
for event in events.iter() {
|
156
|
+
let token = event.token();
|
157
|
+
if token == WAKE_TOKEN {
|
158
|
+
continue;
|
159
|
+
}
|
160
|
+
|
161
|
+
let waiter = io_waiters.remove(&token);
|
162
|
+
if waiter.is_none() {
|
163
|
+
continue;
|
164
|
+
}
|
165
|
+
let mut waiter = waiter.unwrap();
|
166
|
+
let mut evt_readiness = 0;
|
167
|
+
if event.is_readable() {
|
168
|
+
evt_readiness |= 1;
|
169
|
+
}
|
170
|
+
if event.is_priority() {
|
171
|
+
evt_readiness |= 2;
|
172
|
+
}
|
173
|
+
if event.is_writable() {
|
174
|
+
evt_readiness |= 4
|
175
|
+
}
|
176
|
+
self.poll
|
177
|
+
.lock()
|
178
|
+
.registry()
|
179
|
+
.deregister(&mut waiter)
|
180
|
+
.map_err(|_| {
|
181
|
+
ItsiError::ArgumentError("Failed to deregister".to_string())
|
182
|
+
})?;
|
183
|
+
|
184
|
+
due_fibers
|
185
|
+
.get_or_insert_default()
|
186
|
+
.push((waiter.token.0, evt_readiness));
|
187
|
+
|
188
|
+
let mut binding = self.registry.lock();
|
189
|
+
// Pop the current item for the current waiter off the queue
|
190
|
+
let queue = binding.get_mut(&(waiter.fd)).unwrap();
|
191
|
+
queue.pop_front();
|
192
|
+
|
193
|
+
if let Some(head) = queue.get_mut(0) {
|
194
|
+
// Register the next item in the queue if there is one.
|
195
|
+
let interest = build_interest(head.readiness)?;
|
196
|
+
self.poll
|
197
|
+
.lock()
|
198
|
+
.registry()
|
199
|
+
.register(head, head.token, interest)
|
200
|
+
.map_err(|_| {
|
201
|
+
ItsiError::ArgumentError("Failed to deregister".to_string())
|
202
|
+
})?;
|
203
|
+
} else {
|
204
|
+
// Otherwise we drop the queue altogether.
|
205
|
+
binding.remove(&waiter.fd);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
return Ok(due_fibers);
|
209
|
+
}
|
210
|
+
Ok(None)
|
211
|
+
})
|
212
|
+
}
|
213
|
+
|
214
|
+
pub fn run_blocking_in_thread<T, F>(&self, ruby: &Ruby, work: F) -> MagnusResult<Option<T>>
|
215
|
+
where
|
216
|
+
T: Send + Sync + std::fmt::Debug + 'static,
|
217
|
+
F: FnOnce() -> Option<T> + Send + 'static,
|
218
|
+
{
|
219
|
+
let result: Arc<RwLock<Option<T>>> = Arc::new(RwLock::new(None));
|
220
|
+
let result_clone = Arc::clone(&result);
|
221
|
+
|
222
|
+
let current_fiber = Opaque::from(ruby.fiber_current());
|
223
|
+
let scheduler = Opaque::from(
|
224
|
+
ruby.module_kernel()
|
225
|
+
.const_get::<_, RClass>("Fiber")
|
226
|
+
.unwrap()
|
227
|
+
.funcall::<_, _, Value>("scheduler", ())
|
228
|
+
.unwrap(),
|
229
|
+
);
|
230
|
+
|
231
|
+
create_ruby_thread(move || {
|
232
|
+
call_without_gvl(|| {
|
233
|
+
let outcome = work();
|
234
|
+
*result_clone.write() = outcome;
|
235
|
+
});
|
236
|
+
|
237
|
+
let ruby = Ruby::get().unwrap();
|
238
|
+
scheduler
|
239
|
+
.get_inner_with(&ruby)
|
240
|
+
.funcall::<_, _, Value>("unblock", (None::<String>, current_fiber))
|
241
|
+
.unwrap();
|
242
|
+
});
|
243
|
+
|
244
|
+
scheduler
|
245
|
+
.get_inner_with(ruby)
|
246
|
+
.funcall::<_, _, Value>("block", (None::<Value>, None::<u64>))?;
|
247
|
+
|
248
|
+
let result_opt = Arc::try_unwrap(result).unwrap().write().take();
|
249
|
+
Ok(result_opt)
|
250
|
+
}
|
251
|
+
|
252
|
+
pub fn address_resolve(
|
253
|
+
ruby: &Ruby,
|
254
|
+
rself: &Self,
|
255
|
+
hostname: String,
|
256
|
+
) -> MagnusResult<Option<Vec<String>>> {
|
257
|
+
let result: Option<Vec<String>> = rself.run_blocking_in_thread(ruby, move || {
|
258
|
+
use std::net::ToSocketAddrs;
|
259
|
+
let addrs_res = (hostname.as_str(), 0).to_socket_addrs();
|
260
|
+
match addrs_res {
|
261
|
+
Ok(addrs) => {
|
262
|
+
let ips: Vec<String> = addrs.map(|s| s.ip().to_string()).collect();
|
263
|
+
Some(ips)
|
264
|
+
}
|
265
|
+
Err(_) => None,
|
266
|
+
}
|
267
|
+
})?;
|
268
|
+
Ok(result)
|
269
|
+
}
|
270
|
+
|
271
|
+
pub fn fetch_due_timers(&self) -> MagnusResult<Option<Vec<usize>>> {
|
272
|
+
call_without_gvl(|| {
|
273
|
+
let mut timers = self.timers.lock();
|
274
|
+
let mut io_waiters = self.io_waiters.lock();
|
275
|
+
let mut due_fibers: Option<Vec<usize>> = None;
|
276
|
+
while let Some(timer) = timers.peek() {
|
277
|
+
if timer.is_due() {
|
278
|
+
due_fibers.get_or_insert_default().push(timer.token.0);
|
279
|
+
if let Some(waiter) = io_waiters.remove(&timer.token) {
|
280
|
+
let mut binding = self.registry.lock();
|
281
|
+
// Pop the current item for the current waiter off the queue
|
282
|
+
let queue = binding.get_mut(&waiter.fd).unwrap();
|
283
|
+
queue.pop_front();
|
284
|
+
|
285
|
+
if let Some(head) = queue.get_mut(0) {
|
286
|
+
// Register the next item in the queue if there is one.
|
287
|
+
let interest = build_interest(head.readiness)?;
|
288
|
+
self.poll
|
289
|
+
.lock()
|
290
|
+
.registry()
|
291
|
+
.register(head, head.token, interest)
|
292
|
+
.map_err(|_| {
|
293
|
+
ItsiError::ArgumentError("Failed to deregister".to_string())
|
294
|
+
})?;
|
295
|
+
} else {
|
296
|
+
// Otherwise we drop the queue altogether.
|
297
|
+
binding.remove(&waiter.fd);
|
298
|
+
}
|
299
|
+
}
|
300
|
+
timers.pop();
|
301
|
+
} else {
|
302
|
+
break;
|
303
|
+
}
|
304
|
+
}
|
305
|
+
Ok(due_fibers)
|
306
|
+
})
|
307
|
+
}
|
308
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
use itsi_scheduler::ItsiScheduler;
|
2
|
+
use magnus::{function, method, Class, Error, Module, Object, Ruby};
|
3
|
+
mod itsi_scheduler;
|
4
|
+
|
5
|
+
#[magnus::init]
|
6
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
7
|
+
itsi_tracing::init();
|
8
|
+
let module = ruby.define_module("Itsi")?;
|
9
|
+
let scheduler = module.define_class("Scheduler", ruby.class_object())?;
|
10
|
+
scheduler.define_singleton_method("info", function!(ItsiScheduler::class_info, 1))?;
|
11
|
+
scheduler.define_alloc_func::<ItsiScheduler>();
|
12
|
+
scheduler.define_method("initialize", method!(ItsiScheduler::initialize, 0))?;
|
13
|
+
scheduler.define_method("wake", method!(ItsiScheduler::wake, 0))?;
|
14
|
+
scheduler.define_method(
|
15
|
+
"register_io_wait",
|
16
|
+
method!(ItsiScheduler::register_io_wait, 4),
|
17
|
+
)?;
|
18
|
+
scheduler.define_method("info", method!(ItsiScheduler::info, 1))?;
|
19
|
+
scheduler.define_method("debug", method!(ItsiScheduler::debug, 1))?;
|
20
|
+
scheduler.define_method("warn", method!(ItsiScheduler::warn, 1))?;
|
21
|
+
scheduler.define_method("start_timer", method!(ItsiScheduler::start_timer, 2))?;
|
22
|
+
scheduler.define_method(
|
23
|
+
"address_resolve",
|
24
|
+
method!(ItsiScheduler::address_resolve, 1),
|
25
|
+
)?;
|
26
|
+
scheduler.define_method("has_pending_io?", method!(ItsiScheduler::has_pending_io, 0))?;
|
27
|
+
|
28
|
+
scheduler.define_method(
|
29
|
+
"fetch_due_timers",
|
30
|
+
method!(ItsiScheduler::fetch_due_timers, 0),
|
31
|
+
)?;
|
32
|
+
scheduler.define_method(
|
33
|
+
"fetch_due_events",
|
34
|
+
method!(ItsiScheduler::fetch_due_events, 0),
|
35
|
+
)?;
|
36
|
+
|
37
|
+
Ok(())
|
38
|
+
}
|