itsi-scheduler 0.2.26 → 0.2.27
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/Cargo.lock +3 -3
- data/Rakefile +33 -9
- data/ext/itsi_acme/Cargo.toml +2 -1
- data/ext/itsi_acme/src/acceptor.rs +1 -1
- data/ext/itsi_acme/src/acme.rs +31 -3
- data/ext/itsi_acme/src/http_challenge.rs +81 -0
- data/ext/itsi_acme/src/https_helper.rs +3 -1
- data/ext/itsi_acme/src/jose.rs +6 -2
- data/ext/itsi_acme/src/lib.rs +2 -0
- data/ext/itsi_acme/src/resolver.rs +27 -4
- data/ext/itsi_acme/src/state.rs +183 -22
- data/ext/itsi_scheduler/Cargo.toml +1 -1
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +115 -64
- data/ext/itsi_scheduler/src/lib.rs +2 -1
- data/ext/itsi_server/Cargo.lock +2 -2
- data/ext/itsi_server/Cargo.toml +2 -1
- data/ext/itsi_server/src/lib.rs +15 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +0 -1
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +9 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +114 -4
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +22 -1
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +100 -0
- data/ext/itsi_server/src/server/binds/listener.rs +9 -24
- data/ext/itsi_server/src/server/binds/tls.rs +372 -67
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +3 -9
- data/ext/itsi_server/src/server/signal.rs +14 -9
- data/ext/itsi_server/src/services/itsi_http_service.rs +51 -10
- data/lib/itsi/scheduler/version.rb +1 -1
- data/lib/itsi/scheduler.rb +121 -6
- metadata +3 -2
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
use crate::default_responses::{NOT_FOUND_RESPONSE, TIMEOUT_RESPONSE};
|
|
2
2
|
use crate::ruby_types::itsi_server::itsi_server_config::ItsiServerTokenPreference;
|
|
3
3
|
use crate::server::http_message_types::{
|
|
4
|
-
ConversionExt, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
|
|
4
|
+
ConversionExt, HttpBody, HttpRequest, HttpResponse, RequestExt, ResponseFormat,
|
|
5
5
|
};
|
|
6
6
|
use crate::server::lifecycle_event::LifecycleEvent;
|
|
7
7
|
use crate::server::middleware_stack::MiddlewareLayer;
|
|
8
8
|
use crate::server::serve_strategy::acceptor::AcceptorArgs;
|
|
9
9
|
use crate::server::signal::{send_lifecycle_event, SHUTDOWN_REQUESTED};
|
|
10
|
+
use bytes::Bytes;
|
|
10
11
|
use chrono::{self, DateTime, Local};
|
|
11
12
|
use either::Either;
|
|
12
13
|
use http::header::ACCEPT_ENCODING;
|
|
13
|
-
use http::{HeaderValue, Request};
|
|
14
|
+
use http::{HeaderValue, Request, StatusCode};
|
|
14
15
|
use hyper::body::Incoming;
|
|
15
16
|
use regex::Regex;
|
|
16
17
|
use smallvec::SmallVec;
|
|
@@ -174,6 +175,7 @@ impl HttpRequestContext {
|
|
|
174
175
|
const SERVER_TOKEN_VERSION: HeaderValue =
|
|
175
176
|
HeaderValue::from_static(concat!("Itsi/", env!("CARGO_PKG_VERSION")));
|
|
176
177
|
const SERVER_TOKEN_NAME: HeaderValue = HeaderValue::from_static("Itsi");
|
|
178
|
+
const TEXT_PLAIN_UTF8: HeaderValue = HeaderValue::from_static("text/plain; charset=utf-8");
|
|
177
179
|
|
|
178
180
|
impl ItsiHttpService {
|
|
179
181
|
pub async fn handle_request(&self, req: Request<Incoming>) -> itsi_error::Result<HttpResponse> {
|
|
@@ -188,14 +190,15 @@ impl ItsiHttpService {
|
|
|
188
190
|
let token_preference = self.server_params.itsi_server_token_preference;
|
|
189
191
|
|
|
190
192
|
let service_future = async move {
|
|
191
|
-
let
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
.stack_for(&req)
|
|
197
|
-
|
|
198
|
-
|
|
193
|
+
if let Some(acme_response) = self.acme_http01_response(&req) {
|
|
194
|
+
return Ok(acme_response);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let Some((stack, matching_pattern)) =
|
|
198
|
+
self.server_params.middleware.get().unwrap().stack_for(&req)
|
|
199
|
+
else {
|
|
200
|
+
return Ok(NOT_FOUND_RESPONSE.to_http_response(accept).await);
|
|
201
|
+
};
|
|
199
202
|
let mut resp: Option<HttpResponse> = None;
|
|
200
203
|
|
|
201
204
|
let mut context =
|
|
@@ -267,4 +270,42 @@ impl ItsiHttpService {
|
|
|
267
270
|
service_future.await
|
|
268
271
|
}
|
|
269
272
|
}
|
|
273
|
+
|
|
274
|
+
fn acme_http01_response(&self, req: &HttpRequest) -> Option<HttpResponse> {
|
|
275
|
+
let host = normalize_host_header(req.header("host")?)?;
|
|
276
|
+
let managers = self.server_params.acme_managers.read();
|
|
277
|
+
let key_authorization = managers
|
|
278
|
+
.iter()
|
|
279
|
+
.find_map(|(_, manager)| manager.http01_response(host, req.uri().path()))?;
|
|
280
|
+
|
|
281
|
+
let mut builder = http::Response::builder()
|
|
282
|
+
.status(StatusCode::OK)
|
|
283
|
+
.header(http::header::CONTENT_TYPE, TEXT_PLAIN_UTF8);
|
|
284
|
+
|
|
285
|
+
if req.method() == http::Method::HEAD {
|
|
286
|
+
builder = builder.header(http::header::CONTENT_LENGTH, "0");
|
|
287
|
+
return builder.body(HttpBody::empty()).ok();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
builder
|
|
291
|
+
.header(
|
|
292
|
+
http::header::CONTENT_LENGTH,
|
|
293
|
+
key_authorization.len().to_string(),
|
|
294
|
+
)
|
|
295
|
+
.body(HttpBody::full(Bytes::from(key_authorization)))
|
|
296
|
+
.ok()
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fn normalize_host_header(host: &str) -> Option<&str> {
|
|
301
|
+
let host = host.trim();
|
|
302
|
+
if host.is_empty() {
|
|
303
|
+
return None;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if let Some(stripped) = host.strip_prefix('[') {
|
|
307
|
+
return stripped.split(']').next();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
Some(host.split(':').next().unwrap_or(host))
|
|
270
311
|
}
|
data/lib/itsi/scheduler.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "etc"
|
|
4
|
+
|
|
3
5
|
require_relative "scheduler/version"
|
|
4
6
|
require_relative "scheduler/native_extension"
|
|
5
7
|
require_relative "schedule_refinement"
|
|
@@ -7,6 +9,7 @@ require_relative "schedule_refinement"
|
|
|
7
9
|
module Itsi
|
|
8
10
|
class Scheduler
|
|
9
11
|
class Error < StandardError; end
|
|
12
|
+
WorkRequest = Struct.new(:fiber, :work, :result, :error, keyword_init: true)
|
|
10
13
|
|
|
11
14
|
def self.resume_token
|
|
12
15
|
@resume_token ||= 0
|
|
@@ -17,12 +20,14 @@ module Itsi
|
|
|
17
20
|
@join_waiters = {}.compare_by_identity
|
|
18
21
|
@token_map = {}.compare_by_identity
|
|
19
22
|
@resume_tokens = {}.compare_by_identity
|
|
23
|
+
@timeout_requests = {}
|
|
20
24
|
@unblocked = [[], []]
|
|
21
25
|
@unblock_idx = 0
|
|
22
26
|
@unblocked_mux = Mutex.new
|
|
23
27
|
@resume_fiber = method(:resume_fiber).to_proc
|
|
24
28
|
@resume_fiber_with_readiness = method(:resume_fiber_with_readiness).to_proc
|
|
25
29
|
@resume_blocked = method(:resume_blocked).to_proc
|
|
30
|
+
setup_worker_pool
|
|
26
31
|
end
|
|
27
32
|
|
|
28
33
|
def block(_, timeout, fiber = Fiber.current, token = Scheduler.resume_token)
|
|
@@ -33,6 +38,7 @@ module Itsi
|
|
|
33
38
|
@token_map[fiber] = token
|
|
34
39
|
Fiber.yield
|
|
35
40
|
ensure
|
|
41
|
+
cancel_wait(token)
|
|
36
42
|
@resume_tokens.delete(token)
|
|
37
43
|
@token_map.delete(fiber)
|
|
38
44
|
@join_waiters.delete(fiber)
|
|
@@ -61,6 +67,60 @@ module Itsi
|
|
|
61
67
|
block nil, duration
|
|
62
68
|
end
|
|
63
69
|
|
|
70
|
+
def timeout_after(duration, klass = Timeout::Error, message = "execution expired")
|
|
71
|
+
fiber = Fiber.current
|
|
72
|
+
token = Scheduler.resume_token
|
|
73
|
+
exception = klass.is_a?(Class) ? klass.new(message) : klass
|
|
74
|
+
@timeout_requests[token] = [fiber, exception]
|
|
75
|
+
start_timer(duration, token)
|
|
76
|
+
yield duration
|
|
77
|
+
ensure
|
|
78
|
+
clear_timer(token) if token
|
|
79
|
+
@timeout_requests.delete(token) if token
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fiber_interrupt(fiber, exception)
|
|
83
|
+
cancel_wait(@token_map[fiber]) if @token_map.key?(fiber)
|
|
84
|
+
fiber.raise(exception)
|
|
85
|
+
true
|
|
86
|
+
rescue FiberError
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def blocking_operation_wait(work)
|
|
91
|
+
request = WorkRequest.new(fiber: Fiber.current, work: work)
|
|
92
|
+
@worker_queue << request
|
|
93
|
+
block(nil, nil, request.fiber)
|
|
94
|
+
raise request.error if request.error
|
|
95
|
+
|
|
96
|
+
request.result
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def io_select(readables, writables, exceptables, timeout)
|
|
100
|
+
readables = Array(readables).compact
|
|
101
|
+
writables = Array(writables).compact
|
|
102
|
+
exceptables = Array(exceptables).compact
|
|
103
|
+
ios = (readables + writables + exceptables).uniq
|
|
104
|
+
|
|
105
|
+
if ios.length == 1
|
|
106
|
+
io = ios.first
|
|
107
|
+
events = 0
|
|
108
|
+
events |= IO::READABLE if readables.include?(io)
|
|
109
|
+
events |= IO::WRITABLE if writables.include?(io)
|
|
110
|
+
events |= IO::PRIORITY if exceptables.include?(io)
|
|
111
|
+
readiness = io_wait(io, events, timeout)
|
|
112
|
+
return nil unless readiness
|
|
113
|
+
|
|
114
|
+
return [
|
|
115
|
+
(readiness & IO::READABLE).zero? ? [] : readables.select { |entry| entry == io },
|
|
116
|
+
(readiness & IO::WRITABLE).zero? ? [] : writables.select { |entry| entry == io },
|
|
117
|
+
(readiness & IO::PRIORITY).zero? ? [] : exceptables.select { |entry| entry == io }
|
|
118
|
+
]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
blocking_operation_wait(-> { IO.select(readables, writables, exceptables, timeout) })
|
|
122
|
+
end
|
|
123
|
+
|
|
64
124
|
def tick
|
|
65
125
|
events = fetch_due_events
|
|
66
126
|
timers = fetch_due_timers
|
|
@@ -72,6 +132,12 @@ module Itsi
|
|
|
72
132
|
end
|
|
73
133
|
|
|
74
134
|
def resume_fiber(token)
|
|
135
|
+
if (request = @timeout_requests.delete(token))
|
|
136
|
+
fiber, exception = request
|
|
137
|
+
fiber_interrupt(fiber, exception)
|
|
138
|
+
return
|
|
139
|
+
end
|
|
140
|
+
|
|
75
141
|
if (fiber = @resume_tokens.delete(token))
|
|
76
142
|
fiber.resume
|
|
77
143
|
end
|
|
@@ -126,6 +192,7 @@ module Itsi
|
|
|
126
192
|
def close
|
|
127
193
|
run
|
|
128
194
|
ensure
|
|
195
|
+
shutdown_worker_pool
|
|
129
196
|
@closed ||= true
|
|
130
197
|
freeze
|
|
131
198
|
end
|
|
@@ -133,12 +200,17 @@ module Itsi
|
|
|
133
200
|
# Need to defer to Process::Status rather than our extension
|
|
134
201
|
# as we don't have a means of creating our own Process::Status.
|
|
135
202
|
def process_wait(pid, flags)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
203
|
+
blocking_operation_wait(-> { Process::Status.wait(pid, flags) })
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def address_resolve(hostname)
|
|
207
|
+
blocking_operation_wait(-> { native_address_resolve(hostname) })
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def process_fork
|
|
211
|
+
shutdown_worker_pool
|
|
212
|
+
setup_worker_pool
|
|
213
|
+
nil
|
|
142
214
|
end
|
|
143
215
|
|
|
144
216
|
def closed?
|
|
@@ -149,5 +221,48 @@ module Itsi
|
|
|
149
221
|
def fiber(&blk)
|
|
150
222
|
Fiber.new(blocking: false, &blk).tap(&:resume)
|
|
151
223
|
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
def setup_worker_pool
|
|
228
|
+
@worker_stop_token = Object.new
|
|
229
|
+
@worker_queue = Queue.new
|
|
230
|
+
@worker_threads = Array.new(worker_pool_size) { start_worker_thread }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def start_worker_thread
|
|
234
|
+
Thread.new do
|
|
235
|
+
Thread.current.report_on_exception = false
|
|
236
|
+
Thread.current.thread_variable_set(:fork_safe, true)
|
|
237
|
+
|
|
238
|
+
loop do
|
|
239
|
+
request = @worker_queue.pop
|
|
240
|
+
break if request.equal?(@worker_stop_token)
|
|
241
|
+
|
|
242
|
+
begin
|
|
243
|
+
request.result = request.work.call
|
|
244
|
+
rescue Exception => exception
|
|
245
|
+
request.error = exception
|
|
246
|
+
ensure
|
|
247
|
+
unblock(nil, request.fiber)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def shutdown_worker_pool
|
|
254
|
+
return unless @worker_threads
|
|
255
|
+
|
|
256
|
+
@worker_threads.size.times { @worker_queue << @worker_stop_token }
|
|
257
|
+
@worker_threads.each(&:join)
|
|
258
|
+
@worker_threads.clear
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def worker_pool_size
|
|
262
|
+
size = ENV.fetch("ITSI_WORKER_POOL_SIZE", Etc.nprocessors.to_s).to_i
|
|
263
|
+
size.positive? ? size : 1
|
|
264
|
+
rescue StandardError
|
|
265
|
+
1
|
|
266
|
+
end
|
|
152
267
|
end
|
|
153
268
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: itsi-scheduler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.27
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Wouter Coppieters
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -53,6 +53,7 @@ files:
|
|
|
53
53
|
- ext/itsi_acme/src/caches/no.rs
|
|
54
54
|
- ext/itsi_acme/src/caches/test.rs
|
|
55
55
|
- ext/itsi_acme/src/config.rs
|
|
56
|
+
- ext/itsi_acme/src/http_challenge.rs
|
|
56
57
|
- ext/itsi_acme/src/https_helper.rs
|
|
57
58
|
- ext/itsi_acme/src/incoming.rs
|
|
58
59
|
- ext/itsi_acme/src/jose.rs
|