itsi-server 0.1.1 → 0.1.11

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.

Potentially problematic release.


This version of itsi-server might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2926 -0
  3. data/Cargo.toml +7 -0
  4. data/Rakefile +8 -1
  5. data/exe/itsi +119 -29
  6. data/ext/itsi_error/Cargo.toml +2 -0
  7. data/ext/itsi_error/src/from.rs +68 -0
  8. data/ext/itsi_error/src/lib.rs +13 -38
  9. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  10. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  11. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  12. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  13. data/ext/itsi_rb_helpers/src/lib.rs +112 -9
  14. data/ext/itsi_scheduler/Cargo.toml +24 -0
  15. data/ext/itsi_scheduler/extconf.rb +6 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  17. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  18. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  19. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  20. data/ext/itsi_scheduler/src/lib.rs +38 -0
  21. data/ext/itsi_server/Cargo.lock +2956 -0
  22. data/ext/itsi_server/Cargo.toml +25 -4
  23. data/ext/itsi_server/extconf.rb +1 -1
  24. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  25. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  26. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  27. data/ext/itsi_server/src/env.rs +43 -0
  28. data/ext/itsi_server/src/lib.rs +136 -8
  29. data/ext/itsi_server/src/request/itsi_request.rs +258 -103
  30. data/ext/itsi_server/src/response/itsi_response.rs +357 -0
  31. data/ext/itsi_server/src/response/mod.rs +1 -0
  32. data/ext/itsi_server/src/server/bind.rs +65 -29
  33. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  34. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  35. data/ext/itsi_server/src/server/itsi_server.rs +245 -139
  36. data/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
  37. data/ext/itsi_server/src/server/listener.rs +237 -137
  38. data/ext/itsi_server/src/server/mod.rs +7 -1
  39. data/ext/itsi_server/src/server/process_worker.rs +203 -0
  40. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +260 -0
  41. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  42. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +276 -0
  43. data/ext/itsi_server/src/server/signal.rs +74 -0
  44. data/ext/itsi_server/src/server/thread_worker.rs +399 -0
  45. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  46. data/ext/itsi_server/src/server/tls.rs +187 -60
  47. data/ext/itsi_tracing/Cargo.toml +4 -0
  48. data/ext/itsi_tracing/src/lib.rs +53 -6
  49. data/lib/itsi/index.html +91 -0
  50. data/lib/itsi/request.rb +38 -14
  51. data/lib/itsi/server/Itsi.rb +127 -0
  52. data/lib/itsi/server/config.rb +36 -0
  53. data/lib/itsi/server/options_dsl.rb +401 -0
  54. data/lib/itsi/server/rack/handler/itsi.rb +36 -0
  55. data/lib/itsi/server/rack_interface.rb +75 -0
  56. data/lib/itsi/server/scheduler_interface.rb +21 -0
  57. data/lib/itsi/server/scheduler_mode.rb +6 -0
  58. data/lib/itsi/server/signal_trap.rb +23 -0
  59. data/lib/itsi/server/version.rb +1 -1
  60. data/lib/itsi/server.rb +79 -9
  61. data/lib/itsi/stream_io.rb +38 -0
  62. metadata +49 -27
  63. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  64. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  65. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  66. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
@@ -1,56 +1,143 @@
1
1
  use super::{
2
2
  bind::Bind,
3
- listener::{Listener, SockAddr},
3
+ listener::Listener,
4
+ serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode},
5
+ signal::{
6
+ clear_signal_handlers, reset_signal_handlers, send_shutdown_event, SIGNAL_HANDLER_CHANNEL,
7
+ },
4
8
  };
5
- use crate::{request::itsi_request::ItsiRequest, ITSI_SERVER};
6
- use bytes::Bytes;
9
+ use crate::{request::itsi_request::ItsiRequest, server::serve_strategy::ServeStrategy};
7
10
  use derive_more::Debug;
8
- use http_body_util::{combinators::BoxBody, Empty};
9
- use hyper::{
10
- body::Incoming, header::HeaderName, service::service_fn, HeaderMap, Request, Response,
11
- StatusCode,
12
- };
13
- use hyper_util::{rt::TokioExecutor, server::conn::auto::Builder};
14
- use itsi_tracing::{error, info};
11
+ use itsi_rb_helpers::{call_without_gvl, HeapVal, HeapValue};
12
+ use itsi_tracing::{error, run_silently};
15
13
  use magnus::{
14
+ block::Proc,
16
15
  error::Result,
17
- scan_args::{get_kwargs, scan_args, Args, KwArgs},
18
- value::{Opaque, ReprValue},
19
- RHash, Ruby, Value,
16
+ scan_args::{get_kwargs, scan_args, Args, KwArgs, ScanArgsKw, ScanArgsOpt, ScanArgsRequired},
17
+ value::ReprValue,
18
+ ArgList, RArray, RHash, Ruby, Symbol, Value,
20
19
  };
21
- use parking_lot::Mutex;
22
- use std::{collections::HashMap, convert::Infallible, sync::Arc};
23
- use tokio::runtime::Builder as RuntimeBuilder;
24
- use tokio::task::JoinSet;
20
+ use parking_lot::{Mutex, RwLock};
21
+ use std::{cmp::max, collections::HashMap, ops::Deref, sync::Arc};
22
+ use tracing::{info, instrument};
23
+
24
+ static DEFAULT_BIND: &str = "http://localhost:3000";
25
25
 
26
26
  #[magnus::wrap(class = "Itsi::Server", free_immediately, size)]
27
- #[derive(Debug)]
27
+ #[derive(Clone)]
28
28
  pub struct Server {
29
+ pub config: Arc<ServerConfig>,
30
+ }
31
+
32
+ impl Deref for Server {
33
+ type Target = ServerConfig;
34
+
35
+ fn deref(&self) -> &Self::Target {
36
+ &self.config
37
+ }
38
+ }
39
+
40
+ #[derive(Debug)]
41
+ pub struct ServerConfig {
29
42
  #[debug(skip)]
30
- app: Opaque<Value>,
43
+ pub app: HeapVal,
31
44
  #[allow(unused)]
32
- workers: u16,
45
+ pub workers: u8,
33
46
  #[allow(unused)]
34
- threads: u16,
47
+ pub threads: u8,
35
48
  #[allow(unused)]
36
- shutdown_timeout: f64,
37
- script_name: String,
49
+ pub shutdown_timeout: f64,
50
+ pub script_name: String,
38
51
  pub(crate) binds: Mutex<Vec<Bind>>,
52
+ #[debug(skip)]
53
+ pub hooks: HashMap<String, HeapValue<Proc>>,
54
+ pub scheduler_class: Option<String>,
55
+ pub stream_body: Option<bool>,
56
+ pub worker_memory_limit: Option<u64>,
57
+ #[debug(skip)]
58
+ pub(crate) strategy: RwLock<Option<ServeStrategy>>,
59
+ pub silence: bool,
60
+ pub oob_gc_responses_threshold: Option<u64>,
61
+ }
62
+
63
+ #[derive(Debug)]
64
+ pub enum RequestJob {
65
+ ProcessRequest(ItsiRequest),
66
+ Shutdown,
67
+ }
68
+
69
+ fn extract_args<Req, Opt, Splat>(
70
+ scan_args: &Args<(), (), (), (), RHash, ()>,
71
+ primaries: &[&str],
72
+ rest: &[&str],
73
+ ) -> Result<KwArgs<Req, Opt, Splat>>
74
+ where
75
+ Req: ScanArgsRequired,
76
+ Opt: ScanArgsOpt,
77
+ Splat: ScanArgsKw,
78
+ {
79
+ let symbols: Vec<Symbol> = primaries
80
+ .iter()
81
+ .chain(rest.iter())
82
+ .map(|&name| Symbol::new(name))
83
+ .collect();
84
+
85
+ let hash = scan_args
86
+ .keywords
87
+ .funcall::<_, _, RHash>("slice", symbols.into_arg_list_with(&Ruby::get().unwrap()))
88
+ .unwrap();
89
+
90
+ get_kwargs(hash, primaries, rest)
39
91
  }
40
92
 
41
93
  impl Server {
94
+ #[instrument(
95
+ name = "Itsi",
96
+ parent=None,
97
+ skip(args),
98
+ fields(workers = 1, threads = 1, shutdown_timeout = 5)
99
+ )]
42
100
  pub fn new(args: &[Value]) -> Result<Self> {
43
- type OptionalArgs = (
44
- Option<u16>,
45
- Option<u16>,
46
- Option<f64>,
47
- Option<String>,
48
- Option<Vec<String>>,
49
- );
50
-
51
101
  let scan_args: Args<(), (), (), (), RHash, ()> = scan_args(args)?;
52
- let args: KwArgs<(Value,), OptionalArgs, ()> = get_kwargs(
53
- scan_args.keywords,
102
+
103
+ type Args1 = KwArgs<
104
+ (Value,),
105
+ (
106
+ // Workers
107
+ Option<u8>,
108
+ // Threads
109
+ Option<u8>,
110
+ // Shutdown Timeout
111
+ Option<f64>,
112
+ // Script Name
113
+ Option<String>,
114
+ // Binds
115
+ Option<Vec<String>>,
116
+ // Stream Body
117
+ Option<bool>,
118
+ ),
119
+ (),
120
+ >;
121
+
122
+ type Args2 = KwArgs<
123
+ (),
124
+ (
125
+ // Hooks
126
+ Option<RHash>,
127
+ // Scheduler Class
128
+ Option<String>,
129
+ // Worker Memory Limit
130
+ Option<u64>,
131
+ // Out-of-band GC Responses Threshold
132
+ Option<u64>,
133
+ // Silence
134
+ Option<bool>,
135
+ ),
136
+ (),
137
+ >;
138
+
139
+ let args1: Args1 = extract_args(
140
+ &scan_args,
54
141
  &["app"],
55
142
  &[
56
143
  "workers",
@@ -58,125 +145,144 @@ impl Server {
58
145
  "shutdown_timeout",
59
146
  "script_name",
60
147
  "binds",
148
+ "stream_body",
61
149
  ],
62
150
  )?;
63
- let server = Server {
64
- app: Opaque::from(args.required.0),
65
- workers: args.optional.0.unwrap_or(1),
66
- threads: args.optional.1.unwrap_or(1),
67
- shutdown_timeout: args.optional.2.unwrap_or(5.0),
68
- script_name: args.optional.3.unwrap_or("".to_string()),
151
+
152
+ let args2: Args2 = extract_args(
153
+ &scan_args,
154
+ &[],
155
+ &[
156
+ "hooks",
157
+ "scheduler_class",
158
+ "worker_memory_limit",
159
+ "oob_gc_responses_threshold",
160
+ "silence",
161
+ ],
162
+ )?;
163
+
164
+ let hooks = args2
165
+ .optional
166
+ .0
167
+ .map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
168
+ let mut hook_map: HashMap<String, HeapValue<Proc>> = HashMap::new();
169
+ for pair in rhash.enumeratorize::<_, ()>("each", ()) {
170
+ if let Some(pair_value) = RArray::from_value(pair?) {
171
+ if let (Ok(key), Ok(value)) =
172
+ (pair_value.entry::<Value>(0), pair_value.entry::<Proc>(1))
173
+ {
174
+ hook_map.insert(key.to_string(), HeapValue::from(value));
175
+ }
176
+ }
177
+ }
178
+ Ok(hook_map)
179
+ })
180
+ .transpose()?
181
+ .unwrap_or_default();
182
+
183
+ let config = ServerConfig {
184
+ app: HeapVal::from(args1.required.0),
185
+ workers: max(args1.optional.0.unwrap_or(1), 1),
186
+ threads: max(args1.optional.1.unwrap_or(1), 1),
187
+ shutdown_timeout: args1.optional.2.unwrap_or(5.0),
188
+ script_name: args1.optional.3.unwrap_or("".to_string()),
69
189
  binds: Mutex::new(
70
- args.optional
190
+ args1
191
+ .optional
71
192
  .4
72
- .unwrap_or_else(|| vec!["localhost:3000".to_string()])
193
+ .unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
73
194
  .into_iter()
74
- .map(|s| s.parse().unwrap_or_else(|_| Bind::default()))
75
- .collect(),
195
+ .map(|s| s.parse())
196
+ .collect::<itsi_error::Result<Vec<Bind>>>()?,
76
197
  ),
198
+ stream_body: args1.optional.5,
199
+ hooks,
200
+ scheduler_class: args2.optional.1.clone(),
201
+ worker_memory_limit: args2.optional.2,
202
+ strategy: RwLock::new(None),
203
+ oob_gc_responses_threshold: args2.optional.3,
204
+ silence: args2.optional.4.is_some_and(|s| s),
77
205
  };
78
- Ok(server)
79
- }
80
206
 
81
- pub(crate) async fn process_request(
82
- hyper_request: Request<Incoming>,
83
- app: Opaque<Value>,
84
- script_name: String,
85
- listener: Arc<Listener>,
86
- addr: SockAddr,
87
- ) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
88
- let request = ItsiRequest::build_from(hyper_request, addr, script_name, listener).await;
89
- let ruby = Ruby::get().unwrap();
90
- let server = ruby.get_inner(&ITSI_SERVER);
91
- let response: Result<(u16, HashMap<String, String>, Value)> =
92
- server.funcall("call", (app, request));
93
- if let Ok((status, headers_raw, body)) = response {
94
- let mut body_buf = vec![];
95
- for body_chunk in body.enumeratorize("each", ()) {
96
- body_buf.push(body_chunk.unwrap().to_string())
207
+ if !config.silence {
208
+ if let Some(scheduler_class) = args2.optional.1 {
209
+ info!(scheduler_class, fiber_scheduler = true);
210
+ } else {
211
+ info!(fiber_scheduler = false);
97
212
  }
98
- body.check_funcall::<_, _, Value>("close", ());
99
- let boxed_body = BoxBody::new(body_buf.join(""));
100
- let mut response = Response::new(boxed_body);
101
- let mut headers = HeaderMap::new();
102
- headers_raw.into_iter().for_each(|(key, value)| {
103
- let header_name: HeaderName = key.parse().unwrap();
104
- headers.insert(header_name, value.parse().unwrap());
105
- });
106
- *response.headers_mut() = headers;
107
- *response.status_mut() = StatusCode::from_u16(status).unwrap();
108
- Ok(response)
213
+ }
214
+
215
+ Ok(Server {
216
+ config: Arc::new(config),
217
+ })
218
+ }
219
+
220
+ #[instrument(name = "Bind", skip_all, fields(binds=format!("{:?}", self.config.binds.lock())))]
221
+ pub(crate) fn build_listeners(&self) -> Result<Vec<Listener>> {
222
+ let listeners = self
223
+ .config
224
+ .binds
225
+ .lock()
226
+ .iter()
227
+ .cloned()
228
+ .map(Listener::try_from)
229
+ .collect::<std::result::Result<Vec<Listener>, _>>()?
230
+ .into_iter()
231
+ .collect::<Vec<_>>();
232
+ info!("Bound {:?} listeners", listeners.len());
233
+ Ok(listeners)
234
+ }
235
+
236
+ pub(crate) fn build_strategy(self) -> Result<()> {
237
+ let listeners = self.build_listeners()?;
238
+ let server = Arc::new(self);
239
+ let server_clone = server.clone();
240
+
241
+ let strategy = if server.config.workers == 1 {
242
+ ServeStrategy::Single(Arc::new(SingleMode::new(
243
+ server,
244
+ listeners,
245
+ SIGNAL_HANDLER_CHANNEL.0.clone(),
246
+ )?))
247
+ } else {
248
+ ServeStrategy::Cluster(Arc::new(ClusterMode::new(
249
+ server,
250
+ listeners,
251
+ SIGNAL_HANDLER_CHANNEL.0.clone(),
252
+ )))
253
+ };
254
+
255
+ *server_clone.strategy.write() = Some(strategy);
256
+ Ok(())
257
+ }
258
+
259
+ pub fn stop(&self) -> Result<()> {
260
+ send_shutdown_event();
261
+ Ok(())
262
+ }
263
+
264
+ pub fn start(&self) -> Result<()> {
265
+ if self.silence {
266
+ run_silently(|| self.build_and_run_strategy())
109
267
  } else {
110
- let mut response = Response::new(BoxBody::new(Empty::new()));
111
- *response.status_mut() = StatusCode::BAD_REQUEST;
112
- Ok(response)
268
+ self.build_and_run_strategy()
113
269
  }
114
270
  }
115
271
 
116
- pub fn start(&self) {
117
- let mut builder: RuntimeBuilder = RuntimeBuilder::new_current_thread();
118
- let runtime = builder
119
- .thread_name("itsi-server-accept-loop")
120
- .thread_stack_size(3 * 1024 * 1024)
121
- .enable_io()
122
- .enable_time()
123
- .build()
124
- .expect("Failed to build Tokio runtime");
125
-
126
- runtime.block_on(async {
127
- let server = Arc::new(Builder::new(TokioExecutor::new()));
128
- let listeners: Vec<Listener> = self
129
- .binds
130
- .lock()
131
- .iter()
132
- .cloned()
133
- .map(Listener::from)
134
- .collect::<Vec<_>>();
135
-
136
- let mut set = JoinSet::new();
137
-
138
- for listener in listeners {
139
- let app = self.app;
140
- let server_clone = server.clone();
141
- let listener_clone = Arc::new(listener);
142
- let script_name = self.script_name.clone();
143
-
144
- set.spawn(async move {
145
- loop {
146
- let server = server_clone.clone();
147
- let listener = listener_clone.clone();
148
- let script_name = script_name.clone();
149
- let (stream, addr) = match listener.accept().await {
150
- Ok(stream) => stream,
151
- Err(e) => {
152
- error!("Failed to accept connection: {:?}", e);
153
- continue;
154
- }
155
- };
156
-
157
- tokio::spawn(async move {
158
- if let Err(e) = server
159
- .serve_connection_with_upgrades(
160
- stream,
161
- service_fn(move |hyper_request: Request<Incoming>| {
162
- Server::process_request(
163
- hyper_request,
164
- app,
165
- script_name.clone(),
166
- listener.clone(),
167
- addr.clone(),
168
- )
169
- }),
170
- )
171
- .await
172
- {
173
- info!("Closed connection due to: {:?}", e);
174
- }
175
- });
176
- }
177
- });
272
+ fn build_and_run_strategy(&self) -> Result<()> {
273
+ reset_signal_handlers();
274
+ let rself = self.clone();
275
+ call_without_gvl(move || -> Result<()> {
276
+ rself.clone().build_strategy()?;
277
+ if let Err(e) = rself.strategy.read().as_ref().unwrap().run() {
278
+ error!("Error running server: {}", e);
279
+ rself.strategy.read().as_ref().unwrap().stop()?;
178
280
  }
179
- while let Some(_res) = set.join_next().await {}
180
- })
281
+ Ok(())
282
+ })?;
283
+ clear_signal_handlers();
284
+ self.strategy.write().take();
285
+ info!("Server stopped");
286
+ Ok(())
181
287
  }
182
288
  }
@@ -0,0 +1,9 @@
1
+ #[derive(Debug, Clone)]
2
+ pub enum LifecycleEvent {
3
+ Start,
4
+ Shutdown,
5
+ Restart,
6
+ IncreaseWorkers,
7
+ DecreaseWorkers,
8
+ ForceShutdown,
9
+ }