itsi-scheduler 0.1.0 → 0.1.2

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +219 -23
  3. data/Rakefile +7 -1
  4. data/ext/itsi_error/Cargo.toml +2 -0
  5. data/ext/itsi_error/src/from.rs +70 -0
  6. data/ext/itsi_error/src/lib.rs +10 -37
  7. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  8. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  9. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  10. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  11. data/ext/itsi_rb_helpers/src/lib.rs +90 -10
  12. data/ext/itsi_scheduler/Cargo.toml +9 -1
  13. data/ext/itsi_scheduler/extconf.rb +1 -1
  14. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  15. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  17. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  18. data/ext/itsi_scheduler/src/lib.rs +31 -10
  19. data/ext/itsi_server/Cargo.toml +41 -0
  20. data/ext/itsi_server/extconf.rb +6 -0
  21. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  22. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  23. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  24. data/ext/itsi_server/src/lib.rs +103 -0
  25. data/ext/itsi_server/src/request/itsi_request.rs +277 -0
  26. data/ext/itsi_server/src/request/mod.rs +1 -0
  27. data/ext/itsi_server/src/response/itsi_response.rs +347 -0
  28. data/ext/itsi_server/src/response/mod.rs +1 -0
  29. data/ext/itsi_server/src/server/bind.rs +168 -0
  30. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  31. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  32. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +13 -0
  33. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +5 -0
  34. data/ext/itsi_server/src/server/itsi_server.rs +230 -0
  35. data/ext/itsi_server/src/server/lifecycle_event.rs +8 -0
  36. data/ext/itsi_server/src/server/listener.rs +259 -0
  37. data/ext/itsi_server/src/server/mod.rs +11 -0
  38. data/ext/itsi_server/src/server/process_worker.rs +196 -0
  39. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +253 -0
  40. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  41. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +238 -0
  42. data/ext/itsi_server/src/server/signal.rs +57 -0
  43. data/ext/itsi_server/src/server/thread_worker.rs +368 -0
  44. data/ext/itsi_server/src/server/tls.rs +152 -0
  45. data/ext/itsi_tracing/Cargo.toml +4 -0
  46. data/ext/itsi_tracing/src/lib.rs +36 -6
  47. data/lib/itsi/scheduler/version.rb +1 -1
  48. data/lib/itsi/scheduler.rb +137 -1
  49. metadata +38 -4
@@ -0,0 +1,103 @@
1
+ use body_proxy::itsi_body_proxy::ItsiBodyProxy;
2
+ use magnus::{error::Result, function, method, value::Lazy, Module, Object, RClass, RModule, Ruby};
3
+ use request::itsi_request::ItsiRequest;
4
+ use response::itsi_response::ItsiResponse;
5
+ use server::{itsi_server::Server, signal::reset_signal_handlers};
6
+ use tracing::*;
7
+
8
+ pub mod body_proxy;
9
+ pub mod request;
10
+ pub mod response;
11
+ pub mod server;
12
+
13
+ pub static ITSI_MODULE: Lazy<RModule> = Lazy::new(|ruby| ruby.define_module("Itsi").unwrap());
14
+ pub static ITSI_SERVER: Lazy<RClass> = Lazy::new(|ruby| {
15
+ ruby.get_inner(&ITSI_MODULE)
16
+ .define_class("Server", ruby.class_object())
17
+ .unwrap()
18
+ });
19
+ pub static ITSI_REQUEST: Lazy<RClass> = Lazy::new(|ruby| {
20
+ ruby.get_inner(&ITSI_MODULE)
21
+ .define_class("Request", ruby.class_object())
22
+ .unwrap()
23
+ });
24
+
25
+ pub static ITSI_RESPONSE: Lazy<RClass> = Lazy::new(|ruby| {
26
+ ruby.get_inner(&ITSI_MODULE)
27
+ .define_class("Response", ruby.class_object())
28
+ .unwrap()
29
+ });
30
+
31
+ pub static ITSI_BODY_PROXY: Lazy<RClass> = Lazy::new(|ruby| {
32
+ ruby.get_inner(&ITSI_MODULE)
33
+ .define_class("BodyProxy", ruby.class_object())
34
+ .unwrap()
35
+ });
36
+
37
+ pub static ITSI_SERVER_SCHEDULER_TASK: Lazy<RClass> = Lazy::new(|ruby| {
38
+ ruby.get_inner(&ITSI_MODULE)
39
+ .define_class("ServerSchedulerTask", ruby.class_object())
40
+ .unwrap()
41
+ });
42
+
43
+ pub fn log_debug(msg: String) {
44
+ debug!(msg);
45
+ }
46
+ pub fn log_info(msg: String) {
47
+ info!(msg);
48
+ }
49
+ pub fn log_warn(msg: String) {
50
+ warn!(msg);
51
+ }
52
+ pub fn log_error(msg: String) {
53
+ error!(msg);
54
+ }
55
+
56
+ #[magnus::init]
57
+ fn init(ruby: &Ruby) -> Result<()> {
58
+ itsi_tracing::init();
59
+
60
+ let itsi = ruby.get_inner(&ITSI_MODULE);
61
+ itsi.define_singleton_method("log_debug", function!(log_debug, 1))?;
62
+ itsi.define_singleton_method("log_info", function!(log_info, 1))?;
63
+ itsi.define_singleton_method("log_warn", function!(log_warn, 1))?;
64
+ itsi.define_singleton_method("log_error", function!(log_error, 1))?;
65
+
66
+ let server = ruby.get_inner(&ITSI_SERVER);
67
+ server.define_singleton_method("new", function!(Server::new, -1))?;
68
+ server.define_singleton_method("reset_signal_handlers", function!(reset_signal_handlers, 0))?;
69
+ server.define_method("start", method!(Server::start, 0))?;
70
+
71
+ let request = ruby.get_inner(&ITSI_REQUEST);
72
+ request.define_method("path", method!(ItsiRequest::path, 0))?;
73
+ request.define_method("script_name", method!(ItsiRequest::script_name, 0))?;
74
+ request.define_method("query_string", method!(ItsiRequest::query_string, 0))?;
75
+ request.define_method("method", method!(ItsiRequest::method, 0))?;
76
+ request.define_method("version", method!(ItsiRequest::version, 0))?;
77
+ request.define_method("rack_protocol", method!(ItsiRequest::rack_protocol, 0))?;
78
+ request.define_method("host", method!(ItsiRequest::host, 0))?;
79
+ request.define_method("headers", method!(ItsiRequest::headers, 0))?;
80
+ request.define_method("scheme", method!(ItsiRequest::scheme, 0))?;
81
+ request.define_method("remote_addr", method!(ItsiRequest::remote_addr, 0))?;
82
+ request.define_method("port", method!(ItsiRequest::port, 0))?;
83
+ request.define_method("body", method!(ItsiRequest::body, 0))?;
84
+ request.define_method("response", method!(ItsiRequest::response, 0))?;
85
+
86
+ let body_proxy = ruby.get_inner(&ITSI_BODY_PROXY);
87
+ body_proxy.define_method("gets", method!(ItsiBodyProxy::gets, 0))?;
88
+ body_proxy.define_method("each", method!(ItsiBodyProxy::each, 0))?;
89
+ body_proxy.define_method("read", method!(ItsiBodyProxy::read, -1))?;
90
+ body_proxy.define_method("close", method!(ItsiBodyProxy::close, 0))?;
91
+
92
+ let response = ruby.get_inner(&ITSI_RESPONSE);
93
+ response.define_method("add_header", method!(ItsiResponse::add_header, 2))?;
94
+ response.define_method("status=", method!(ItsiResponse::set_status, 1))?;
95
+ response.define_method("send_frame", method!(ItsiResponse::send_frame, 1))?;
96
+ response.define_method("send_and_close", method!(ItsiResponse::send_and_close, 1))?;
97
+ response.define_method("close_write", method!(ItsiResponse::close_write, 0))?;
98
+ response.define_method("close_read", method!(ItsiResponse::close_read, 0))?;
99
+ response.define_method("close", method!(ItsiResponse::close, 0))?;
100
+ response.define_method("hijack", method!(ItsiResponse::hijack, 1))?;
101
+
102
+ Ok(())
103
+ }
@@ -0,0 +1,277 @@
1
+ use crate::{
2
+ body_proxy::{
3
+ big_bytes::BigBytes,
4
+ itsi_body_proxy::{ItsiBody, ItsiBodyProxy},
5
+ },
6
+ response::itsi_response::ItsiResponse,
7
+ server::{
8
+ itsi_server::{RequestJob, Server},
9
+ listener::{SockAddr, TokioListener},
10
+ serve_strategy::single_mode::RunningPhase,
11
+ },
12
+ };
13
+ use bytes::Bytes;
14
+ use derive_more::Debug;
15
+ use futures::StreamExt;
16
+ use http::{request::Parts, Response, StatusCode};
17
+ use http_body_util::{combinators::BoxBody, BodyExt, Empty};
18
+ use hyper::{body::Incoming, Request};
19
+ use itsi_error::from::CLIENT_CONNECTION_CLOSED;
20
+ use itsi_tracing::{debug, error};
21
+ use magnus::{
22
+ error::{ErrorType, Result as MagnusResult},
23
+ Error,
24
+ };
25
+ use magnus::{
26
+ value::{LazyId, Opaque, ReprValue},
27
+ RClass, Ruby, Value,
28
+ };
29
+ use std::{convert::Infallible, fmt, io::Write, sync::Arc, time::Instant};
30
+ use tokio::sync::{
31
+ mpsc::{self},
32
+ watch,
33
+ };
34
+ static ID_CALL: LazyId = LazyId::new("call");
35
+ static ID_MESSAGE: LazyId = LazyId::new("message");
36
+ static ID_BACKTRACE: LazyId = LazyId::new("backtrace");
37
+
38
+ #[derive(Debug)]
39
+ #[magnus::wrap(class = "Itsi::Request", free_immediately, size)]
40
+ pub struct ItsiRequest {
41
+ pub parts: Parts,
42
+ #[debug(skip)]
43
+ pub body: ItsiBody,
44
+ pub remote_addr: String,
45
+ pub version: String,
46
+ #[debug(skip)]
47
+ pub(crate) listener: Arc<TokioListener>,
48
+ #[debug(skip)]
49
+ pub server: Arc<Server>,
50
+ pub response: ItsiResponse,
51
+ pub start: Instant,
52
+ }
53
+
54
+ impl fmt::Display for ItsiRequest {
55
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56
+ write!(
57
+ f,
58
+ "{} {} {}",
59
+ self.version().unwrap(),
60
+ self.method().unwrap(),
61
+ self.path().unwrap()
62
+ )
63
+ }
64
+ }
65
+
66
+ impl ItsiRequest {
67
+ pub fn is_connection_closed_err(ruby: &Ruby, err: &Error) -> bool {
68
+ match err.error_type() {
69
+ ErrorType::Jump(_) => false,
70
+ ErrorType::Error(_, _) => false,
71
+ ErrorType::Exception(exception) => {
72
+ exception.is_kind_of(ruby.exception_eof_error())
73
+ && err
74
+ .value()
75
+ .map(|v| {
76
+ v.funcall::<_, _, String>(*ID_MESSAGE, ())
77
+ .unwrap_or("".to_string())
78
+ .eq(CLIENT_CONNECTION_CLOSED)
79
+ })
80
+ .unwrap_or(false)
81
+ }
82
+ }
83
+ }
84
+
85
+ pub fn process(
86
+ self,
87
+ ruby: &Ruby,
88
+ server: RClass,
89
+ app: Opaque<Value>,
90
+ ) -> magnus::error::Result<()> {
91
+ let req = format!("{}", self);
92
+ let response = self.response.clone();
93
+ let start = self.start;
94
+ debug!("{} Started", req);
95
+ let result = server.funcall::<_, _, Value>(*ID_CALL, (app, self));
96
+ if let Err(err) = result {
97
+ Self::internal_error(ruby, response, err);
98
+ }
99
+ debug!("{} Finished in {:?}", req, start.elapsed());
100
+
101
+ Ok(())
102
+ }
103
+
104
+ pub fn internal_error(ruby: &Ruby, response: ItsiResponse, err: Error) {
105
+ if Self::is_connection_closed_err(ruby, &err) {
106
+ debug!("Connection closed by client");
107
+ response.close();
108
+ } else if let Some(rb_err) = err.value() {
109
+ let backtrace = rb_err
110
+ .funcall::<_, _, Vec<String>>(*ID_BACKTRACE, ())
111
+ .unwrap_or_default();
112
+
113
+ error!("Error occurred in Handler: {:?}", rb_err);
114
+ for line in backtrace {
115
+ error!("{}", line);
116
+ }
117
+ response.internal_server_error(err.to_string());
118
+ } else {
119
+ response.internal_server_error(err.to_string());
120
+ }
121
+ }
122
+
123
+ pub fn error(self, message: String) {
124
+ self.response.internal_server_error(message);
125
+ }
126
+
127
+ pub(crate) async fn process_request(
128
+ hyper_request: Request<Incoming>,
129
+ sender: async_channel::Sender<RequestJob>,
130
+ server: Arc<Server>,
131
+ listener: Arc<TokioListener>,
132
+ addr: SockAddr,
133
+ shutdown_rx: watch::Receiver<RunningPhase>,
134
+ ) -> itsi_error::Result<Response<BoxBody<Bytes, Infallible>>> {
135
+ let (request, mut receiver) = ItsiRequest::new(hyper_request, addr, server, listener).await;
136
+
137
+ let response = request.response.clone();
138
+ match sender.send(RequestJob::ProcessRequest(request)).await {
139
+ Err(err) => {
140
+ error!("Error occurred: {}", err);
141
+ let mut response = Response::new(BoxBody::new(Empty::new()));
142
+ *response.status_mut() = StatusCode::BAD_REQUEST;
143
+ Ok(response)
144
+ }
145
+ _ => match receiver.recv().await {
146
+ Some(first_frame) => Ok(response.build(first_frame, receiver, shutdown_rx).await),
147
+ None => Ok(response.build(None, receiver, shutdown_rx).await),
148
+ },
149
+ }
150
+ }
151
+
152
+ pub(crate) async fn new(
153
+ request: Request<Incoming>,
154
+ sock_addr: SockAddr,
155
+ server: Arc<Server>,
156
+ listener: Arc<TokioListener>,
157
+ ) -> (ItsiRequest, mpsc::Receiver<Option<Bytes>>) {
158
+ let (parts, body) = request.into_parts();
159
+ let body = if server.stream_body.is_some_and(|f| f) {
160
+ ItsiBody::Stream(ItsiBodyProxy::new(body))
161
+ } else {
162
+ let mut body_bytes = BigBytes::new();
163
+ let mut stream = body.into_data_stream();
164
+ while let Some(chunk) = stream.next().await {
165
+ let byte_array = chunk.unwrap().to_vec();
166
+ body_bytes.write_all(&byte_array).unwrap();
167
+ }
168
+ ItsiBody::Buffered(body_bytes)
169
+ };
170
+ let response_channel = mpsc::channel::<Option<Bytes>>(100);
171
+ (
172
+ Self {
173
+ remote_addr: sock_addr.to_string(),
174
+ body,
175
+ server,
176
+ listener,
177
+ version: format!("{:?}", &parts.version),
178
+ response: ItsiResponse::new(parts.clone(), response_channel.0),
179
+ start: Instant::now(),
180
+ parts,
181
+ },
182
+ response_channel.1,
183
+ )
184
+ }
185
+
186
+ pub(crate) fn path(&self) -> MagnusResult<&str> {
187
+ Ok(self
188
+ .parts
189
+ .uri
190
+ .path()
191
+ .strip_prefix(&self.server.script_name)
192
+ .unwrap_or(self.parts.uri.path()))
193
+ }
194
+
195
+ pub(crate) fn script_name(&self) -> MagnusResult<&str> {
196
+ Ok(&self.server.script_name)
197
+ }
198
+
199
+ pub(crate) fn query_string(&self) -> MagnusResult<&str> {
200
+ Ok(self.parts.uri.query().unwrap_or(""))
201
+ }
202
+
203
+ pub(crate) fn method(&self) -> MagnusResult<&str> {
204
+ Ok(self.parts.method.as_str())
205
+ }
206
+
207
+ pub(crate) fn version(&self) -> MagnusResult<&str> {
208
+ Ok(&self.version)
209
+ }
210
+
211
+ pub(crate) fn rack_protocol(&self) -> MagnusResult<Vec<&str>> {
212
+ Ok(self
213
+ .parts
214
+ .headers
215
+ .get("upgrade")
216
+ .or_else(|| self.parts.headers.get("protocol"))
217
+ .map(|value| {
218
+ value
219
+ .to_str()
220
+ .unwrap_or("")
221
+ .split(',')
222
+ .map(|s| s.trim())
223
+ .collect::<Vec<&str>>()
224
+ })
225
+ .unwrap_or_else(|| vec!["http"]))
226
+ }
227
+
228
+ pub(crate) fn host(&self) -> MagnusResult<String> {
229
+ Ok(self
230
+ .parts
231
+ .uri
232
+ .host()
233
+ .map(|host| host.to_string())
234
+ .unwrap_or_else(|| self.listener.host()))
235
+ }
236
+
237
+ pub(crate) fn scheme(&self) -> MagnusResult<String> {
238
+ Ok(self
239
+ .parts
240
+ .uri
241
+ .scheme()
242
+ .map(|scheme| scheme.to_string())
243
+ .unwrap_or_else(|| self.listener.scheme()))
244
+ }
245
+
246
+ pub(crate) fn headers(&self) -> MagnusResult<Vec<(String, &str)>> {
247
+ Ok(self
248
+ .parts
249
+ .headers
250
+ .iter()
251
+ .map(|(hn, hv)| {
252
+ let key = match hn.as_str() {
253
+ "content-length" => "CONTENT_LENGTH".to_string(),
254
+ "content-type" => "CONTENT_TYPE".to_string(),
255
+ _ => format!("HTTP_{}", hn.as_str().to_uppercase().replace("-", "_")),
256
+ };
257
+ (key, hv.to_str().unwrap_or(""))
258
+ })
259
+ .collect())
260
+ }
261
+
262
+ pub(crate) fn remote_addr(&self) -> MagnusResult<&str> {
263
+ Ok(&self.remote_addr)
264
+ }
265
+
266
+ pub(crate) fn port(&self) -> MagnusResult<u16> {
267
+ Ok(self.parts.uri.port_u16().unwrap_or(self.listener.port()))
268
+ }
269
+
270
+ pub(crate) fn body(&self) -> MagnusResult<Value> {
271
+ Ok(self.body.into_value())
272
+ }
273
+
274
+ pub(crate) fn response(&self) -> MagnusResult<ItsiResponse> {
275
+ Ok(self.response.clone())
276
+ }
277
+ }
@@ -0,0 +1 @@
1
+ pub mod itsi_request;