itsi-server 0.2.16 → 0.2.17
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 +3 -1
- data/exe/itsi +6 -1
- data/ext/itsi_acme/Cargo.toml +1 -1
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_server/Cargo.toml +3 -1
- data/ext/itsi_server/src/lib.rs +6 -1
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -5
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
- data/ext/itsi_server/src/server/binds/listener.rs +45 -7
- data/ext/itsi_server/src/server/frame_stream.rs +142 -0
- data/ext/itsi_server/src/server/http_message_types.rs +142 -9
- data/ext/itsi_server/src/server/io_stream.rs +28 -5
- data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
- data/ext/itsi_server/src/server/mod.rs +1 -0
- data/ext/itsi_server/src/server/process_worker.rs +3 -4
- data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +158 -142
- data/ext/itsi_server/src/server/signal.rs +37 -9
- data/ext/itsi_server/src/server/thread_worker.rs +84 -69
- data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
- data/ext/itsi_server/src/services/static_file_server.rs +28 -47
- data/lib/itsi/http_request.rb +31 -39
- data/lib/itsi/http_response.rb +5 -0
- data/lib/itsi/rack_env_pool.rb +59 -0
- data/lib/itsi/server/config/dsl.rb +5 -4
- data/lib/itsi/server/config/middleware/proxy.rb +1 -1
- data/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
- data/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
- data/lib/itsi/server/config/options/include.rb +5 -2
- data/lib/itsi/server/config/options/pipeline_flush.md +16 -0
- data/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
- data/lib/itsi/server/config/options/writev.md +25 -0
- data/lib/itsi/server/config/options/writev.rb +19 -0
- data/lib/itsi/server/config.rb +21 -8
- data/lib/itsi/server/default_config/Itsi.rb +1 -4
- data/lib/itsi/server/grpc/grpc_call.rb +2 -0
- data/lib/itsi/server/grpc/grpc_interface.rb +2 -2
- data/lib/itsi/server/rack/handler/itsi.rb +3 -1
- data/lib/itsi/server/rack_interface.rb +17 -12
- data/lib/itsi/server/scheduler_interface.rb +2 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +1 -0
- data/lib/ruby_lsp/itsi/addon.rb +12 -13
- metadata +7 -1
@@ -1,13 +1,11 @@
|
|
1
1
|
use bytes::Bytes;
|
2
2
|
use http::header::CONTENT_TYPE;
|
3
3
|
use http::Response;
|
4
|
-
use http_body_util::{combinators::BoxBody, Full};
|
5
4
|
use serde::{Deserialize, Deserializer};
|
6
|
-
use std::convert::Infallible;
|
7
5
|
use std::path::PathBuf;
|
8
6
|
use tracing::warn;
|
9
7
|
|
10
|
-
use crate::server::http_message_types::{HttpResponse, ResponseFormat};
|
8
|
+
use crate::server::http_message_types::{HttpBody, HttpResponse, ResponseFormat};
|
11
9
|
use crate::services::static_file_server::ROOT_STATIC_FILE_SERVER;
|
12
10
|
mod default_responses;
|
13
11
|
|
@@ -19,7 +17,7 @@ pub enum ContentSource {
|
|
19
17
|
File(PathBuf),
|
20
18
|
#[serde(rename(deserialize = "static"))]
|
21
19
|
#[serde(skip_deserializing)]
|
22
|
-
Static(
|
20
|
+
Static(Bytes),
|
23
21
|
}
|
24
22
|
|
25
23
|
#[derive(Debug, Clone, Deserialize, Default)]
|
@@ -144,13 +142,13 @@ impl ErrorResponse {
|
|
144
142
|
code: u16,
|
145
143
|
source: &Option<ContentSource>,
|
146
144
|
accept: ResponseFormat,
|
147
|
-
) ->
|
145
|
+
) -> HttpBody {
|
148
146
|
match source {
|
149
147
|
Some(ContentSource::Inline(text)) => {
|
150
|
-
return
|
148
|
+
return HttpBody::full(Bytes::from(text.clone()));
|
151
149
|
}
|
152
150
|
Some(ContentSource::Static(text)) => {
|
153
|
-
return
|
151
|
+
return HttpBody::full(text.clone());
|
154
152
|
}
|
155
153
|
Some(ContentSource::File(path)) => {
|
156
154
|
// Convert the PathBuf to a &str (assumes valid UTF-8).
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use crate::{
|
2
|
-
server::http_message_types::{HttpRequest, HttpResponse},
|
2
|
+
server::http_message_types::{HttpBody, HttpRequest, HttpResponse},
|
3
3
|
services::itsi_http_service::HttpRequestContext,
|
4
4
|
};
|
5
5
|
|
@@ -10,7 +10,7 @@ use bytes::{Bytes, BytesMut};
|
|
10
10
|
use either::Either;
|
11
11
|
use futures::TryStreamExt;
|
12
12
|
use http::{header, HeaderValue, Response, StatusCode};
|
13
|
-
use http_body_util::
|
13
|
+
use http_body_util::BodyExt;
|
14
14
|
use hyper::body::Body;
|
15
15
|
use magnus::error::Result;
|
16
16
|
use serde::Deserialize;
|
@@ -113,7 +113,7 @@ impl MiddlewareLayer for ETag {
|
|
113
113
|
.await
|
114
114
|
{
|
115
115
|
Ok(bytes_mut) => bytes_mut.freeze(),
|
116
|
-
Err(_) => return Response::from_parts(parts,
|
116
|
+
Err(_) => return Response::from_parts(parts, HttpBody::empty()),
|
117
117
|
};
|
118
118
|
|
119
119
|
let computed_etag = match self.algorithm {
|
@@ -139,14 +139,14 @@ impl MiddlewareLayer for ETag {
|
|
139
139
|
parts.headers.insert(header::ETAG, value);
|
140
140
|
}
|
141
141
|
|
142
|
-
body =
|
142
|
+
body = HttpBody::full(full_bytes);
|
143
143
|
formatted_etag
|
144
144
|
};
|
145
145
|
|
146
146
|
if let Some(if_none_match) = context.get_if_none_match() {
|
147
147
|
if if_none_match == etag_value || if_none_match == "*" {
|
148
148
|
// Return 304 Not Modified without the body
|
149
|
-
let mut not_modified = Response::new(
|
149
|
+
let mut not_modified = Response::new(HttpBody::empty());
|
150
150
|
*not_modified.status_mut() = StatusCode::NOT_MODIFIED;
|
151
151
|
// Copy headers we want to preserve
|
152
152
|
for (name, value) in parts.headers.iter() {
|
@@ -14,7 +14,7 @@ use super::{string_rewrite::StringRewrite, ErrorResponse, FromValue, MiddlewareL
|
|
14
14
|
use crate::{
|
15
15
|
server::{
|
16
16
|
binds::bind::{Bind, BindAddress},
|
17
|
-
http_message_types::{HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
17
|
+
http_message_types::{HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat},
|
18
18
|
size_limited_incoming::MaxBodySizeReached,
|
19
19
|
},
|
20
20
|
services::itsi_http_service::HttpRequestContext,
|
@@ -24,8 +24,7 @@ use bytes::{Bytes, BytesMut};
|
|
24
24
|
use either::Either;
|
25
25
|
use futures::TryStreamExt;
|
26
26
|
use http::{HeaderMap, Method, Response, StatusCode};
|
27
|
-
use http_body_util::
|
28
|
-
use hyper::body::Frame;
|
27
|
+
use http_body_util::BodyExt;
|
29
28
|
use magnus::error::Result;
|
30
29
|
use rand::Rng;
|
31
30
|
use reqwest::{
|
@@ -373,19 +372,17 @@ impl MiddlewareLayer for Proxy {
|
|
373
372
|
for (hn, hv) in response.headers() {
|
374
373
|
builder = builder.header(hn, hv);
|
375
374
|
}
|
376
|
-
let response =
|
377
|
-
response
|
378
|
-
|
379
|
-
|
380
|
-
.map_err(|_| -> Infallible { unreachable!("We handle IO errors above") }),
|
381
|
-
)));
|
375
|
+
let response =
|
376
|
+
builder.body(HttpBody::stream(response.bytes_stream().map_err(
|
377
|
+
|_| -> Infallible { unreachable!("We handle IO errors above") },
|
378
|
+
)));
|
382
379
|
response.unwrap_or(error_response)
|
383
380
|
}
|
384
381
|
Err(e) => {
|
385
382
|
debug!(target: "middleware::proxy", "Error {:?} received", e);
|
386
383
|
if let Some(inner) = e.source() {
|
387
384
|
if inner.downcast_ref::<MaxBodySizeReached>().is_some() {
|
388
|
-
let mut max_body_response = Response::new(
|
385
|
+
let mut max_body_response = Response::new(HttpBody::empty());
|
389
386
|
*max_body_response.status_mut() = StatusCode::PAYLOAD_TOO_LARGE;
|
390
387
|
return Ok(Either::Right(max_body_response));
|
391
388
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
use super::{string_rewrite::StringRewrite, FromValue, MiddlewareLayer};
|
2
2
|
use crate::{
|
3
3
|
server::{
|
4
|
-
http_message_types::{HttpRequest, HttpResponse},
|
4
|
+
http_message_types::{HttpBody, HttpRequest, HttpResponse},
|
5
5
|
redirect_type::RedirectType,
|
6
6
|
},
|
7
7
|
services::itsi_http_service::HttpRequestContext,
|
@@ -9,7 +9,6 @@ use crate::{
|
|
9
9
|
use async_trait::async_trait;
|
10
10
|
use either::Either;
|
11
11
|
use http::Response;
|
12
|
-
use http_body_util::{combinators::BoxBody, Empty};
|
13
12
|
use magnus::error::Result;
|
14
13
|
use serde::Deserialize;
|
15
14
|
use tracing::debug;
|
@@ -39,7 +38,7 @@ impl Redirect {
|
|
39
38
|
req: &HttpRequest,
|
40
39
|
context: &mut HttpRequestContext,
|
41
40
|
) -> Result<HttpResponse> {
|
42
|
-
let mut response = Response::new(
|
41
|
+
let mut response = Response::new(HttpBody::empty());
|
43
42
|
*response.status_mut() = self.redirect_type.status_code();
|
44
43
|
let destination = self.to.rewrite_request(req, context).parse().map_err(|e| {
|
45
44
|
magnus::Error::new(
|
@@ -134,8 +134,7 @@ impl MiddlewareLayer for StaticAssets {
|
|
134
134
|
let file_server = self.file_server.get().unwrap();
|
135
135
|
let encodings: &[HeaderValue] = context
|
136
136
|
.supported_encoding_set()
|
137
|
-
.
|
138
|
-
.unwrap_or(&[] as &[HeaderValue]);
|
137
|
+
.map_or(&[], |set| set.as_slice());
|
139
138
|
let response = file_server
|
140
139
|
.serve(
|
141
140
|
&req,
|
@@ -1,15 +1,13 @@
|
|
1
1
|
use std::sync::OnceLock;
|
2
2
|
|
3
3
|
use super::{FromValue, MiddlewareLayer};
|
4
|
-
use crate::server::http_message_types::{HttpRequest, HttpResponse};
|
4
|
+
use crate::server::http_message_types::{HttpBody, HttpRequest, HttpResponse};
|
5
5
|
use crate::services::itsi_http_service::HttpRequestContext;
|
6
6
|
use async_trait::async_trait;
|
7
7
|
use bytes::Bytes;
|
8
8
|
use derive_more::Debug;
|
9
9
|
use either::Either;
|
10
10
|
use http::{HeaderMap, HeaderName, HeaderValue, Response, StatusCode};
|
11
|
-
use http_body_util::combinators::BoxBody;
|
12
|
-
use http_body_util::Full;
|
13
11
|
use itsi_error::ItsiError;
|
14
12
|
use magnus::error::Result;
|
15
13
|
use serde::Deserialize;
|
@@ -22,7 +20,7 @@ pub struct StaticResponse {
|
|
22
20
|
#[serde(skip)]
|
23
21
|
header_map: OnceLock<HeaderMap>,
|
24
22
|
#[serde(skip)]
|
25
|
-
body_bytes: OnceLock<
|
23
|
+
body_bytes: OnceLock<Bytes>,
|
26
24
|
#[serde(skip)]
|
27
25
|
status_code: OnceLock<StatusCode>,
|
28
26
|
}
|
@@ -40,7 +38,7 @@ impl MiddlewareLayer for StaticResponse {
|
|
40
38
|
.set(header_map)
|
41
39
|
.map_err(|_| ItsiError::new("Failed to set headers"))?;
|
42
40
|
self.body_bytes
|
43
|
-
.set(
|
41
|
+
.set(Bytes::from(self.body.clone()))
|
44
42
|
.map_err(|_| ItsiError::new("Failed to set body bytes"))?;
|
45
43
|
self.status_code
|
46
44
|
.set(StatusCode::from_u16(self.code).unwrap_or(StatusCode::OK))
|
@@ -53,7 +51,7 @@ impl MiddlewareLayer for StaticResponse {
|
|
53
51
|
_req: HttpRequest,
|
54
52
|
_context: &mut HttpRequestContext,
|
55
53
|
) -> Result<Either<HttpRequest, HttpResponse>> {
|
56
|
-
let mut resp = Response::new(
|
54
|
+
let mut resp = Response::new(HttpBody::full(self.body_bytes.get().unwrap().clone()));
|
57
55
|
*resp.status_mut() = *self.status_code.get().unwrap();
|
58
56
|
*resp.headers_mut() = self.header_map.get().unwrap().clone();
|
59
57
|
|
@@ -79,7 +79,7 @@ impl ProcessWorker {
|
|
79
79
|
) {
|
80
80
|
error!("Failed to set process group ID: {}", e);
|
81
81
|
}
|
82
|
-
match SingleMode::new(cluster_template.server_config.clone()) {
|
82
|
+
match SingleMode::new(cluster_template.server_config.clone(), self.worker_id) {
|
83
83
|
Ok(single_mode) => {
|
84
84
|
if cluster_template
|
85
85
|
.server_config
|
@@ -88,7 +88,7 @@ impl ProcessWorker {
|
|
88
88
|
.pin_worker_cores
|
89
89
|
{
|
90
90
|
core_affinity::set_for_current(
|
91
|
-
CORE_IDS[self.worker_id % CORE_IDS.len()],
|
91
|
+
CORE_IDS[(2 * self.worker_id) % CORE_IDS.len()],
|
92
92
|
);
|
93
93
|
}
|
94
94
|
Arc::new(single_mode).run().ok();
|
@@ -166,7 +166,7 @@ impl ProcessWorker {
|
|
166
166
|
}
|
167
167
|
|
168
168
|
pub(crate) fn boot_if_dead(&self, cluster_template: Arc<ClusterMode>) -> bool {
|
169
|
-
if !self.is_alive() {
|
169
|
+
if !self.is_alive() && self.child_pid.lock().is_some() {
|
170
170
|
if self.just_started() {
|
171
171
|
error!(
|
172
172
|
"Worker in crash loop {:?}. Refusing to restart",
|
@@ -202,7 +202,6 @@ impl ProcessWorker {
|
|
202
202
|
let child_pid = *self.child_pid.lock();
|
203
203
|
if let Some(pid) = child_pid {
|
204
204
|
if self.is_alive() {
|
205
|
-
info!("Worker still alive, sending SIGKILL {}", pid);
|
206
205
|
if let Err(e) = kill(pid, SIGKILL) {
|
207
206
|
error!("Failed to force kill process {}: {}", pid, e);
|
208
207
|
}
|
@@ -1,6 +1,5 @@
|
|
1
|
-
use std::{ops::Deref, pin::Pin, sync::Arc, time::Duration};
|
2
|
-
|
3
1
|
use hyper_util::rt::TokioIo;
|
2
|
+
use std::{ops::Deref, pin::Pin, sync::Arc, time::Duration};
|
4
3
|
use tokio::task::JoinSet;
|
5
4
|
use tracing::debug;
|
6
5
|
|
@@ -40,17 +39,21 @@ impl Acceptor {
|
|
40
39
|
let io: TokioIo<Pin<Box<IoStream>>> = TokioIo::new(Box::pin(stream));
|
41
40
|
let mut shutdown_channel = self.shutdown_receiver.clone();
|
42
41
|
let acceptor_args = self.acceptor_args.clone();
|
42
|
+
let service = ItsiHttpService {
|
43
|
+
inner: Arc::new(ItsiHttpServiceInner {
|
44
|
+
acceptor_args: acceptor_args.clone(),
|
45
|
+
addr,
|
46
|
+
}),
|
47
|
+
};
|
48
|
+
|
43
49
|
self.join_set.spawn(async move {
|
44
50
|
let executor = &acceptor_args.strategy.executor;
|
45
|
-
let
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
}),
|
52
|
-
},
|
53
|
-
));
|
51
|
+
let svc = hyper::service::service_fn(move |req| {
|
52
|
+
let service = service.clone();
|
53
|
+
async move { service.handle_request(req).await }
|
54
|
+
});
|
55
|
+
|
56
|
+
let mut serve = Box::pin(executor.serve_connection_with_upgrades(io, svc));
|
54
57
|
|
55
58
|
tokio::select! {
|
56
59
|
// Await the connection finishing naturally.
|
@@ -63,7 +66,6 @@ impl Acceptor {
|
|
63
66
|
debug!("Connection closed abruptly: {:?}", res);
|
64
67
|
}
|
65
68
|
}
|
66
|
-
serve.as_mut().graceful_shutdown();
|
67
69
|
},
|
68
70
|
// A lifecycle event triggers shutdown.
|
69
71
|
_ = shutdown_channel.changed() => {
|
@@ -81,6 +83,7 @@ impl Acceptor {
|
|
81
83
|
|
82
84
|
pub async fn join(&mut self) {
|
83
85
|
// Join all acceptor tasks with timeout
|
86
|
+
|
84
87
|
let deadline = tokio::time::Instant::now()
|
85
88
|
+ Duration::from_secs_f64(self.server_params.shutdown_timeout);
|
86
89
|
let sleep_until = tokio::time::sleep_until(deadline);
|
@@ -89,6 +92,7 @@ impl Acceptor {
|
|
89
92
|
while (self.join_set.join_next().await).is_some() {}
|
90
93
|
} => {},
|
91
94
|
_ = sleep_until => {
|
95
|
+
self.join_set.abort_all();
|
92
96
|
debug!("Shutdown timeout reached; abandoning remaining acceptor tasks.");
|
93
97
|
}
|
94
98
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
use crate::ruby_types::itsi_server::itsi_server_config::ItsiServerConfig;
|
2
|
-
use crate::server::signal::
|
2
|
+
use crate::server::signal::{subscribe_runtime_to_signals, unsubscribe_runtime};
|
3
3
|
use crate::server::{lifecycle_event::LifecycleEvent, process_worker::ProcessWorker};
|
4
4
|
use itsi_error::{ItsiError, Result};
|
5
5
|
use itsi_rb_helpers::{call_with_gvl, call_without_gvl, create_ruby_thread};
|
@@ -7,31 +7,32 @@ use itsi_tracing::{error, info, warn};
|
|
7
7
|
use magnus::Value;
|
8
8
|
use nix::{libc::exit, unistd::Pid};
|
9
9
|
|
10
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
10
11
|
use std::{
|
11
|
-
sync::
|
12
|
+
sync::Arc,
|
12
13
|
time::{Duration, Instant},
|
13
14
|
};
|
14
15
|
use tokio::{
|
15
16
|
runtime::{Builder as RuntimeBuilder, Runtime},
|
16
|
-
sync::{
|
17
|
+
sync::{watch, Mutex},
|
17
18
|
time::{self, sleep},
|
18
19
|
};
|
19
20
|
use tracing::{debug, instrument};
|
20
21
|
pub(crate) struct ClusterMode {
|
21
22
|
pub server_config: Arc<ItsiServerConfig>,
|
22
23
|
pub process_workers: parking_lot::Mutex<Vec<ProcessWorker>>,
|
23
|
-
pub lifecycle_channel: broadcast::Sender<LifecycleEvent>,
|
24
24
|
}
|
25
25
|
|
26
|
-
static WORKER_ID: AtomicUsize = AtomicUsize::new(0);
|
27
26
|
static CHILD_SIGNAL_SENDER: parking_lot::Mutex<Option<watch::Sender<()>>> =
|
28
27
|
parking_lot::Mutex::new(None);
|
29
28
|
|
29
|
+
static RELOAD_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
|
30
|
+
|
30
31
|
impl ClusterMode {
|
31
32
|
pub fn new(server_config: Arc<ItsiServerConfig>) -> Self {
|
32
33
|
let process_workers = (0..server_config.server_params.read().workers)
|
33
|
-
.map(|
|
34
|
-
worker_id:
|
34
|
+
.map(|id| ProcessWorker {
|
35
|
+
worker_id: id as usize,
|
35
36
|
..Default::default()
|
36
37
|
})
|
37
38
|
.collect();
|
@@ -39,7 +40,6 @@ impl ClusterMode {
|
|
39
40
|
Self {
|
40
41
|
server_config,
|
41
42
|
process_workers: parking_lot::Mutex::new(process_workers),
|
42
|
-
lifecycle_channel: SIGNAL_HANDLER_CHANNEL.0.clone(),
|
43
43
|
}
|
44
44
|
}
|
45
45
|
|
@@ -60,6 +60,26 @@ impl ClusterMode {
|
|
60
60
|
}
|
61
61
|
}
|
62
62
|
|
63
|
+
fn next_worker_id(&self) -> usize {
|
64
|
+
let mut ids: Vec<usize> = self
|
65
|
+
.process_workers
|
66
|
+
.lock()
|
67
|
+
.iter()
|
68
|
+
.map(|w| w.worker_id)
|
69
|
+
.collect();
|
70
|
+
self.next_available_id_in(&mut ids)
|
71
|
+
}
|
72
|
+
|
73
|
+
fn next_available_id_in(&self, list: &mut [usize]) -> usize {
|
74
|
+
list.sort_unstable();
|
75
|
+
for (expected, &id) in list.iter().enumerate() {
|
76
|
+
if id != expected {
|
77
|
+
return expected;
|
78
|
+
}
|
79
|
+
}
|
80
|
+
list.len()
|
81
|
+
}
|
82
|
+
|
63
83
|
#[allow(clippy::await_holding_lock)]
|
64
84
|
pub async fn handle_lifecycle_event(
|
65
85
|
self: Arc<Self>,
|
@@ -97,40 +117,56 @@ impl ClusterMode {
|
|
97
117
|
self.shutdown().await.ok();
|
98
118
|
self.server_config.reload_exec()?;
|
99
119
|
}
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
worker.reboot(self.clone()).await?;
|
108
|
-
next_workers.push(worker);
|
109
|
-
}
|
120
|
+
|
121
|
+
if RELOAD_IN_PROGRESS
|
122
|
+
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
123
|
+
.is_err()
|
124
|
+
{
|
125
|
+
warn!("Reload already in progress, ignoring request");
|
126
|
+
return Ok(());
|
110
127
|
}
|
111
|
-
self.
|
112
|
-
|
113
|
-
|
128
|
+
let workers_to_load = self.server_config.server_params.read().workers;
|
129
|
+
let mut next_workers = Vec::new();
|
130
|
+
let mut old_workers = self.process_workers.lock().drain(..).collect::<Vec<_>>();
|
131
|
+
|
132
|
+
// Spawn new workers
|
133
|
+
for i in 0..workers_to_load {
|
114
134
|
let worker = ProcessWorker {
|
115
|
-
worker_id:
|
135
|
+
worker_id: i as usize,
|
116
136
|
..Default::default()
|
117
137
|
};
|
118
138
|
let worker_clone = worker.clone();
|
119
139
|
let self_clone = self.clone();
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
140
|
+
|
141
|
+
call_with_gvl(|_| {
|
142
|
+
create_ruby_thread(move || {
|
143
|
+
call_without_gvl(move || match worker_clone.boot(self_clone) {
|
144
|
+
Err(err) => error!("Worker boot failed {:?}", err),
|
145
|
+
_ => {}
|
146
|
+
})
|
147
|
+
});
|
124
148
|
});
|
125
|
-
|
126
|
-
|
149
|
+
|
150
|
+
next_workers.push(worker);
|
151
|
+
|
152
|
+
if let Some(old) = old_workers.pop() {
|
153
|
+
old.graceful_shutdown(self.clone()).await;
|
154
|
+
}
|
127
155
|
}
|
156
|
+
|
157
|
+
for worker in old_workers {
|
158
|
+
worker.graceful_shutdown(self.clone()).await;
|
159
|
+
}
|
160
|
+
|
161
|
+
self.process_workers.lock().extend(next_workers);
|
162
|
+
RELOAD_IN_PROGRESS.store(false, Ordering::SeqCst);
|
163
|
+
|
128
164
|
Ok(())
|
129
165
|
}
|
130
166
|
LifecycleEvent::IncreaseWorkers => {
|
131
167
|
let mut workers = self.process_workers.lock();
|
132
168
|
let worker = ProcessWorker {
|
133
|
-
worker_id:
|
169
|
+
worker_id: self.next_worker_id(),
|
134
170
|
..Default::default()
|
135
171
|
};
|
136
172
|
let worker_clone = worker.clone();
|
@@ -171,6 +207,10 @@ impl ClusterMode {
|
|
171
207
|
unsafe { exit(0) };
|
172
208
|
}
|
173
209
|
LifecycleEvent::ChildTerminated => {
|
210
|
+
if RELOAD_IN_PROGRESS.load(Ordering::SeqCst) {
|
211
|
+
warn!("Reload already in progress, ignoring child signal");
|
212
|
+
return Ok(());
|
213
|
+
}
|
174
214
|
CHILD_SIGNAL_SENDER.lock().as_ref().inspect(|i| {
|
175
215
|
i.send(()).ok();
|
176
216
|
});
|
@@ -275,18 +315,29 @@ impl ClusterMode {
|
|
275
315
|
pub fn run(self: Arc<Self>) -> Result<()> {
|
276
316
|
info!("Starting in Cluster mode");
|
277
317
|
self.invoke_hook("before_fork");
|
318
|
+
|
278
319
|
self.process_workers
|
279
320
|
.lock()
|
280
321
|
.iter()
|
281
322
|
.try_for_each(|worker| worker.boot(Arc::clone(&self)))?;
|
282
323
|
|
324
|
+
if cfg!(target_os = "linux") {
|
325
|
+
self.server_config
|
326
|
+
.server_params
|
327
|
+
.write()
|
328
|
+
.listeners
|
329
|
+
.lock()
|
330
|
+
.drain(..);
|
331
|
+
};
|
332
|
+
|
283
333
|
let (sender, mut receiver) = watch::channel(());
|
284
334
|
*CHILD_SIGNAL_SENDER.lock() = Some(sender);
|
285
335
|
|
286
|
-
let mut lifecycle_rx = self.lifecycle_channel.subscribe();
|
287
336
|
let self_ref = self.clone();
|
288
337
|
|
289
338
|
self.build_runtime().block_on(async {
|
339
|
+
let mut lifecycle_rx = subscribe_runtime_to_signals();
|
340
|
+
|
290
341
|
let self_ref = self_ref.clone();
|
291
342
|
let memory_check_duration = if self_ref.server_config.server_params.read().worker_memory_limit.is_some(){
|
292
343
|
time::Duration::from_secs(15)
|
@@ -338,11 +389,16 @@ impl ClusterMode {
|
|
338
389
|
}
|
339
390
|
|
340
391
|
},
|
341
|
-
Err(e) =>
|
392
|
+
Err(e) => {
|
393
|
+
debug!("Lifecycle channel closed: {:?}, exiting cluster monitor loop", e);
|
394
|
+
break
|
395
|
+
},
|
342
396
|
}
|
343
397
|
}
|
344
398
|
}
|
345
399
|
});
|
400
|
+
|
401
|
+
unsubscribe_runtime();
|
346
402
|
self.server_config
|
347
403
|
.server_params
|
348
404
|
.write()
|