itsi 0.1.0 → 0.1.3
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.lock +524 -44
- data/Rakefile +22 -33
- data/crates/itsi_error/Cargo.toml +2 -0
- data/crates/itsi_error/src/from.rs +70 -0
- data/crates/itsi_error/src/lib.rs +10 -37
- data/crates/itsi_instrument_entry/Cargo.toml +15 -0
- data/crates/itsi_instrument_entry/src/lib.rs +31 -0
- data/crates/itsi_rb_helpers/Cargo.toml +2 -0
- data/crates/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/crates/itsi_rb_helpers/src/lib.rs +90 -10
- data/crates/itsi_scheduler/Cargo.toml +9 -1
- data/crates/itsi_scheduler/extconf.rb +1 -1
- data/crates/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/crates/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/crates/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/crates/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/crates/itsi_scheduler/src/lib.rs +31 -10
- data/crates/itsi_server/Cargo.toml +14 -2
- data/crates/itsi_server/extconf.rb +1 -1
- data/crates/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/crates/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/crates/itsi_server/src/body_proxy/mod.rs +2 -0
- data/crates/itsi_server/src/lib.rs +58 -7
- data/crates/itsi_server/src/request/itsi_request.rs +238 -104
- data/crates/itsi_server/src/response/itsi_response.rs +347 -0
- data/crates/itsi_server/src/response/mod.rs +1 -0
- data/crates/itsi_server/src/server/bind.rs +50 -20
- data/crates/itsi_server/src/server/bind_protocol.rs +37 -0
- data/crates/itsi_server/src/server/io_stream.rs +104 -0
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
- data/crates/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
- data/crates/itsi_server/src/server/itsi_server.rs +196 -134
- data/crates/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/crates/itsi_server/src/server/listener.rs +184 -127
- data/crates/itsi_server/src/server/mod.rs +7 -1
- data/crates/itsi_server/src/server/process_worker.rs +196 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/crates/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/crates/itsi_server/src/server/signal.rs +70 -0
- data/crates/itsi_server/src/server/thread_worker.rs +368 -0
- data/crates/itsi_server/src/server/tls.rs +42 -28
- data/crates/itsi_tracing/Cargo.toml +4 -0
- data/crates/itsi_tracing/src/lib.rs +36 -6
- data/gems/scheduler/Cargo.lock +219 -23
- data/gems/scheduler/Rakefile +7 -1
- data/gems/scheduler/ext/itsi_error/Cargo.toml +2 -0
- data/gems/scheduler/ext/itsi_error/src/from.rs +70 -0
- data/gems/scheduler/ext/itsi_error/src/lib.rs +10 -37
- data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +90 -10
- data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +9 -1
- data/gems/scheduler/ext/itsi_scheduler/extconf.rb +1 -1
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +31 -10
- data/gems/scheduler/ext/itsi_server/Cargo.toml +41 -0
- data/gems/scheduler/ext/itsi_server/extconf.rb +6 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/gems/scheduler/ext/itsi_server/src/lib.rs +103 -0
- data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +277 -0
- data/gems/scheduler/ext/itsi_server/src/request/mod.rs +1 -0
- data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +347 -0
- data/gems/scheduler/ext/itsi_server/src/response/mod.rs +1 -0
- data/gems/scheduler/ext/itsi_server/src/server/bind.rs +168 -0
- data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +13 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +5 -0
- data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +244 -0
- data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +275 -0
- data/gems/scheduler/ext/itsi_server/src/server/mod.rs +11 -0
- data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +196 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +70 -0
- data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +368 -0
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +152 -0
- data/gems/scheduler/ext/itsi_tracing/Cargo.toml +4 -0
- data/gems/scheduler/ext/itsi_tracing/src/lib.rs +36 -6
- data/gems/scheduler/itsi-scheduler.gemspec +2 -3
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +137 -1
- data/gems/scheduler/test/helpers/test_helper.rb +24 -0
- data/gems/scheduler/test/test_active_record.rb +158 -0
- data/gems/scheduler/test/test_address_resolve.rb +23 -0
- data/gems/scheduler/test/test_block_unblock.rb +229 -0
- data/gems/scheduler/test/test_file_io.rb +193 -0
- data/gems/scheduler/test/test_itsi_scheduler.rb +24 -1
- data/gems/scheduler/test/test_kernel_sleep.rb +91 -0
- data/gems/scheduler/test/test_nested_fibers.rb +286 -0
- data/gems/scheduler/test/test_network_io.rb +274 -0
- data/gems/scheduler/test/test_process_wait.rb +26 -0
- data/gems/server/exe/itsi +88 -28
- data/gems/server/ext/itsi_error/Cargo.toml +2 -0
- data/gems/server/ext/itsi_error/src/from.rs +70 -0
- data/gems/server/ext/itsi_error/src/lib.rs +10 -37
- data/gems/server/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/gems/server/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/gems/server/ext/itsi_rb_helpers/Cargo.toml +2 -0
- data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
- data/gems/server/ext/itsi_rb_helpers/src/lib.rs +90 -10
- data/gems/server/ext/itsi_scheduler/Cargo.toml +24 -0
- data/gems/server/ext/itsi_scheduler/extconf.rb +6 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/gems/server/ext/itsi_scheduler/src/lib.rs +38 -0
- data/gems/server/ext/itsi_server/Cargo.toml +14 -2
- data/gems/server/ext/itsi_server/extconf.rb +1 -1
- data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
- data/gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
- data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +2 -0
- data/gems/server/ext/itsi_server/src/lib.rs +58 -7
- data/gems/server/ext/itsi_server/src/request/itsi_request.rs +238 -104
- data/gems/server/ext/itsi_server/src/response/itsi_response.rs +347 -0
- data/gems/server/ext/itsi_server/src/response/mod.rs +1 -0
- data/gems/server/ext/itsi_server/src/server/bind.rs +50 -20
- data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/gems/server/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
- data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
- data/gems/server/ext/itsi_server/src/server/itsi_server.rs +196 -134
- data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
- data/gems/server/ext/itsi_server/src/server/listener.rs +184 -127
- data/gems/server/ext/itsi_server/src/server/mod.rs +7 -1
- data/gems/server/ext/itsi_server/src/server/process_worker.rs +196 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
- data/gems/server/ext/itsi_server/src/server/signal.rs +70 -0
- data/gems/server/ext/itsi_server/src/server/thread_worker.rs +368 -0
- data/gems/server/ext/itsi_server/src/server/tls.rs +42 -28
- data/gems/server/ext/itsi_tracing/Cargo.toml +4 -0
- data/gems/server/ext/itsi_tracing/src/lib.rs +36 -6
- data/gems/server/itsi-server.gemspec +4 -5
- data/gems/server/lib/itsi/request.rb +30 -14
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +25 -0
- data/gems/server/lib/itsi/server/scheduler_mode.rb +6 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +82 -2
- data/gems/server/lib/itsi/signals.rb +23 -0
- data/gems/server/lib/itsi/stream_io.rb +38 -0
- data/gems/server/test/test_helper.rb +2 -0
- data/gems/server/test/test_itsi_server.rb +1 -1
- data/lib/itsi/version.rb +1 -1
- data/tasks.txt +18 -0
- metadata +102 -12
- data/crates/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/crates/itsi_server/src/stream_writer/mod.rs +0 -21
- data/gems/scheduler/test/test_helper.rb +0 -6
- data/gems/server/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/gems/server/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -0,0 +1,196 @@
|
|
1
|
+
use super::serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode};
|
2
|
+
use itsi_error::{ItsiError, Result};
|
3
|
+
use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread, fork};
|
4
|
+
use itsi_tracing::error;
|
5
|
+
use nix::{
|
6
|
+
errno::Errno,
|
7
|
+
sys::{
|
8
|
+
signal::{
|
9
|
+
kill,
|
10
|
+
Signal::{SIGKILL, SIGTERM},
|
11
|
+
},
|
12
|
+
wait::{waitpid, WaitPidFlag, WaitStatus},
|
13
|
+
},
|
14
|
+
unistd::{setpgid, Pid},
|
15
|
+
};
|
16
|
+
use parking_lot::Mutex;
|
17
|
+
use std::{
|
18
|
+
process::{self, exit},
|
19
|
+
sync::Arc,
|
20
|
+
time::{Duration, Instant},
|
21
|
+
};
|
22
|
+
use sysinfo::System;
|
23
|
+
|
24
|
+
use tokio::{sync::watch, time::sleep};
|
25
|
+
use tracing::{info, instrument, warn};
|
26
|
+
|
27
|
+
#[derive(Clone, Debug)]
|
28
|
+
pub struct ProcessWorker {
|
29
|
+
pub worker_id: usize,
|
30
|
+
pub child_pid: Arc<Mutex<Option<Pid>>>,
|
31
|
+
pub started_at: Instant,
|
32
|
+
}
|
33
|
+
|
34
|
+
impl Default for ProcessWorker {
|
35
|
+
fn default() -> Self {
|
36
|
+
Self {
|
37
|
+
worker_id: 0,
|
38
|
+
child_pid: Arc::new(Mutex::new(None)),
|
39
|
+
started_at: Instant::now(),
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
impl ProcessWorker {
|
45
|
+
#[instrument(skip(self, cluster_template), fields(self.worker_id = %self.worker_id))]
|
46
|
+
pub(crate) fn boot(&self, cluster_template: Arc<ClusterMode>) -> Result<()> {
|
47
|
+
let child_pid = *self.child_pid.lock();
|
48
|
+
if let Some(pid) = child_pid {
|
49
|
+
if self.is_alive() {
|
50
|
+
if let Err(e) = kill(pid, SIGTERM) {
|
51
|
+
info!("Failed to send SIGTERM to process {}: {}", pid, e);
|
52
|
+
}
|
53
|
+
}
|
54
|
+
*self.child_pid.lock() = None;
|
55
|
+
}
|
56
|
+
|
57
|
+
match call_with_gvl(|_ruby| fork(cluster_template.server.after_fork.lock().clone())) {
|
58
|
+
Some(pid) => {
|
59
|
+
*self.child_pid.lock() = Some(Pid::from_raw(pid));
|
60
|
+
}
|
61
|
+
None => {
|
62
|
+
if let Err(e) = setpgid(
|
63
|
+
Pid::from_raw(process::id() as i32),
|
64
|
+
Pid::from_raw(process::id() as i32),
|
65
|
+
) {
|
66
|
+
error!("Failed to set process group ID: {}", e);
|
67
|
+
}
|
68
|
+
match SingleMode::new(
|
69
|
+
cluster_template.server.clone(),
|
70
|
+
cluster_template.listeners.clone(),
|
71
|
+
cluster_template.lifecycle_channel.clone(),
|
72
|
+
) {
|
73
|
+
Ok(single_mode) => {
|
74
|
+
Arc::new(single_mode).run().ok();
|
75
|
+
}
|
76
|
+
Err(e) => {
|
77
|
+
error!("Failed to boot into worker mode: {}", e);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
exit(0)
|
81
|
+
}
|
82
|
+
}
|
83
|
+
Ok(())
|
84
|
+
}
|
85
|
+
|
86
|
+
pub(crate) fn memory_usage(&self) -> Option<u64> {
|
87
|
+
if let Some(pid) = *self.child_pid.lock() {
|
88
|
+
let s = System::new_all();
|
89
|
+
if let Some(process) = s.process(sysinfo::Pid::from(pid.as_raw() as usize)) {
|
90
|
+
return Some(process.memory());
|
91
|
+
}
|
92
|
+
}
|
93
|
+
None
|
94
|
+
}
|
95
|
+
|
96
|
+
pub(crate) async fn reboot(&self, cluster_template: Arc<ClusterMode>) -> Result<bool> {
|
97
|
+
self.graceful_shutdown(cluster_template.clone()).await;
|
98
|
+
let self_clone = self.clone();
|
99
|
+
let (booted_sender, mut booted_receiver) = watch::channel(false);
|
100
|
+
create_ruby_thread(move || {
|
101
|
+
call_without_gvl(move || {
|
102
|
+
if self_clone.boot(cluster_template).is_ok() {
|
103
|
+
booted_sender.send(true).ok()
|
104
|
+
} else {
|
105
|
+
booted_sender.send(false).ok()
|
106
|
+
};
|
107
|
+
})
|
108
|
+
});
|
109
|
+
|
110
|
+
booted_receiver
|
111
|
+
.changed()
|
112
|
+
.await
|
113
|
+
.map_err(|_| ItsiError::InternalServerError("Failed to boot worker".to_owned()))?;
|
114
|
+
|
115
|
+
let guard = booted_receiver.borrow();
|
116
|
+
let result = guard.to_owned();
|
117
|
+
// Not very robust, we should check to see if the worker is actually listening before considering this successful.
|
118
|
+
sleep(Duration::from_secs(1)).await;
|
119
|
+
Ok(result)
|
120
|
+
}
|
121
|
+
|
122
|
+
pub(crate) async fn graceful_shutdown(&self, cluster_template: Arc<ClusterMode>) {
|
123
|
+
let self_clone = self.clone();
|
124
|
+
self_clone.request_shutdown();
|
125
|
+
let force_kill_time =
|
126
|
+
Instant::now() + Duration::from_secs_f64(cluster_template.server.shutdown_timeout);
|
127
|
+
while self_clone.is_alive() && force_kill_time > Instant::now() {
|
128
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
129
|
+
}
|
130
|
+
if self_clone.is_alive() {
|
131
|
+
self_clone.force_kill();
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
pub(crate) fn boot_if_dead(&self, cluster_template: Arc<ClusterMode>) -> bool {
|
136
|
+
if !self.is_alive() {
|
137
|
+
if self.just_started() {
|
138
|
+
error!(
|
139
|
+
"Worker in crash loop {:?}. Refusing to restart",
|
140
|
+
self.child_pid.lock()
|
141
|
+
);
|
142
|
+
return false;
|
143
|
+
} else {
|
144
|
+
let self_clone = self.clone();
|
145
|
+
create_ruby_thread(move || {
|
146
|
+
call_without_gvl(move || {
|
147
|
+
self_clone.boot(cluster_template).ok();
|
148
|
+
})
|
149
|
+
});
|
150
|
+
}
|
151
|
+
}
|
152
|
+
true
|
153
|
+
}
|
154
|
+
|
155
|
+
pub(crate) fn request_shutdown(&self) {
|
156
|
+
let child_pid = *self.child_pid.lock();
|
157
|
+
if let Some(pid) = child_pid {
|
158
|
+
if let Err(e) = kill(pid, SIGTERM) {
|
159
|
+
error!("Failed to send SIGTERM to process {}: {}", pid, e);
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
pub(crate) fn force_kill(&self) {
|
165
|
+
let child_pid = *self.child_pid.lock();
|
166
|
+
if let Some(pid) = child_pid {
|
167
|
+
if let Err(e) = kill(pid, SIGKILL) {
|
168
|
+
error!("Failed to force kill process {}: {}", pid, e);
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
pub(crate) fn just_started(&self) -> bool {
|
174
|
+
let now = Instant::now();
|
175
|
+
now.duration_since(self.started_at).as_millis() < 2000
|
176
|
+
}
|
177
|
+
|
178
|
+
pub(crate) fn is_alive(&self) -> bool {
|
179
|
+
let child_pid = *self.child_pid.lock();
|
180
|
+
if let Some(pid) = child_pid {
|
181
|
+
match waitpid(pid, Some(WaitPidFlag::WNOHANG)) {
|
182
|
+
Ok(WaitStatus::Exited(_, _)) | Ok(WaitStatus::Signaled(_, _, _)) => {
|
183
|
+
return false;
|
184
|
+
}
|
185
|
+
Ok(WaitStatus::StillAlive) | Ok(_) => {}
|
186
|
+
Err(_) => return false,
|
187
|
+
}
|
188
|
+
match kill(pid, None) {
|
189
|
+
Ok(_) => true,
|
190
|
+
Err(errno) => !matches!(errno, Errno::ESRCH),
|
191
|
+
}
|
192
|
+
} else {
|
193
|
+
false
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}
|
@@ -0,0 +1,254 @@
|
|
1
|
+
use crate::server::{
|
2
|
+
itsi_server::Server, lifecycle_event::LifecycleEvent, listener::Listener,
|
3
|
+
process_worker::ProcessWorker,
|
4
|
+
};
|
5
|
+
use itsi_error::{ItsiError, Result};
|
6
|
+
use itsi_rb_helpers::{call_without_gvl, create_ruby_thread};
|
7
|
+
use itsi_tracing::{error, info, warn};
|
8
|
+
use nix::{
|
9
|
+
libc::{self, exit},
|
10
|
+
unistd::Pid,
|
11
|
+
};
|
12
|
+
|
13
|
+
use std::{
|
14
|
+
sync::{atomic::AtomicUsize, Arc},
|
15
|
+
time::{Duration, Instant},
|
16
|
+
};
|
17
|
+
use tokio::{
|
18
|
+
runtime::{Builder as RuntimeBuilder, Runtime},
|
19
|
+
sync::{broadcast, watch, Mutex},
|
20
|
+
time::{self, sleep},
|
21
|
+
};
|
22
|
+
use tracing::instrument;
|
23
|
+
pub(crate) struct ClusterMode {
|
24
|
+
pub listeners: Arc<Vec<Arc<Listener>>>,
|
25
|
+
pub server: Arc<Server>,
|
26
|
+
pub process_workers: parking_lot::Mutex<Vec<ProcessWorker>>,
|
27
|
+
pub lifecycle_channel: broadcast::Sender<LifecycleEvent>,
|
28
|
+
}
|
29
|
+
|
30
|
+
static WORKER_ID: AtomicUsize = AtomicUsize::new(0);
|
31
|
+
static CHILD_SIGNAL_SENDER: parking_lot::Mutex<Option<watch::Sender<()>>> =
|
32
|
+
parking_lot::Mutex::new(None);
|
33
|
+
|
34
|
+
impl ClusterMode {
|
35
|
+
pub fn new(
|
36
|
+
server: Arc<Server>,
|
37
|
+
listeners: Arc<Vec<Arc<Listener>>>,
|
38
|
+
lifecycle_channel: broadcast::Sender<LifecycleEvent>,
|
39
|
+
) -> Self {
|
40
|
+
if let Some(f) = server.before_fork.lock().take() {
|
41
|
+
f();
|
42
|
+
}
|
43
|
+
let process_workers = (0..server.workers)
|
44
|
+
.map(|_| ProcessWorker {
|
45
|
+
worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
46
|
+
..Default::default()
|
47
|
+
})
|
48
|
+
.collect();
|
49
|
+
|
50
|
+
Self {
|
51
|
+
listeners,
|
52
|
+
server,
|
53
|
+
process_workers: parking_lot::Mutex::new(process_workers),
|
54
|
+
lifecycle_channel,
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
pub fn build_runtime(&self) -> Runtime {
|
59
|
+
let mut builder: RuntimeBuilder = RuntimeBuilder::new_current_thread();
|
60
|
+
builder
|
61
|
+
.thread_name("itsi-server-accept-loop")
|
62
|
+
.thread_stack_size(3 * 1024 * 1024)
|
63
|
+
.enable_io()
|
64
|
+
.enable_time()
|
65
|
+
.build()
|
66
|
+
.expect("Failed to build Tokio runtime")
|
67
|
+
}
|
68
|
+
|
69
|
+
#[allow(clippy::await_holding_lock)]
|
70
|
+
pub async fn handle_lifecycle_event(
|
71
|
+
self: Arc<Self>,
|
72
|
+
lifecycle_event: LifecycleEvent,
|
73
|
+
) -> Result<()> {
|
74
|
+
match lifecycle_event {
|
75
|
+
LifecycleEvent::Start => Ok(()),
|
76
|
+
LifecycleEvent::Shutdown => {
|
77
|
+
self.shutdown().await?;
|
78
|
+
Ok(())
|
79
|
+
}
|
80
|
+
LifecycleEvent::Restart => {
|
81
|
+
for worker in self.process_workers.lock().iter() {
|
82
|
+
worker.reboot(self.clone()).await?;
|
83
|
+
}
|
84
|
+
Ok(())
|
85
|
+
}
|
86
|
+
LifecycleEvent::IncreaseWorkers => {
|
87
|
+
let mut workers = self.process_workers.lock();
|
88
|
+
let worker = ProcessWorker {
|
89
|
+
worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
|
90
|
+
..Default::default()
|
91
|
+
};
|
92
|
+
let worker_clone = worker.clone();
|
93
|
+
let self_clone = self.clone();
|
94
|
+
create_ruby_thread(move || {
|
95
|
+
call_without_gvl(move || {
|
96
|
+
worker_clone.boot(self_clone).ok();
|
97
|
+
})
|
98
|
+
});
|
99
|
+
workers.push(worker);
|
100
|
+
Ok(())
|
101
|
+
}
|
102
|
+
LifecycleEvent::DecreaseWorkers => {
|
103
|
+
let worker = {
|
104
|
+
let mut workers = self.process_workers.lock();
|
105
|
+
workers.pop()
|
106
|
+
};
|
107
|
+
if let Some(dropped_worker) = worker {
|
108
|
+
dropped_worker.request_shutdown();
|
109
|
+
let force_kill_time =
|
110
|
+
Instant::now() + Duration::from_secs_f64(self.server.shutdown_timeout);
|
111
|
+
while dropped_worker.is_alive() && force_kill_time > Instant::now() {
|
112
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
113
|
+
}
|
114
|
+
if dropped_worker.is_alive() {
|
115
|
+
dropped_worker.force_kill();
|
116
|
+
}
|
117
|
+
};
|
118
|
+
Ok(())
|
119
|
+
}
|
120
|
+
LifecycleEvent::ForceShutdown => {
|
121
|
+
for worker in self.process_workers.lock().iter() {
|
122
|
+
worker.force_kill();
|
123
|
+
}
|
124
|
+
unsafe { exit(0) };
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
pub async fn shutdown(&self) -> Result<()> {
|
130
|
+
let shutdown_timeout = self.server.shutdown_timeout;
|
131
|
+
let workers = self.process_workers.lock().clone();
|
132
|
+
|
133
|
+
workers.iter().for_each(|worker| worker.request_shutdown());
|
134
|
+
|
135
|
+
let remaining_children = Arc::new(Mutex::new(workers.len()));
|
136
|
+
let monitor_handle = {
|
137
|
+
let remaining_children: Arc<Mutex<usize>> = Arc::clone(&remaining_children);
|
138
|
+
let mut workers = workers.clone();
|
139
|
+
tokio::spawn(async move {
|
140
|
+
loop {
|
141
|
+
// Check if all workers have exited
|
142
|
+
let mut remaining = remaining_children.lock().await;
|
143
|
+
workers.retain(|worker| worker.is_alive());
|
144
|
+
*remaining = workers.len();
|
145
|
+
if *remaining == 0 {
|
146
|
+
break;
|
147
|
+
}
|
148
|
+
sleep(Duration::from_millis(100)).await;
|
149
|
+
}
|
150
|
+
})
|
151
|
+
};
|
152
|
+
|
153
|
+
tokio::select! {
|
154
|
+
_ = monitor_handle => {
|
155
|
+
info!("All children exited early, exit normally")
|
156
|
+
}
|
157
|
+
_ = sleep(Duration::from_secs_f64(shutdown_timeout)) => {
|
158
|
+
warn!("Graceful shutdown timeout reached, force killing remaining children");
|
159
|
+
workers.iter().for_each(|worker| worker.force_kill());
|
160
|
+
}
|
161
|
+
}
|
162
|
+
|
163
|
+
Err(ItsiError::Break())
|
164
|
+
}
|
165
|
+
|
166
|
+
pub fn receive_signal(signal: i32) {
|
167
|
+
match signal {
|
168
|
+
libc::SIGCHLD => {
|
169
|
+
CHILD_SIGNAL_SENDER.lock().as_ref().inspect(|i| {
|
170
|
+
i.send(()).ok();
|
171
|
+
});
|
172
|
+
}
|
173
|
+
_ => {
|
174
|
+
// Handle other signals
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
|
179
|
+
pub fn stop(&self) -> Result<()> {
|
180
|
+
unsafe { libc::signal(libc::SIGCHLD, libc::SIG_DFL) };
|
181
|
+
|
182
|
+
for worker in self.process_workers.lock().iter() {
|
183
|
+
if worker.is_alive() {
|
184
|
+
worker.force_kill();
|
185
|
+
}
|
186
|
+
}
|
187
|
+
|
188
|
+
Ok(())
|
189
|
+
}
|
190
|
+
|
191
|
+
#[instrument(skip(self), fields(mode = "cluster", pid=format!("{:?}", Pid::this())))]
|
192
|
+
pub fn run(self: Arc<Self>) -> Result<()> {
|
193
|
+
info!("Starting in Cluster mode");
|
194
|
+
self.process_workers
|
195
|
+
.lock()
|
196
|
+
.iter()
|
197
|
+
.try_for_each(|worker| worker.boot(Arc::clone(&self)))?;
|
198
|
+
|
199
|
+
let (sender, mut receiver) = watch::channel(());
|
200
|
+
*CHILD_SIGNAL_SENDER.lock() = Some(sender);
|
201
|
+
|
202
|
+
unsafe { libc::signal(libc::SIGCHLD, Self::receive_signal as usize) };
|
203
|
+
|
204
|
+
let mut lifecycle_rx = self.lifecycle_channel.subscribe();
|
205
|
+
let self_ref = self.clone();
|
206
|
+
|
207
|
+
self.build_runtime().block_on(async {
|
208
|
+
let self_ref = self_ref.clone();
|
209
|
+
let mut memory_check_interval = time::interval(time::Duration::from_secs(2));
|
210
|
+
loop {
|
211
|
+
tokio::select! {
|
212
|
+
_ = receiver.changed() => {
|
213
|
+
let mut workers = self_ref.process_workers.lock();
|
214
|
+
workers.retain(|worker| {
|
215
|
+
worker.boot_if_dead(Arc::clone(&self_ref))
|
216
|
+
});
|
217
|
+
if workers.is_empty() {
|
218
|
+
warn!("No workers running. Send SIGTTIN to increase worker count");
|
219
|
+
}
|
220
|
+
}
|
221
|
+
_ = memory_check_interval.tick() => {
|
222
|
+
if let Some(memory_limit) = self_ref.server.worker_memory_limit {
|
223
|
+
let largest_worker = {
|
224
|
+
let workers = self_ref.process_workers.lock();
|
225
|
+
workers.iter().max_by(|wa, wb| wa.memory_usage().cmp(&wb.memory_usage())).cloned()
|
226
|
+
};
|
227
|
+
if let Some(largest_worker) = largest_worker {
|
228
|
+
if let Some(current_mem_usage) = largest_worker.memory_usage(){
|
229
|
+
if current_mem_usage > memory_limit {
|
230
|
+
largest_worker.reboot(self_ref.clone()).await.ok();
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
}
|
235
|
+
}
|
236
|
+
lifecycle_event = lifecycle_rx.recv() => match lifecycle_event{
|
237
|
+
Ok(lifecycle_event) => {
|
238
|
+
if let Err(e) = self_ref.clone().handle_lifecycle_event(lifecycle_event).await{
|
239
|
+
match e {
|
240
|
+
ItsiError::Break() => break,
|
241
|
+
_ => error!("Error in handle_lifecycle_event {:?}", e)
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
},
|
246
|
+
Err(e) => error!("Error receiving lifecycle_event: {:?}", e),
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}
|
250
|
+
});
|
251
|
+
|
252
|
+
Ok(())
|
253
|
+
}
|
254
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
use cluster_mode::ClusterMode;
|
2
|
+
use itsi_error::Result;
|
3
|
+
use single_mode::SingleMode;
|
4
|
+
use std::sync::Arc;
|
5
|
+
pub mod cluster_mode;
|
6
|
+
pub mod single_mode;
|
7
|
+
|
8
|
+
pub(crate) enum ServeStrategy {
|
9
|
+
Single(Arc<SingleMode>),
|
10
|
+
Cluster(Arc<ClusterMode>),
|
11
|
+
}
|
12
|
+
|
13
|
+
impl ServeStrategy {
|
14
|
+
pub fn run(&self) -> Result<()> {
|
15
|
+
match self {
|
16
|
+
ServeStrategy::Single(single_router) => single_router.clone().run(),
|
17
|
+
ServeStrategy::Cluster(cluster_router) => cluster_router.clone().run(),
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
pub(crate) fn stop(&self) -> Result<()> {
|
22
|
+
match self {
|
23
|
+
ServeStrategy::Single(single_router) => single_router.clone().stop(),
|
24
|
+
ServeStrategy::Cluster(cluster_router) => cluster_router.clone().stop(),
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|