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,4 +1,9 @@
1
- use bytes::{Buf, Bytes, BytesMut};
1
+ use crate::server::{
2
+ frame_stream::{BufferedStream, FrameStream},
3
+ http_message_types::{HttpBody, HttpResponse},
4
+ serve_strategy::single_mode::RunningPhase,
5
+ };
6
+ use bytes::{Buf, Bytes};
2
7
  use derive_more::Debug;
3
8
  use futures::stream::{unfold, StreamExt};
4
9
  use http::{
@@ -6,10 +11,11 @@ use http::{
6
11
  request::Parts,
7
12
  HeaderMap, HeaderName, HeaderValue, Request, Response, StatusCode,
8
13
  };
9
- use http_body_util::{combinators::BoxBody, Empty, Full, StreamBody};
10
- use hyper::{body::Frame, upgrade::Upgraded};
14
+ use http_body_util::Empty;
15
+ use hyper::upgrade::Upgraded;
11
16
  use hyper_util::rt::TokioIo;
12
17
  use itsi_error::Result;
18
+ use itsi_rb_helpers::call_without_gvl;
13
19
  use itsi_tracing::error;
14
20
  use magnus::error::Result as MagnusResult;
15
21
  use memchr::{memchr, memchr_iter};
@@ -17,105 +23,66 @@ use parking_lot::RwLock;
17
23
  use std::{
18
24
  collections::HashMap,
19
25
  io,
26
+ ops::Deref,
20
27
  os::{fd::FromRawFd, unix::net::UnixStream},
21
28
  str::FromStr,
22
29
  sync::Arc,
30
+ time::Duration,
23
31
  };
24
32
  use tokio::{
25
33
  io::AsyncReadExt,
26
34
  net::UnixStream as TokioUnixStream,
27
- sync::{
28
- mpsc::{self},
29
- watch,
30
- },
35
+ sync::{mpsc::Sender, oneshot::Sender as OneshotSender, watch},
31
36
  };
32
- use tokio_stream::wrappers::ReceiverStream;
33
37
  use tokio_util::io::ReaderStream;
34
- use tracing::warn;
35
-
36
- use crate::server::{
37
- byte_frame::ByteFrame, http_message_types::HttpResponse,
38
- serve_strategy::single_mode::RunningPhase,
39
- };
38
+ use tracing::{info, warn};
40
39
 
41
40
  #[magnus::wrap(class = "Itsi::HttpResponse", free_immediately, size)]
42
41
  #[derive(Debug, Clone)]
43
42
  pub struct ItsiHttpResponse {
44
- pub data: Arc<ResponseData>,
43
+ pub inner: Arc<ResponseInner>,
44
+ }
45
+
46
+ impl Deref for ItsiHttpResponse {
47
+ type Target = Arc<ResponseInner>;
48
+
49
+ fn deref(&self) -> &Self::Target {
50
+ &self.inner
51
+ }
45
52
  }
46
53
 
47
54
  #[derive(Debug)]
48
- pub struct ResponseData {
55
+ pub struct ResponseInner {
56
+ pub frame_writer: RwLock<Option<Sender<Bytes>>>,
49
57
  pub response: RwLock<Option<HttpResponse>>,
50
- pub response_writer: RwLock<Option<mpsc::Sender<ByteFrame>>>,
51
- pub response_buffer: RwLock<BytesMut>,
52
58
  pub hijacked_socket: RwLock<Option<UnixStream>>,
53
- pub parts: Parts,
59
+ pub response_sender: RwLock<Option<OneshotSender<ResponseFrame>>>,
60
+ pub shutdown_rx: watch::Receiver<RunningPhase>,
61
+ pub parts: Arc<Parts>,
62
+ }
63
+
64
+ #[derive(Debug)]
65
+ pub enum ResponseFrame {
66
+ HttpResponse(HttpResponse),
67
+ HijackedResponse(ItsiHttpResponse),
54
68
  }
55
69
 
56
70
  impl ItsiHttpResponse {
57
- pub async fn build(
58
- &self,
59
- first_frame: ByteFrame,
60
- receiver: mpsc::Receiver<ByteFrame>,
71
+ pub fn new(
72
+ parts: Arc<Parts>,
73
+ response_sender: OneshotSender<ResponseFrame>,
61
74
  shutdown_rx: watch::Receiver<RunningPhase>,
62
- ) -> HttpResponse {
63
- if self.is_hijacked() {
64
- return match self.process_hijacked_response().await {
65
- Ok(result) => result,
66
- Err(e) => {
67
- error!("Error processing hijacked response: {}", e);
68
- Response::new(BoxBody::new(Empty::new()))
69
- }
70
- };
75
+ ) -> Self {
76
+ Self {
77
+ inner: Arc::new(ResponseInner {
78
+ parts,
79
+ shutdown_rx,
80
+ response_sender: RwLock::new(Some(response_sender)),
81
+ frame_writer: RwLock::new(None),
82
+ response: RwLock::new(Some(Response::new(HttpBody::empty()))),
83
+ hijacked_socket: RwLock::new(None),
84
+ }),
71
85
  }
72
- let mut response = self.data.response.write().take().unwrap();
73
- *response.body_mut() = if matches!(first_frame, ByteFrame::Empty) {
74
- BoxBody::new(Empty::new())
75
- } else if matches!(first_frame, ByteFrame::End(_)) {
76
- BoxBody::new(Full::new(first_frame.into()))
77
- } else {
78
- let initial_frame = tokio_stream::once(Ok(Frame::data(Bytes::from(first_frame))));
79
- let frame_stream = unfold(
80
- (ReceiverStream::new(receiver), shutdown_rx),
81
- |(mut receiver, mut shutdown_rx)| async move {
82
- if let RunningPhase::ShutdownPending = *shutdown_rx.borrow() {
83
- return None;
84
- }
85
- loop {
86
- tokio::select! {
87
- maybe_bytes = receiver.next() => {
88
- match maybe_bytes {
89
- Some(ByteFrame::Data(bytes)) | Some(ByteFrame::End(bytes)) => {
90
- return Some((Ok(Frame::data(bytes)), (receiver, shutdown_rx)));
91
- }
92
- _ => {
93
- return None;
94
- }
95
- }
96
- },
97
- _ = shutdown_rx.changed() => {
98
- match *shutdown_rx.borrow() {
99
- RunningPhase::ShutdownPending => {
100
- warn!("Disconnecting streaming client.");
101
- return None;
102
- },
103
- _ => continue,
104
- }
105
- }
106
- }
107
- }
108
- },
109
- );
110
-
111
- let combined_stream = initial_frame.chain(frame_stream);
112
- BoxBody::new(StreamBody::new(combined_stream))
113
- };
114
- response
115
- }
116
-
117
- pub fn close(&self) {
118
- self.data.response_writer.write().take();
119
86
  }
120
87
 
121
88
  async fn two_way_bridge(upgraded: Upgraded, local: TokioUnixStream) -> io::Result<()> {
@@ -163,8 +130,7 @@ impl ItsiHttpResponse {
163
130
  &self,
164
131
  ) -> Result<(HeaderMap, StatusCode, bool, TokioUnixStream)> {
165
132
  let hijacked_socket =
166
- self.data
167
- .hijacked_socket
133
+ self.hijacked_socket
168
134
  .write()
169
135
  .take()
170
136
  .ok_or(itsi_error::ItsiError::InvalidInput(
@@ -191,9 +157,9 @@ impl ItsiHttpResponse {
191
157
  pub async fn process_hijacked_response(&self) -> Result<HttpResponse> {
192
158
  let (headers, status, requires_upgrade, reader) = self.read_hijacked_headers().await?;
193
159
  let mut response = if requires_upgrade {
194
- let parts = self.data.parts.clone();
160
+ let parts = self.parts.clone();
195
161
  tokio::spawn(async move {
196
- let mut req = Request::from_parts(parts, Empty::<Bytes>::new());
162
+ let mut req = Request::from_parts((*parts).clone(), Empty::<Bytes>::new());
197
163
  match hyper::upgrade::on(&mut req).await {
198
164
  Ok(upgraded) => {
199
165
  Self::two_way_bridge(upgraded, reader)
@@ -203,14 +169,14 @@ impl ItsiHttpResponse {
203
169
  Err(e) => eprintln!("upgrade error: {:?}", e),
204
170
  }
205
171
  });
206
- Response::new(BoxBody::new(Empty::new()))
172
+ Response::new(HttpBody::empty())
207
173
  } else {
208
174
  let stream = ReaderStream::new(reader);
209
175
  let boxed_body = if headers
210
176
  .get(TRANSFER_ENCODING)
211
177
  .is_some_and(|h| h == "chunked")
212
178
  {
213
- BoxBody::new(StreamBody::new(unfold(
179
+ HttpBody::stream(unfold(
214
180
  (stream, Vec::new()),
215
181
  |(mut stream, mut buf)| async move {
216
182
  loop {
@@ -231,7 +197,7 @@ impl ItsiHttpResponse {
231
197
  if buf.starts_with(b"\r\n") {
232
198
  buf.drain(..2);
233
199
  }
234
- return Some((Ok(Frame::data(Bytes::from(data))), (stream, buf)));
200
+ return Some((Ok(Bytes::from(data)), (stream, buf)));
235
201
  }
236
202
  match stream.next().await {
237
203
  Some(Ok(chunk)) => buf.extend_from_slice(&chunk),
@@ -239,15 +205,11 @@ impl ItsiHttpResponse {
239
205
  }
240
206
  }
241
207
  },
242
- )))
208
+ ))
243
209
  } else {
244
- BoxBody::new(StreamBody::new(stream.map(
245
- |result: std::result::Result<Bytes, io::Error>| {
246
- result
247
- .map(Frame::data)
248
- .map_err(|e| unreachable!("unexpected io error: {:?}", e))
249
- },
250
- )))
210
+ HttpBody::stream(stream.map(|result: std::result::Result<Bytes, io::Error>| {
211
+ result.map_err(|e| unreachable!("unexpected io error: {:?}", e))
212
+ }))
251
213
  };
252
214
  Response::new(boxed_body)
253
215
  };
@@ -259,59 +221,97 @@ impl ItsiHttpResponse {
259
221
 
260
222
  pub fn internal_server_error(&self, message: String) {
261
223
  error!(message);
262
- self.data.response_writer.write().take();
263
- if let Some(ref mut response) = *self.data.response.write() {
224
+ self.close_write().ok();
225
+ if let Some(mut response) = self.response.write().take() {
264
226
  *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
227
+ if let Some(sender) = self.response_sender.write().take() {
228
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
229
+ }
265
230
  }
266
231
  }
267
232
 
268
- pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
269
- self.send_frame_into(ByteFrame::Data(frame), &self.data.response_writer)
233
+ pub fn service_unavailable(&self) {
234
+ self.close_write().ok();
235
+ if let Some(mut response) = self.response.write().take() {
236
+ *response.status_mut() = StatusCode::SERVICE_UNAVAILABLE;
237
+ if let Some(sender) = self.response_sender.write().take() {
238
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
239
+ }
240
+ }
270
241
  }
271
242
 
272
- pub fn recv_frame(&self) {
273
- // not implemented
243
+ pub fn send_frame(&self, frame: Bytes) -> MagnusResult<()> {
244
+ {
245
+ if self.frame_writer.read().is_none() && self.response.read().is_some() {
246
+ if let Some(mut response) = self.response.write().take() {
247
+ let (writer, reader) = tokio::sync::mpsc::channel::<Bytes>(256);
248
+ let shutdown_rx = self.shutdown_rx.clone();
249
+ let frame_stream = FrameStream::new(reader, shutdown_rx.clone());
250
+
251
+ let buffered =
252
+ BufferedStream::new(frame_stream, 32 * 1024, Duration::from_millis(10));
253
+ *response.body_mut() = HttpBody::stream(buffered);
254
+ self.frame_writer.write().replace(writer);
255
+ if let Some(sender) = self.response_sender.write().take() {
256
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
257
+ }
258
+ } else {
259
+ info!("No response!");
260
+ }
261
+ }
262
+ }
263
+ if let Some(frame_writer) = self.frame_writer.read().as_ref() {
264
+ call_without_gvl(|| frame_writer.blocking_send(frame))
265
+ .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?;
266
+ }
267
+ Ok(())
274
268
  }
275
269
 
276
- pub fn flush(&self) {
277
- // no-op
270
+ pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
271
+ if self.frame_writer.read().is_some() {
272
+ self.send_frame(frame)?;
273
+ self.close()?;
274
+ return Ok(());
275
+ }
276
+ if let Some(mut response) = self.response.write().take() {
277
+ if frame.is_empty() {
278
+ *response.body_mut() = HttpBody::empty();
279
+ } else {
280
+ *response.body_mut() = HttpBody::full(frame);
281
+ }
282
+ if let Some(sender) = self.response_sender.write().take() {
283
+ sender.send(ResponseFrame::HttpResponse(response)).ok();
284
+ }
285
+ }
286
+
287
+ Ok(())
278
288
  }
279
289
 
280
- pub fn is_closed(&self) -> bool {
281
- self.data.response_writer.write().is_none()
290
+ pub fn close_write(&self) -> MagnusResult<bool> {
291
+ self.frame_writer.write().take();
292
+ Ok(true)
282
293
  }
283
294
 
284
- pub fn send_and_close(&self, frame: Bytes) -> MagnusResult<()> {
285
- let result = self.send_frame_into(ByteFrame::End(frame), &self.data.response_writer);
286
- self.data.response_writer.write().take();
287
- result
295
+ pub fn recv_frame(&self) {
296
+ // not implemented
288
297
  }
289
298
 
290
- pub fn send_frame_into(
291
- &self,
292
- frame: ByteFrame,
293
- writer: &RwLock<Option<mpsc::Sender<ByteFrame>>>,
294
- ) -> MagnusResult<()> {
295
- if let Some(writer) = writer.write().as_ref() {
296
- return Ok(writer
297
- .blocking_send(frame)
298
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?);
299
- }
300
- Ok(())
299
+ pub fn is_closed(&self) -> bool {
300
+ self.response.read().is_none() && self.frame_writer.read().is_none()
301
301
  }
302
302
 
303
303
  pub fn is_hijacked(&self) -> bool {
304
- self.data.hijacked_socket.read().is_some()
304
+ self.hijacked_socket.read().is_some()
305
305
  }
306
306
 
307
- pub fn close_write(&self) -> MagnusResult<bool> {
308
- self.data.response_writer.write().take();
309
- Ok(true)
307
+ pub fn close(&self) -> MagnusResult<()> {
308
+ self.close_write()?;
309
+ self.close_read()?;
310
+ Ok(())
310
311
  }
311
312
 
312
313
  pub fn accept_str(&self) -> &str {
313
- self.data
314
- .parts
314
+ self.parts
315
315
  .headers
316
316
  .get(ACCEPT)
317
317
  .and_then(|hv| hv.to_str().ok()) // handle invalid utf-8
@@ -330,25 +330,9 @@ impl ItsiHttpResponse {
330
330
  Ok(true)
331
331
  }
332
332
 
333
- pub fn new(parts: Parts, response_writer: mpsc::Sender<ByteFrame>) -> Self {
334
- Self {
335
- data: Arc::new(ResponseData {
336
- response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
337
- response_writer: RwLock::new(Some(response_writer)),
338
- response_buffer: RwLock::new(BytesMut::new()),
339
- hijacked_socket: RwLock::new(None),
340
- parts,
341
- }),
342
- }
343
- }
344
-
345
- pub fn add_header(&self, name: Bytes, value: Bytes) -> MagnusResult<()> {
346
- let header_name: HeaderName = HeaderName::from_bytes(&name).map_err(|e| {
347
- itsi_error::ItsiError::InvalidInput(format!("Invalid header name {:?}: {:?}", name, e))
348
- })?;
349
- if let Some(ref mut resp) = *self.data.response.write() {
350
- let headers_mut = resp.headers_mut();
351
- self.insert_header(headers_mut, &header_name, value);
333
+ pub fn reserve_headers(&self, header_count: usize) -> MagnusResult<()> {
334
+ if let Some(ref mut resp) = *self.response.write() {
335
+ resp.headers_mut().try_reserve(header_count).ok();
352
336
  }
353
337
  Ok(())
354
338
  }
@@ -394,8 +378,22 @@ impl ItsiHttpResponse {
394
378
  }
395
379
  }
396
380
 
381
+ pub fn add_header(&self, header_name: Bytes, value: Bytes) -> MagnusResult<()> {
382
+ if let Some(ref mut resp) = *self.response.write() {
383
+ let headers_mut = resp.headers_mut();
384
+ let header_name = HeaderName::from_bytes(&header_name).map_err(|e| {
385
+ itsi_error::ItsiError::InvalidInput(format!(
386
+ "Invalid header name {:?}: {:?}",
387
+ header_name, e
388
+ ))
389
+ })?;
390
+ self.insert_header(headers_mut, &header_name, value);
391
+ }
392
+ Ok(())
393
+ }
394
+
397
395
  pub fn add_headers(&self, headers: HashMap<Bytes, Vec<Bytes>>) -> MagnusResult<()> {
398
- if let Some(ref mut resp) = *self.data.response.write() {
396
+ if let Some(ref mut resp) = *self.response.write() {
399
397
  let headers_mut = resp.headers_mut();
400
398
  for (name, values) in headers {
401
399
  let header_name = HeaderName::from_bytes(&name).map_err(|e| {
@@ -414,7 +412,7 @@ impl ItsiHttpResponse {
414
412
  }
415
413
 
416
414
  pub fn set_status(&self, status: u16) -> MagnusResult<()> {
417
- if let Some(ref mut resp) = *self.data.response.write() {
415
+ if let Some(ref mut resp) = *self.response.write() {
418
416
  *resp.status_mut() = StatusCode::from_u16(status).map_err(|e| {
419
417
  itsi_error::ItsiError::InvalidInput(format!(
420
418
  "Invalid status code {:?}: {:?}",
@@ -428,13 +426,14 @@ impl ItsiHttpResponse {
428
426
  pub fn hijack(&self, fd: i32) -> MagnusResult<()> {
429
427
  let stream = unsafe { UnixStream::from_raw_fd(fd) };
430
428
 
431
- *self.data.hijacked_socket.write() = Some(stream);
432
- if let Some(writer) = self.data.response_writer.write().as_ref() {
433
- writer
434
- .blocking_send(ByteFrame::Empty)
435
- .map_err(|_| itsi_error::ItsiError::ClientConnectionClosed)?
429
+ *self.hijacked_socket.write() = Some(stream);
430
+ if let Some(sender) = self.response_sender.write().take() {
431
+ sender
432
+ .send(ResponseFrame::HijackedResponse(self.clone()))
433
+ .ok();
436
434
  }
437
- self.close();
435
+
436
+ self.close()?;
438
437
  Ok(())
439
438
  }
440
439
  }
@@ -32,18 +32,8 @@ struct PatternGroup {
32
32
  /// component that contains a wildcard character.
33
33
  fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf {
34
34
  if !(pattern.contains("*") || pattern.contains("?") || pattern.contains('[')) {
35
- if let Ok(metadata) = fs::metadata(pattern) {
36
- if metadata.is_dir() {
37
- return fs::canonicalize(pattern).unwrap();
38
- }
39
- if metadata.is_file() {
40
- return fs::canonicalize(pattern)
41
- .unwrap()
42
- .parent()
43
- .unwrap()
44
- .to_path_buf();
45
- }
46
- }
35
+ let base = PathBuf::from(".");
36
+ return fs::canonicalize(&base).unwrap_or(base);
47
37
  }
48
38
 
49
39
  let path = Path::new(pattern);
@@ -63,12 +53,11 @@ fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf {
63
53
  base
64
54
  };
65
55
 
66
- // Canonicalize to get the absolute path.
67
56
  fs::canonicalize(&base).unwrap_or(base)
68
57
  }
69
58
 
70
59
  /// Minimum time between triggering the same pattern group (debounce time)
71
- const DEBOUNCE_DURATION: Duration = Duration::from_millis(500);
60
+ const DEBOUNCE_DURATION: Duration = Duration::from_millis(2000);
72
61
 
73
62
  pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<Option<OwnedFd>> {
74
63
  let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
@@ -145,13 +134,14 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
145
134
  notify::recommended_watcher(event_fn(sender)).expect("Failed to create watcher");
146
135
  for group in &groups {
147
136
  if watched_dirs.insert(group.base_dir.clone()) {
148
- debug!("Watching {}{}", group.base_dir.display(), group.pattern);
137
+ debug!("Watching {}/{}", group.base_dir.display(), group.pattern);
149
138
  watcher
150
139
  .watch(&group.base_dir, RecursiveMode::Recursive)
151
140
  .expect("Failed to add watch");
152
141
  }
153
142
  }
154
143
 
144
+ debug!("Monitored groups {:?}", groups.len());
155
145
  // Main event loop.
156
146
  for res in rx {
157
147
  match res {
@@ -159,6 +149,7 @@ pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<O
159
149
  if !matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) {
160
150
  continue;
161
151
  }
152
+ debug!("Event fired {:?}", event);
162
153
  let now = Instant::now();
163
154
  for group in &mut groups {
164
155
  for path in event.paths.iter() {
@@ -72,6 +72,12 @@ pub struct ServerParams {
72
72
  pub ruby_thread_request_backlog_size: Option<usize>,
73
73
  pub middleware_loader: HeapValue<Proc>,
74
74
  pub middleware: OnceLock<MiddlewareSet>,
75
+ pub pipeline_flush: bool,
76
+ pub writev: Option<bool>,
77
+ pub max_concurrent_streams: Option<u32>,
78
+ pub max_local_error_reset_streams: Option<usize>,
79
+ pub max_header_list_size: u32,
80
+ pub max_send_buf_size: usize,
75
81
  pub binds: Vec<Bind>,
76
82
  #[debug(skip)]
77
83
  pub(crate) listeners: Mutex<Vec<Listener>>,
@@ -178,19 +184,19 @@ impl ServerParams {
178
184
  }
179
185
 
180
186
  fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
187
+ let num_cpus = num_cpus::get_physical() as u8;
181
188
  let workers = rb_param_hash
182
189
  .fetch::<_, Option<u8>>("workers")?
183
- .unwrap_or(num_cpus::get() as u8);
190
+ .unwrap_or(num_cpus);
184
191
  let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
185
192
  let silence: bool = rb_param_hash.fetch("silence")?;
186
193
  let multithreaded_reactor: bool = rb_param_hash
187
194
  .fetch::<_, Option<bool>>("multithreaded_reactor")?
188
- .unwrap_or(workers == 1);
195
+ .unwrap_or(workers <= (num_cpus / 3));
189
196
  let pin_worker_cores: bool = rb_param_hash
190
197
  .fetch::<_, Option<bool>>("pin_worker_cores")?
191
- .unwrap_or(true);
198
+ .unwrap_or(false);
192
199
  let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
193
-
194
200
  let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
195
201
  let hooks = hooks
196
202
  .map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
@@ -281,6 +287,14 @@ impl ServerParams {
281
287
  set_target_filters(target_filters);
282
288
  }
283
289
 
290
+ let pipeline_flush: bool = rb_param_hash.fetch("pipeline_flush")?;
291
+ let writev: Option<bool> = rb_param_hash.fetch("writev")?;
292
+ let max_concurrent_streams: Option<u32> = rb_param_hash.fetch("max_concurrent_streams")?;
293
+ let max_local_error_reset_streams: Option<usize> =
294
+ rb_param_hash.fetch("max_local_error_reset_streams")?;
295
+ let max_header_list_size: u32 = rb_param_hash.fetch("max_header_list_size")?;
296
+ let max_send_buf_size: usize = rb_param_hash.fetch("max_send_buf_size")?;
297
+
284
298
  let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
285
299
  let binds = binds
286
300
  .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
@@ -322,6 +336,12 @@ impl ServerParams {
322
336
  scheduler_class,
323
337
  ruby_thread_request_backlog_size,
324
338
  oob_gc_responses_threshold,
339
+ pipeline_flush,
340
+ writev,
341
+ max_concurrent_streams,
342
+ max_local_error_reset_streams,
343
+ max_header_list_size,
344
+ max_send_buf_size,
325
345
  binds,
326
346
  itsi_server_token_preference,
327
347
  socket_opts,
@@ -437,7 +457,8 @@ impl ItsiServerConfig {
437
457
  let requires_exec = if !is_single_mode && !server_params.preload {
438
458
  // In cluster mode children are cycled during a reload
439
459
  // and if preload is disabled, will get a clean memory slate,
440
- // so we don't need to exec.
460
+ // so we don't need to exec. We do need to rebind our listeners here.
461
+ server_params.setup_listeners()?;
441
462
  false
442
463
  } else {
443
464
  // In non-cluster mode, or when preloading is enabled, we shouldn't try to
@@ -64,7 +64,7 @@ impl ItsiServer {
64
64
  Ok(if server_config.server_params.read().workers > 1 {
65
65
  ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone())))
66
66
  } else {
67
- ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?))
67
+ ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone(), 0)?))
68
68
  })
69
69
  }
70
70
 
@@ -9,7 +9,7 @@ use super::bind_protocol::BindProtocol;
9
9
  use super::tls::ItsiTlsAcceptor;
10
10
  use itsi_error::{ItsiError, Result};
11
11
  use itsi_tracing::info;
12
- use socket2::{Domain, Protocol, Socket, Type};
12
+ use socket2::{Domain, Protocol, SockRef, Socket, Type};
13
13
  use std::fmt::Display;
14
14
  use std::net::{IpAddr, SocketAddr, TcpListener};
15
15
  use std::os::fd::{AsRawFd, FromRawFd, RawFd};
@@ -274,15 +274,53 @@ impl Display for Listener {
274
274
  }
275
275
 
276
276
  impl Listener {
277
- pub fn into_tokio_listener(self) -> TokioListener {
277
+ pub fn rebind_listener(listener: TcpListener) -> TcpListener {
278
+ let sock = SockRef::from(&listener);
279
+ let (reuse_address, reuse_port) = (
280
+ sock.reuse_address().unwrap_or(true),
281
+ sock.reuse_port().unwrap_or(true),
282
+ );
283
+
284
+ if !reuse_address || !reuse_port {
285
+ return listener;
286
+ }
287
+
288
+ let (ip, port) = sock
289
+ .local_addr()
290
+ .unwrap()
291
+ .as_socket()
292
+ .map(|addr| (addr.ip(), addr.port()))
293
+ .unwrap();
294
+
295
+ let socket_opts = SocketOpts {
296
+ reuse_address: sock.reuse_address().unwrap_or(true), // default: true
297
+ reuse_port: sock.reuse_port().unwrap_or(false), // default: false
298
+ nodelay: sock.nodelay().unwrap_or(false), // default: false
299
+ recv_buffer_size: sock.recv_buffer_size().unwrap_or(0),
300
+ send_buffer_size: sock.send_buffer_size().unwrap_or(0),
301
+ listen_backlog: 1024, // cannot query – pick sane default
302
+ };
303
+
304
+ connect_tcp_socket(ip, port, &socket_opts).unwrap()
305
+ }
306
+
307
+ pub fn into_tokio_listener(self, no_rebind: bool) -> TokioListener {
278
308
  match self {
279
- Listener::Tcp(listener) => {
309
+ Listener::Tcp(mut listener) => {
310
+ if cfg!(target_os = "linux") && !no_rebind {
311
+ listener = Listener::rebind_listener(listener);
312
+ }
280
313
  TokioListener::Tcp(TokioTcpListener::from_std(listener).unwrap())
281
314
  }
282
- Listener::TcpTls((listener, acceptor)) => TokioListener::TcpTls(
283
- TokioTcpListener::from_std(listener).unwrap(),
284
- acceptor.clone(),
285
- ),
315
+ Listener::TcpTls((mut listener, acceptor)) => {
316
+ if cfg!(target_os = "linux") && !no_rebind {
317
+ listener = Listener::rebind_listener(listener);
318
+ }
319
+ TokioListener::TcpTls(
320
+ TokioTcpListener::from_std(listener).unwrap(),
321
+ acceptor.clone(),
322
+ )
323
+ }
286
324
  Listener::Unix(listener) => {
287
325
  TokioListener::Unix(TokioUnixListener::from_std(listener).unwrap())
288
326
  }