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.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/crates/itsi_server/src/lib.rs +5 -0
- data/crates/itsi_server/src/request/itsi_request.rs +30 -2
- data/crates/itsi_server/src/response/itsi_response.rs +12 -2
- data/crates/itsi_server/src/server/itsi_server.rs +127 -70
- data/crates/itsi_server/src/server/listener.rs +1 -1
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
- data/crates/itsi_server/src/server/signal.rs +7 -0
- data/crates/itsi_server/src/server/thread_worker.rs +3 -4
- data/crates/itsi_server/src/server/tls.rs +11 -8
- data/crates/itsi_tracing/src/lib.rs +18 -1
- data/gems/scheduler/Cargo.lock +12 -12
- data/gems/scheduler/ext/itsi_server/src/lib.rs +5 -0
- data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +30 -2
- data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +12 -2
- data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +127 -70
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +1 -1
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +7 -0
- data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +3 -4
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +11 -8
- data/gems/scheduler/ext/itsi_tracing/src/lib.rs +18 -1
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/test/test_address_resolve.rb +0 -1
- data/gems/scheduler/test/test_file_io.rb +0 -1
- data/gems/scheduler/test/test_kernel_sleep.rb +3 -4
- data/gems/server/Rakefile +8 -1
- data/gems/server/ext/itsi_server/src/lib.rs +5 -0
- data/gems/server/ext/itsi_server/src/request/itsi_request.rs +30 -2
- data/gems/server/ext/itsi_server/src/response/itsi_response.rs +12 -2
- data/gems/server/ext/itsi_server/src/server/itsi_server.rs +127 -70
- data/gems/server/ext/itsi_server/src/server/listener.rs +1 -1
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +18 -12
- data/gems/server/ext/itsi_server/src/server/signal.rs +7 -0
- data/gems/server/ext/itsi_server/src/server/thread_worker.rs +3 -4
- data/gems/server/ext/itsi_server/src/server/tls.rs +11 -8
- data/gems/server/ext/itsi_tracing/src/lib.rs +18 -1
- data/gems/server/lib/itsi/request.rb +29 -21
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +3 -4
- data/gems/server/lib/itsi/server/rack_interface.rb +79 -0
- data/gems/server/lib/itsi/server/scheduler_interface.rb +21 -0
- data/gems/server/lib/itsi/server/signal_trap.rb +24 -0
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +67 -101
- data/gems/server/test/helpers/test_helper.rb +28 -0
- data/gems/server/test/test_itsi_server.rb +275 -3
- data/lib/itsi/version.rb +1 -1
- data/sandbox/deploy/main.tf +1 -0
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/tasks.txt +0 -6
- metadata +13 -11
- data/gems/server/lib/itsi/signals.rb +0 -23
- data/gems/server/test/test_helper.rb +0 -7
- /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 == "
|
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:{}",
|
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
|
+
}
|
@@ -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
|
-
|
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
|
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.
|
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
|
-
|
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(
|
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
|
-
|
306
|
+
Ok(true)
|
298
307
|
}
|
299
308
|
|
300
|
-
pub fn new(parts: Parts, response_writer: mpsc::Sender<Option<Bytes
|
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::{
|
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:
|
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
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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:
|
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
|
-
&[
|
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:
|
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
|
-
|
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(
|
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:
|
166
|
-
|
167
|
-
|
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
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
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
|
-
|
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.
|
279
|
+
let listeners = self.build_listeners()?;
|
224
280
|
let listeners_clone = listeners.clone();
|
225
281
|
call_without_gvl(move || -> Result<()> {
|
226
|
-
|
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!("
|
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::
|
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
|
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:
|
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:
|
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
|
});
|