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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +3 -1
  3. data/exe/itsi +6 -1
  4. data/ext/itsi_acme/Cargo.toml +1 -1
  5. data/ext/itsi_scheduler/Cargo.toml +1 -1
  6. data/ext/itsi_server/Cargo.toml +3 -1
  7. data/ext/itsi_server/src/lib.rs +6 -1
  8. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +2 -0
  9. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +4 -4
  10. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +14 -13
  11. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +64 -33
  12. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +151 -152
  13. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +6 -15
  14. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +26 -5
  15. data/ext/itsi_server/src/ruby_types/itsi_server.rs +1 -1
  16. data/ext/itsi_server/src/server/binds/listener.rs +45 -7
  17. data/ext/itsi_server/src/server/frame_stream.rs +142 -0
  18. data/ext/itsi_server/src/server/http_message_types.rs +142 -9
  19. data/ext/itsi_server/src/server/io_stream.rs +28 -5
  20. data/ext/itsi_server/src/server/lifecycle_event.rs +1 -1
  21. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +2 -3
  22. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +8 -10
  23. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +2 -3
  24. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +3 -3
  25. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +54 -56
  26. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +5 -7
  27. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +5 -5
  28. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +7 -10
  29. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +2 -3
  30. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +1 -2
  31. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +4 -6
  32. data/ext/itsi_server/src/server/mod.rs +1 -0
  33. data/ext/itsi_server/src/server/process_worker.rs +3 -4
  34. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +16 -12
  35. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +87 -31
  36. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +158 -142
  37. data/ext/itsi_server/src/server/signal.rs +37 -9
  38. data/ext/itsi_server/src/server/thread_worker.rs +84 -69
  39. data/ext/itsi_server/src/services/itsi_http_service.rs +43 -43
  40. data/ext/itsi_server/src/services/static_file_server.rs +28 -47
  41. data/lib/itsi/http_request.rb +31 -39
  42. data/lib/itsi/http_response.rb +5 -0
  43. data/lib/itsi/rack_env_pool.rb +59 -0
  44. data/lib/itsi/server/config/dsl.rb +5 -4
  45. data/lib/itsi/server/config/middleware/proxy.rb +1 -1
  46. data/lib/itsi/server/config/middleware/rackup_file.rb +2 -2
  47. data/lib/itsi/server/config/options/auto_reload_config.rb +6 -2
  48. data/lib/itsi/server/config/options/include.rb +5 -2
  49. data/lib/itsi/server/config/options/pipeline_flush.md +16 -0
  50. data/lib/itsi/server/config/options/pipeline_flush.rb +19 -0
  51. data/lib/itsi/server/config/options/writev.md +25 -0
  52. data/lib/itsi/server/config/options/writev.rb +19 -0
  53. data/lib/itsi/server/config.rb +21 -8
  54. data/lib/itsi/server/default_config/Itsi.rb +1 -4
  55. data/lib/itsi/server/grpc/grpc_call.rb +2 -0
  56. data/lib/itsi/server/grpc/grpc_interface.rb +2 -2
  57. data/lib/itsi/server/rack/handler/itsi.rb +3 -1
  58. data/lib/itsi/server/rack_interface.rb +17 -12
  59. data/lib/itsi/server/scheduler_interface.rb +2 -0
  60. data/lib/itsi/server/version.rb +1 -1
  61. data/lib/itsi/server.rb +1 -0
  62. data/lib/ruby_lsp/itsi/addon.rb +12 -13
  63. 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(Full<Bytes>),
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
- ) -> BoxBody<Bytes, Infallible> {
145
+ ) -> HttpBody {
148
146
  match source {
149
147
  Some(ContentSource::Inline(text)) => {
150
- return BoxBody::new(Full::new(Bytes::from(text.clone())));
148
+ return HttpBody::full(Bytes::from(text.clone()));
151
149
  }
152
150
  Some(ContentSource::Static(text)) => {
153
- return BoxBody::new(text.clone());
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::{combinators::BoxBody, BodyExt, Empty, Full};
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, BoxBody::new(Empty::new())),
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 = Full::new(full_bytes).boxed();
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(BoxBody::new(Empty::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::{combinators::BoxBody, BodyExt, Empty, StreamBody};
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 = builder.body(BoxBody::new(StreamBody::new(
377
- response
378
- .bytes_stream()
379
- .map_ok(Frame::data)
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(BoxBody::new(Empty::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(BoxBody::new(Empty::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
- .map(Vec::as_slice)
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<Full<Bytes>>,
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(Full::new(Bytes::from(self.body.clone())))
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(BoxBody::new(self.body_bytes.get().unwrap().clone()));
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
 
@@ -1,5 +1,6 @@
1
1
  pub mod binds;
2
2
  pub mod byte_frame;
3
+ pub mod frame_stream;
3
4
  pub mod http_message_types;
4
5
  pub mod io_stream;
5
6
  pub mod lifecycle_event;
@@ -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 mut serve = Box::pin(executor.serve_connection_with_upgrades(
46
- io,
47
- ItsiHttpService {
48
- inner: Arc::new(ItsiHttpServiceInner {
49
- acceptor_args: acceptor_args.clone(),
50
- addr: addr.to_string(),
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::SIGNAL_HANDLER_CHANNEL;
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::{atomic::AtomicUsize, Arc},
12
+ sync::Arc,
12
13
  time::{Duration, Instant},
13
14
  };
14
15
  use tokio::{
15
16
  runtime::{Builder as RuntimeBuilder, Runtime},
16
- sync::{broadcast, watch, Mutex},
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(|_| ProcessWorker {
34
- worker_id: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
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
- let mut workers_to_load = self.server_config.server_params.read().workers;
101
- let mut next_workers = Vec::new();
102
- for worker in self.process_workers.lock().drain(..) {
103
- if workers_to_load == 0 {
104
- worker.graceful_shutdown(self.clone()).await
105
- } else {
106
- workers_to_load -= 1;
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.process_workers.lock().extend(next_workers);
112
- while workers_to_load > 0 {
113
- let mut workers = self.process_workers.lock();
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: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
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
- create_ruby_thread(move || {
121
- call_without_gvl(move || {
122
- worker_clone.boot(self_clone).ok();
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
- workers.push(worker);
126
- workers_to_load -= 1
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: WORKER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
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) => error!("Error receiving lifecycle_event: {:?}", 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()