itsi 0.1.8 → 0.1.9

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/crates/itsi_server/src/lib.rs +5 -0
  4. data/crates/itsi_server/src/request/itsi_request.rs +30 -2
  5. data/crates/itsi_server/src/response/itsi_response.rs +12 -2
  6. data/crates/itsi_server/src/server/itsi_server.rs +127 -70
  7. data/crates/itsi_server/src/server/listener.rs +1 -1
  8. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
  9. data/crates/itsi_server/src/server/signal.rs +7 -0
  10. data/crates/itsi_server/src/server/thread_worker.rs +3 -4
  11. data/crates/itsi_server/src/server/tls.rs +11 -8
  12. data/crates/itsi_tracing/src/lib.rs +18 -1
  13. data/gems/scheduler/Cargo.lock +12 -12
  14. data/gems/scheduler/ext/itsi_server/src/lib.rs +5 -0
  15. data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +30 -2
  16. data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +12 -2
  17. data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +127 -70
  18. data/gems/scheduler/ext/itsi_server/src/server/listener.rs +1 -1
  19. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
  20. data/gems/scheduler/ext/itsi_server/src/server/signal.rs +7 -0
  21. data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +3 -4
  22. data/gems/scheduler/ext/itsi_server/src/server/tls.rs +11 -8
  23. data/gems/scheduler/ext/itsi_tracing/src/lib.rs +18 -1
  24. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  25. data/gems/scheduler/test/test_address_resolve.rb +0 -1
  26. data/gems/scheduler/test/test_file_io.rb +0 -1
  27. data/gems/scheduler/test/test_kernel_sleep.rb +3 -4
  28. data/gems/server/Rakefile +8 -1
  29. data/gems/server/ext/itsi_server/src/lib.rs +5 -0
  30. data/gems/server/ext/itsi_server/src/request/itsi_request.rs +30 -2
  31. data/gems/server/ext/itsi_server/src/response/itsi_response.rs +12 -2
  32. data/gems/server/ext/itsi_server/src/server/itsi_server.rs +127 -70
  33. data/gems/server/ext/itsi_server/src/server/listener.rs +1 -1
  34. data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
  35. data/gems/server/ext/itsi_server/src/server/signal.rs +7 -0
  36. data/gems/server/ext/itsi_server/src/server/thread_worker.rs +3 -4
  37. data/gems/server/ext/itsi_server/src/server/tls.rs +11 -8
  38. data/gems/server/ext/itsi_tracing/src/lib.rs +18 -1
  39. data/gems/server/lib/itsi/request.rb +29 -21
  40. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -4
  41. data/gems/server/lib/itsi/server/rack_interface.rb +79 -0
  42. data/gems/server/lib/itsi/server/scheduler_interface.rb +21 -0
  43. data/gems/server/lib/itsi/server/signal_trap.rb +24 -0
  44. data/gems/server/lib/itsi/server/version.rb +1 -1
  45. data/gems/server/lib/itsi/server.rb +67 -101
  46. data/gems/server/test/helpers/test_helper.rb +28 -0
  47. data/gems/server/test/test_itsi_server.rb +275 -3
  48. data/lib/itsi/version.rb +1 -1
  49. data/sandbox/deploy/main.tf +1 -0
  50. data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
  51. data/tasks.txt +0 -6
  52. metadata +13 -11
  53. data/gems/server/lib/itsi/signals.rb +0 -23
  54. data/gems/server/test/test_helper.rb +0 -7
  55. /data/gems/server/lib/itsi/{index.html.erb → index.html} +0 -0
@@ -48,23 +48,26 @@ pub fn configure_tls(
48
48
  ) -> Result<ItsiTlsAcceptor> {
49
49
  let domains = query_params
50
50
  .get("domains")
51
- .map(|v| v.split(',').map(String::from).collect::<Vec<_>>());
51
+ .map(|v| v.split(',').map(String::from).collect::<Vec<_>>())
52
+ .or_else(|| query_params.get("domain").map(|v| vec![v.to_string()]));
52
53
 
53
- if query_params.get("cert").is_some_and(|c| c == "auto") {
54
+ if query_params.get("cert").is_some_and(|c| c == "acme") {
54
55
  if let Some(domains) = domains {
55
56
  let directory_url = &*ITSI_ACME_DIRECTORY_URL;
56
57
  info!(
57
58
  domains = format!("{:?}", domains),
58
59
  directory_url, "Requesting acme cert"
59
60
  );
61
+ let acme_contact_email = query_params
62
+ .get("acme_email")
63
+ .map(|s| s.to_string())
64
+ .or_else(|| (*ITSI_ACME_CONTACT_EMAIL).as_ref().ok().map(|s| s.to_string()))
65
+ .ok_or_else(|| itsi_error::ItsiError::ArgumentError(
66
+ "acme_cert query param or ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate let's encrypt certificates".to_string(),
67
+ ))?;
60
68
 
61
69
  let acme_config = AcmeConfig::new(domains)
62
- .contact([format!("mailto:{}", (*ITSI_ACME_CONTACT_EMAIL).as_ref().map_err(|_| {
63
- itsi_error::ItsiError::ArgumentError(
64
- "ITSI_ACME_CONTACT_EMAIL must be set before you can auto-generate production certificates"
65
- .to_string(),
66
- )
67
- })?)])
70
+ .contact([format!("mailto:{}", acme_contact_email)])
68
71
  .cache(LockedDirCache::new(&*ITSI_ACME_CACHE_DIR))
69
72
  .directory(directory_url);
70
73
 
@@ -1,11 +1,13 @@
1
1
  use std::env;
2
2
 
3
3
  use atty::{Stream, is};
4
+ use tracing::level_filters::LevelFilter;
4
5
  pub use tracing::{debug, error, info, trace, warn};
5
6
  pub use tracing_attributes::instrument; // Explicitly export from tracing-attributes
6
7
  use tracing_subscriber::{
7
- EnvFilter,
8
+ EnvFilter, Layer,
8
9
  fmt::{self, format},
10
+ layer::SubscriberExt,
9
11
  };
10
12
 
11
13
  #[instrument]
@@ -39,3 +41,18 @@ pub fn init() {
39
41
  .init();
40
42
  }
41
43
  }
44
+
45
+ pub fn run_silently<F, R>(f: F) -> R
46
+ where
47
+ F: FnOnce() -> R,
48
+ {
49
+ // Build a minimal subscriber that filters *everything* out
50
+ let no_op_subscriber =
51
+ tracing_subscriber::registry().with(fmt::layer().with_filter(LevelFilter::OFF));
52
+
53
+ // Turn that subscriber into a `Dispatch`
54
+ let no_op_dispatch = tracing::dispatcher::Dispatch::new(no_op_subscriber);
55
+
56
+ // Temporarily set `no_op_dispatch` as the *default* within this closure
57
+ tracing::dispatcher::with_default(&no_op_dispatch, f)
58
+ }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.1.8"
5
+ VERSION = "0.1.9"
6
6
  end
7
7
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require 'debug'
3
3
 
4
-
5
4
  class TestAddressResolve < Minitest::Test
6
5
  include Itsi::Scheduler::TestHelper
7
6
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  class TestFileIO < Minitest::Test
5
4
  include Itsi::Scheduler::TestHelper
6
5
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
3
  class TestKernelSleep < Minitest::Test
5
4
  include Itsi::Scheduler::TestHelper
6
5
 
@@ -14,7 +13,7 @@ class TestKernelSleep < Minitest::Test
14
13
  # Run the scheduler in a dedicated thread to avoid interference with the
15
14
  # main thread’s scheduler state.
16
15
  with_scheduler do |_scheduler|
17
- 5.times do
16
+ 10.times do
18
17
  Fiber.schedule do
19
18
  sleep 0.05
20
19
  results << "first"
@@ -26,9 +25,9 @@ class TestKernelSleep < Minitest::Test
26
25
 
27
26
  ends_at = Time.now
28
27
  # We expect 10 sleep completions overall (2 per fiber).
29
- assert_equal 10, results.size
28
+ assert_equal 20, results.size
30
29
  # Because all sleeps run concurrently, the total elapsed time should be about 1 second.
31
- assert_in_delta 0.1, ends_at - start_at, 0.02, "Total elapsed time should be close to 0.1 second"
30
+ assert_in_delta 0.1, ends_at - start_at, 0.1, "Total elapsed time should be close to 0.1 second"
32
31
  end
33
32
 
34
33
  def test_sleep_zero_duration
data/gems/server/Rakefile CHANGED
@@ -3,7 +3,14 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
5
 
6
- Minitest::TestTask.create
6
+
7
+ Minitest::TestTask.create(:test) do |t|
8
+ t.libs << 'test'
9
+ t.libs << 'lib'
10
+ t.warning = false
11
+ t.test_globs = ['test/**/*.rb']
12
+ t.test_prelude = 'require "helpers/test_helper.rb"'
13
+ end
7
14
 
8
15
  require "rubocop/rake_task"
9
16
 
@@ -71,6 +71,7 @@ fn init(ruby: &Ruby) -> Result<()> {
71
71
  server.define_singleton_method("new", function!(Server::new, -1))?;
72
72
  server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
73
73
  server.define_method("start", method!(Server::start, 0))?;
74
+ server.define_method("stop", method!(Server::stop, 0))?;
74
75
 
75
76
  let request = ruby.get_inner(&ITSI_REQUEST);
76
77
  request.define_method("path", method!(ItsiRequest::path, 0))?;
@@ -86,6 +87,8 @@ fn init(ruby: &Ruby) -> Result<()> {
86
87
  request.define_method("port", method!(ItsiRequest::port, 0))?;
87
88
  request.define_method("body", method!(ItsiRequest::body, 0))?;
88
89
  request.define_method("response", method!(ItsiRequest::response, 0))?;
90
+ request.define_method("json?", method!(ItsiRequest::is_json, 0))?;
91
+ request.define_method("html?", method!(ItsiRequest::is_html, 0))?;
89
92
 
90
93
  let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
91
94
  body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
@@ -102,6 +105,8 @@ fn init(ruby: &Ruby) -> Result<()> {
102
105
  response.define_method("close_read", method!(ItsiResponse::close_read, 0))?;
103
106
  response.define_method("close", method!(ItsiResponse::close, 0))?;
104
107
  response.define_method("hijack", method!(ItsiResponse::hijack, 1))?;
108
+ response.define_method("json?", method!(ItsiResponse::is_json, 0))?;
109
+ response.define_method("html?", method!(ItsiResponse::is_html, 0))?;
105
110
 
106
111
  Ok(())
107
112
  }
@@ -13,7 +13,7 @@ use crate::{
13
13
  use bytes::Bytes;
14
14
  use derive_more::Debug;
15
15
  use futures::StreamExt;
16
- use http::{request::Parts, Response, StatusCode};
16
+ use http::{request::Parts, HeaderValue, Response, StatusCode};
17
17
  use http_body_util::{combinators::BoxBody, BodyExt, Empty};
18
18
  use hyper::{body::Incoming, Request};
19
19
  use itsi_error::from::CLIENT_CONNECTION_CLOSED;
@@ -49,6 +49,7 @@ pub struct ItsiRequest {
49
49
  pub server: Arc<Server>,
50
50
  pub response: ItsiResponse,
51
51
  pub start: Instant,
52
+ pub content_type: String,
52
53
  }
53
54
 
54
55
  impl fmt::Display for ItsiRequest {
@@ -82,6 +83,14 @@ impl ItsiRequest {
82
83
  }
83
84
  }
84
85
 
86
+ pub fn is_json(&self) -> bool {
87
+ self.content_type.eq("application/json")
88
+ }
89
+
90
+ pub fn is_html(&self) -> bool {
91
+ self.content_type.eq("text/html")
92
+ }
93
+
85
94
  pub fn process(
86
95
  self,
87
96
  ruby: &Ruby,
@@ -175,8 +184,27 @@ impl ItsiRequest {
175
184
  server,
176
185
  listener,
177
186
  version: format!("{:?}", &parts.version),
178
- response: ItsiResponse::new(parts.clone(), response_channel.0),
187
+ response: ItsiResponse::new(
188
+ parts.clone(),
189
+ response_channel.0,
190
+ parts
191
+ .headers
192
+ .get("Accept")
193
+ .unwrap_or(&HeaderValue::from_static("text/html"))
194
+ .to_str()
195
+ .unwrap()
196
+ .to_string(),
197
+ ),
179
198
  start: Instant::now(),
199
+ content_type: parts
200
+ .headers
201
+ .get("Content-Type")
202
+ .unwrap_or(&HeaderValue::from_static(
203
+ "application/x-www-form-urlencoded",
204
+ ))
205
+ .to_str()
206
+ .unwrap()
207
+ .to_string(),
180
208
  parts,
181
209
  },
182
210
  response_channel.1,
@@ -37,6 +37,7 @@ use crate::server::serve_strategy::single_mode::RunningPhase;
37
37
  #[derive(Debug, Clone)]
38
38
  pub struct ItsiResponse {
39
39
  pub data: Arc<ResponseData>,
40
+ pub accept: String,
40
41
  }
41
42
 
42
43
  #[derive(Debug)]
@@ -293,11 +294,19 @@ impl ItsiResponse {
293
294
  Ok(true)
294
295
  }
295
296
 
297
+ pub fn is_html(&self) -> bool {
298
+ self.accept.starts_with("text/html")
299
+ }
300
+
301
+ pub fn is_json(&self) -> bool {
302
+ self.accept.starts_with("application/json")
303
+ }
304
+
296
305
  pub fn close_read(&self) -> MagnusResult<bool> {
297
- todo!();
306
+ Ok(true)
298
307
  }
299
308
 
300
- pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>) -> Self {
309
+ pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes>>, accept: String) -> Self {
301
310
  Self {
302
311
  data: Arc::new(ResponseData {
303
312
  response: RwLock::new(Some(Response::new(BoxBody::new(Empty::new())))),
@@ -306,6 +315,7 @@ impl ItsiResponse {
306
315
  hijacked_socket: RwLock::new(None),
307
316
  parts,
308
317
  }),
318
+ accept,
309
319
  }
310
320
  }
311
321
 
@@ -2,20 +2,22 @@ use super::{
2
2
  bind::Bind,
3
3
  listener::Listener,
4
4
  serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode},
5
- signal::{clear_signal_handlers, reset_signal_handlers, SIGNAL_HANDLER_CHANNEL},
5
+ signal::{
6
+ clear_signal_handlers, reset_signal_handlers, send_shutdown_event, SIGNAL_HANDLER_CHANNEL,
7
+ },
6
8
  };
7
9
  use crate::{request::itsi_request::ItsiRequest, server::serve_strategy::ServeStrategy};
8
10
  use derive_more::Debug;
9
- use itsi_rb_helpers::call_without_gvl;
10
- use itsi_tracing::error;
11
+ use itsi_rb_helpers::{call_without_gvl, HeapVal};
12
+ use itsi_tracing::{error, run_silently};
11
13
  use magnus::{
12
14
  block::Proc,
13
15
  error::Result,
14
- scan_args::{get_kwargs, scan_args, Args, KwArgs},
16
+ scan_args::{get_kwargs, scan_args, Args, KwArgs, ScanArgsKw, ScanArgsOpt, ScanArgsRequired},
15
17
  value::{InnerValue, Opaque, ReprValue},
16
- RHash, Ruby, Symbol, Value,
18
+ ArgList, RHash, Ruby, Symbol, Value,
17
19
  };
18
- use parking_lot::Mutex;
20
+ use parking_lot::{Mutex, RwLock};
19
21
  use std::{cmp::max, ops::Deref, sync::Arc};
20
22
  use tracing::{info, instrument};
21
23
 
@@ -39,7 +41,7 @@ type AfterFork = Mutex<Arc<Option<Box<dyn Fn() + Send + Sync>>>>;
39
41
  #[derive(Debug)]
40
42
  pub struct ServerConfig {
41
43
  #[debug(skip)]
42
- pub app: Opaque<Value>,
44
+ pub app: HeapVal,
43
45
  #[allow(unused)]
44
46
  pub workers: u8,
45
47
  #[allow(unused)]
@@ -55,6 +57,9 @@ pub struct ServerConfig {
55
57
  pub scheduler_class: Option<String>,
56
58
  pub stream_body: Option<bool>,
57
59
  pub worker_memory_limit: Option<u64>,
60
+ #[debug(skip)]
61
+ pub(crate) strategy: RwLock<Option<ServeStrategy>>,
62
+ pub silence: bool,
58
63
  }
59
64
 
60
65
  #[derive(Debug)]
@@ -63,6 +68,35 @@ pub enum RequestJob {
63
68
  Shutdown,
64
69
  }
65
70
 
71
+ // Define your helper function.
72
+ // Here P, A, C correspond to the types for the first tuple, second tuple, and extra parameters respectively.
73
+ fn extract_args<Req, Opt, Splat>(
74
+ scan_args: &Args<(), (), (), (), RHash, ()>,
75
+ primaries: &[&str],
76
+ rest: &[&str],
77
+ ) -> Result<KwArgs<Req, Opt, Splat>>
78
+ where
79
+ Req: ScanArgsRequired,
80
+ Opt: ScanArgsOpt,
81
+ Splat: ScanArgsKw,
82
+ {
83
+ // Combine the primary and rest names into one Vec of Symbols.
84
+ let symbols: Vec<Symbol> = primaries
85
+ .iter()
86
+ .chain(rest.iter())
87
+ .map(|&name| Symbol::new(name))
88
+ .collect();
89
+
90
+ // Call the "slice" function with the combined symbols.
91
+ let hash = scan_args
92
+ .keywords
93
+ .funcall::<_, _, RHash>("slice", symbols.into_arg_list_with(&Ruby::get().unwrap()))
94
+ .unwrap();
95
+
96
+ // Finally, call get_kwargs with the original name slices.
97
+ get_kwargs(hash, primaries, rest)
98
+ }
99
+
66
100
  impl Server {
67
101
  #[instrument(
68
102
  name = "Itsi",
@@ -73,39 +107,44 @@ impl Server {
73
107
  pub fn new(args: &[Value]) -> Result<Self> {
74
108
  let scan_args: Args<(), (), (), (), RHash, ()> = scan_args(args)?;
75
109
 
76
- type ArgSet1 = (
77
- Option<u8>,
78
- Option<u8>,
79
- Option<f64>,
80
- Option<String>,
81
- Option<Vec<String>>,
82
- Option<Proc>,
83
- Option<Proc>,
84
- Option<String>,
85
- Option<bool>,
86
- );
87
-
88
- type ArgSet2 = (Option<u64>,);
89
-
90
- let args1: KwArgs<(Value,), ArgSet1, ()> = get_kwargs(
91
- scan_args
92
- .keywords
93
- .funcall::<_, _, RHash>(
94
- "slice",
95
- (
96
- Symbol::new("app"),
97
- Symbol::new("workers"),
98
- Symbol::new("threads"),
99
- Symbol::new("shutdown_timeout"),
100
- Symbol::new("script_name"),
101
- Symbol::new("binds"),
102
- Symbol::new("before_fork"),
103
- Symbol::new("after_fork"),
104
- Symbol::new("scheduler_class"),
105
- Symbol::new("stream_body"),
106
- ),
107
- )
108
- .unwrap(),
110
+ type Args1 = KwArgs<
111
+ (Value,),
112
+ (
113
+ // Workers
114
+ Option<u8>,
115
+ // Threads
116
+ Option<u8>,
117
+ // Shutdown Timeout
118
+ Option<f64>,
119
+ // Script Name
120
+ Option<String>,
121
+ // Binds
122
+ Option<Vec<String>>,
123
+ // Stream Body
124
+ Option<bool>,
125
+ ),
126
+ (),
127
+ >;
128
+
129
+ type Args2 = KwArgs<
130
+ (),
131
+ (
132
+ // Before Fork
133
+ Option<Proc>,
134
+ // After Fork
135
+ Option<Proc>,
136
+ // Scheduler Class
137
+ Option<String>,
138
+ // Worker Memory Limit
139
+ Option<u64>,
140
+ // Silence
141
+ Option<bool>,
142
+ ),
143
+ (),
144
+ >;
145
+
146
+ let args1: Args1 = extract_args(
147
+ &scan_args,
109
148
  &["app"],
110
149
  &[
111
150
  "workers",
@@ -113,24 +152,24 @@ impl Server {
113
152
  "shutdown_timeout",
114
153
  "script_name",
115
154
  "binds",
116
- "before_fork",
117
- "after_fork",
118
- "scheduler_class",
119
155
  "stream_body",
120
156
  ],
121
157
  )?;
122
158
 
123
- let args2: KwArgs<(), ArgSet2, ()> = get_kwargs(
124
- scan_args
125
- .keywords
126
- .funcall::<_, _, RHash>("slice", (Symbol::new("worker_memory_limit"),))
127
- .unwrap(),
159
+ let args2: Args2 = extract_args(
160
+ &scan_args,
128
161
  &[],
129
- &["worker_memory_limit"],
162
+ &[
163
+ "before_fork",
164
+ "after_fork",
165
+ "scheduler_class",
166
+ "worker_memory_limit",
167
+ "silence",
168
+ ],
130
169
  )?;
131
170
 
132
171
  let config = ServerConfig {
133
- app: Opaque::from(args1.required.0),
172
+ app: HeapVal::from(args1.required.0),
134
173
  workers: max(args1.optional.0.unwrap_or(1), 1),
135
174
  threads: max(args1.optional.1.unwrap_or(1), 1),
136
175
  shutdown_timeout: args1.optional.2.unwrap_or(5.0),
@@ -144,7 +183,8 @@ impl Server {
144
183
  .map(|s| s.parse())
145
184
  .collect::<itsi_error::Result<Vec<Bind>>>()?,
146
185
  ),
147
- before_fork: Mutex::new(args1.optional.5.map(|p| {
186
+ stream_body: args1.optional.5,
187
+ before_fork: Mutex::new(args2.optional.0.map(|p| {
148
188
  let opaque_proc = Opaque::from(p);
149
189
  Box::new(move || {
150
190
  opaque_proc
@@ -153,7 +193,7 @@ impl Server {
153
193
  .unwrap();
154
194
  }) as Box<dyn FnOnce() + Send + Sync>
155
195
  })),
156
- after_fork: Mutex::new(Arc::new(args1.optional.6.map(|p| {
196
+ after_fork: Mutex::new(Arc::new(args2.optional.1.map(|p| {
157
197
  let opaque_proc = Opaque::from(p);
158
198
  Box::new(move || {
159
199
  opaque_proc
@@ -162,15 +202,18 @@ impl Server {
162
202
  .unwrap();
163
203
  }) as Box<dyn Fn() + Send + Sync>
164
204
  }))),
165
- scheduler_class: args1.optional.7.clone(),
166
- stream_body: args1.optional.8,
167
- worker_memory_limit: args2.optional.0,
205
+ scheduler_class: args2.optional.2.clone(),
206
+ worker_memory_limit: args2.optional.3,
207
+ silence: args2.optional.4.is_some_and(|s| s),
208
+ strategy: RwLock::new(None),
168
209
  };
169
210
 
170
- if let Some(scheduler_class) = args1.optional.7 {
171
- info!(scheduler_class, fiber_scheduler = true);
172
- } else {
173
- info!(fiber_scheduler = false);
211
+ if !config.silence {
212
+ if let Some(scheduler_class) = args2.optional.2 {
213
+ info!(scheduler_class, fiber_scheduler = true);
214
+ } else {
215
+ info!(fiber_scheduler = false);
216
+ }
174
217
  }
175
218
 
176
219
  Ok(Server {
@@ -179,7 +222,7 @@ impl Server {
179
222
  }
180
223
 
181
224
  #[instrument(name = "Bind", skip_all, fields(binds=format!("{:?}", self.config.binds.lock())))]
182
- pub(crate) fn listeners(&self) -> Result<Arc<Vec<Arc<Listener>>>> {
225
+ pub(crate) fn build_listeners(&self) -> Result<Arc<Vec<Arc<Listener>>>> {
183
226
  let listeners = self
184
227
  .config
185
228
  .binds
@@ -195,11 +238,9 @@ impl Server {
195
238
  Ok(Arc::new(listeners))
196
239
  }
197
240
 
198
- pub(crate) fn build_strategy(
199
- self,
200
- listeners: Arc<Vec<Arc<Listener>>>,
201
- ) -> Result<ServeStrategy> {
241
+ pub(crate) fn build_strategy(self, listeners: Arc<Vec<Arc<Listener>>>) -> Result<()> {
202
242
  let server = Arc::new(self);
243
+ let server_clone = server.clone();
203
244
 
204
245
  let strategy = if server.config.workers == 1 {
205
246
  ServeStrategy::Single(Arc::new(SingleMode::new(
@@ -214,24 +255,40 @@ impl Server {
214
255
  SIGNAL_HANDLER_CHANNEL.0.clone(),
215
256
  )))
216
257
  };
217
- Ok(strategy)
258
+
259
+ *server_clone.strategy.write() = Some(strategy);
260
+ Ok(())
261
+ }
262
+
263
+ pub fn stop(&self) -> Result<()> {
264
+ send_shutdown_event();
265
+ Ok(())
218
266
  }
219
267
 
220
268
  pub fn start(&self) -> Result<()> {
269
+ if self.silence {
270
+ run_silently(|| self.build_and_run_strategy())
271
+ } else {
272
+ self.build_and_run_strategy()
273
+ }
274
+ }
275
+
276
+ fn build_and_run_strategy(&self) -> Result<()> {
221
277
  reset_signal_handlers();
222
278
  let rself = self.clone();
223
- let listeners = self.listeners()?;
279
+ let listeners = self.build_listeners()?;
224
280
  let listeners_clone = listeners.clone();
225
281
  call_without_gvl(move || -> Result<()> {
226
- let strategy = rself.build_strategy(listeners_clone)?;
227
- if let Err(e) = strategy.run() {
282
+ rself.clone().build_strategy(listeners_clone)?;
283
+ if let Err(e) = rself.clone().strategy.read().as_ref().unwrap().run() {
228
284
  error!("Error running server: {}", e);
229
- strategy.stop()?;
285
+ rself.strategy.read().as_ref().unwrap().stop()?;
230
286
  }
231
- drop(strategy);
232
287
  Ok(())
233
288
  })?;
234
289
  clear_signal_handlers();
290
+ self.strategy.write().take();
291
+ info!("Server stopped");
235
292
  Ok(())
236
293
  }
237
294
  }
@@ -117,7 +117,7 @@ impl TokioListener {
117
117
  tokio::select! {
118
118
  stream_event = StreamExt::next(&mut *state) => {
119
119
  match stream_event {
120
- Some(event) => info!("Received acme event: {:?}", event),
120
+ Some(event) => info!("ACME Event: {:?}", event),
121
121
  None => error!("Received no acme event"),
122
122
  }
123
123
  },
@@ -25,7 +25,10 @@ use std::{
25
25
  };
26
26
  use tokio::{
27
27
  runtime::{Builder as RuntimeBuilder, Runtime},
28
- sync::broadcast,
28
+ sync::{
29
+ broadcast,
30
+ watch::{self, Sender},
31
+ },
29
32
  task::JoinSet,
30
33
  };
31
34
  use tracing::instrument;
@@ -55,7 +58,7 @@ impl SingleMode {
55
58
  let (thread_workers, sender) = build_thread_workers(
56
59
  Pid::this(),
57
60
  NonZeroU8::try_from(server.threads).unwrap(),
58
- server.app,
61
+ server.app.clone(),
59
62
  server.scheduler_class.clone(),
60
63
  )?;
61
64
  Ok(Self {
@@ -80,6 +83,9 @@ impl SingleMode {
80
83
  }
81
84
 
82
85
  pub fn stop(&self) -> Result<()> {
86
+ self.lifecycle_channel
87
+ .send(LifecycleEvent::Shutdown)
88
+ .expect("Failed to send shutdown event");
83
89
  Ok(())
84
90
  }
85
91
 
@@ -95,14 +101,17 @@ impl SingleMode {
95
101
  .iter()
96
102
  .map(|list| Arc::new(list.to_tokio_listener()))
97
103
  .collect::<Vec<_>>();
104
+ let (shutdown_sender, _) = watch::channel::<RunningPhase>(RunningPhase::Running);
98
105
  for listener in tokio_listeners.iter() {
99
106
  let mut lifecycle_rx = self_ref.lifecycle_channel.subscribe();
100
107
  let listener_info = Arc::new(listener.listener_info());
101
108
  let self_ref = self_ref.clone();
102
109
  let listener = listener.clone();
103
- let (shutdown_sender, mut shutdown_receiver) = tokio::sync::watch::channel::<RunningPhase>(RunningPhase::Running);
104
- let listener_clone = listener.clone();
110
+ let shutdown_sender = shutdown_sender.clone();
105
111
 
112
+
113
+ let listener_clone = listener.clone();
114
+ let mut shutdown_receiver = shutdown_sender.clone().subscribe();
106
115
  let shutdown_receiver_clone = shutdown_receiver.clone();
107
116
  listener_task_set.spawn(async move {
108
117
  listener_clone.spawn_state_task(shutdown_receiver_clone).await;
@@ -157,7 +166,7 @@ impl SingleMode {
157
166
  &self,
158
167
  stream: IoStream,
159
168
  listener: Arc<ListenerInfo>,
160
- shutdown_channel: tokio::sync::watch::Receiver<RunningPhase>,
169
+ shutdown_channel: watch::Receiver<RunningPhase>,
161
170
  ) -> Result<()> {
162
171
  let sender_clone = self.sender.clone();
163
172
  let addr = stream.addr();
@@ -219,12 +228,11 @@ impl SingleMode {
219
228
  pub async fn handle_lifecycle_event(
220
229
  &self,
221
230
  lifecycle_event: LifecycleEvent,
222
- shutdown_sender: tokio::sync::watch::Sender<RunningPhase>,
231
+ shutdown_sender: Sender<RunningPhase>,
223
232
  ) -> Result<()> {
233
+ info!("Handling lifecycle event: {:?}", lifecycle_event);
224
234
  if let LifecycleEvent::Shutdown = lifecycle_event {
225
- shutdown_sender
226
- .send(RunningPhase::ShutdownPending)
227
- .expect("Failed to send shutdown pending signal");
235
+ shutdown_sender.send(RunningPhase::ShutdownPending).ok();
228
236
  let deadline = Instant::now() + Duration::from_secs_f64(self.server.shutdown_timeout);
229
237
  for worker in &*self.thread_workers {
230
238
  worker.request_shutdown().await;
@@ -243,9 +251,7 @@ impl SingleMode {
243
251
  }
244
252
 
245
253
  info!("Sending shutdown signal");
246
- shutdown_sender
247
- .send(RunningPhase::Shutdown)
248
- .expect("Failed to send shutdown signal");
254
+ shutdown_sender.send(RunningPhase::Shutdown).ok();
249
255
  self.thread_workers.iter().for_each(|worker| {
250
256
  worker.poll_shutdown(deadline);
251
257
  });