itsi-scheduler 0.2.11 → 0.2.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c057b5de1665276930d7c09f81ccc6e1b81419cf7cd9a4b3c04a08d3b6d0976d
4
- data.tar.gz: 32fb526429c78f471eb248d3800dff138e7021aec6f95b13ee30ff4a4297a570
3
+ metadata.gz: 92a729e2814758244bb48ab697c3e6f2cc5d239414df2f5f106beb6356d46e02
4
+ data.tar.gz: 4104654365bb5ace7881d90ac9e49f849e899ee1ca2b17ae03a7f5bf9d16dac2
5
5
  SHA512:
6
- metadata.gz: c2304a02521484da65165d2f08632d9b52be536246ccefb5993b1715e7adcbe6cbc42fda820dffcd1e10bb1082b93ca9317e7aec6205cdfb735b3a6c933a63d5
7
- data.tar.gz: d1c3f48ba6ba4c71694a5e0ee3f72ebb2b4229bbf4fe4cde8756ec5d6ed4d1c971c6e1e791c36b1f3ee7816e2db0a712aa70704b9c77ad9d2695021829c769c8
6
+ metadata.gz: 3c8512c18fe4b5bde77b999ecf5f6772af2102d213bddfa8be2416178c2210b94787a8dc5531910cf85c815155ed2cf739999c08d6891342eb414a51bed5bc4f
7
+ data.tar.gz: 22914be4826e42af88929240d968ac493b8d9c82737807f33fe5aea0f979070eadac6de64104f912b0190dea2c3207e15199178f07c8c927ed5c5d0400936807
data/Cargo.lock CHANGED
@@ -213,7 +213,7 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
213
213
 
214
214
  [[package]]
215
215
  name = "itsi-scheduler"
216
- version = "0.2.11"
216
+ version = "0.2.12"
217
217
  dependencies = [
218
218
  "bytes",
219
219
  "derive_more",
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "itsi_error"
3
3
  version = "0.1.0"
4
- edition = "2024"
4
+ edition = "2021"
5
5
 
6
6
  [dependencies]
7
7
  thiserror = "2.0.11"
@@ -1,7 +1,7 @@
1
1
  [package]
2
2
  name = "itsi_rb_helpers"
3
3
  version = "0.1.0"
4
- edition = "2024"
4
+ edition = "2021"
5
5
 
6
6
  [dependencies]
7
7
  cfg-if = "1.0.0"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-scheduler"
3
- version = "0.2.11"
3
+ version = "0.2.12"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "itsi-server"
3
- version = "0.2.11"
3
+ version = "0.2.12"
4
4
  edition = "2021"
5
5
  authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
6
  license = "MIT"
@@ -9,3 +9,6 @@ pub static NOT_FOUND_RESPONSE: LazyLock<ErrorResponse> = LazyLock::new(ErrorResp
9
9
 
10
10
  pub static INTERNAL_SERVER_ERROR_RESPONSE: LazyLock<ErrorResponse> =
11
11
  LazyLock::new(ErrorResponse::internal_server_error);
12
+
13
+ pub static SERVICE_UNAVAILABLE_RESPONSE: LazyLock<ErrorResponse> =
14
+ LazyLock::new(ErrorResponse::service_unavailable);
@@ -22,6 +22,7 @@ use super::{
22
22
  itsi_http_response::ItsiHttpResponse,
23
23
  };
24
24
  use crate::{
25
+ default_responses::{INTERNAL_SERVER_ERROR_RESPONSE, SERVICE_UNAVAILABLE_RESPONSE},
25
26
  server::{
26
27
  byte_frame::ByteFrame,
27
28
  http_message_types::{HttpRequest, HttpResponse},
@@ -186,16 +187,20 @@ impl ItsiHttpRequest {
186
187
  } else {
187
188
  &context.sender
188
189
  };
189
- match sender
190
- .send(RequestJob::ProcessHttpRequest(request, app))
191
- .await
192
- {
193
- Err(err) => {
194
- error!("Error occurred: {}", err);
195
- let mut response = Response::new(BoxBody::new(Empty::new()));
196
- *response.status_mut() = StatusCode::BAD_REQUEST;
197
- Ok(response)
198
- }
190
+ match sender.try_send(RequestJob::ProcessHttpRequest(request, app)) {
191
+ Err(err) => match err {
192
+ async_channel::TrySendError::Full(_) => {
193
+ Ok(SERVICE_UNAVAILABLE_RESPONSE
194
+ .to_http_response(context.accept.clone())
195
+ .await)
196
+ }
197
+ async_channel::TrySendError::Closed(err) => {
198
+ error!("Error occurred: {:?}", err);
199
+ Ok(INTERNAL_SERVER_ERROR_RESPONSE
200
+ .to_http_response(context.accept.clone())
201
+ .await)
202
+ }
203
+ },
199
204
  _ => match receiver.recv().await {
200
205
  Some(first_frame) => Ok(response
201
206
  .build(first_frame, receiver, shutdown_channel)
@@ -347,7 +347,7 @@ impl ItsiHttpResponse {
347
347
  })?;
348
348
  let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
349
349
  if let Some(ref mut resp) = *self.data.response.write() {
350
- resp.headers_mut().insert(header_name, header_value);
350
+ resp.headers_mut().append(header_name, header_value);
351
351
  }
352
352
  Ok(())
353
353
  }
@@ -364,7 +364,7 @@ impl ItsiHttpResponse {
364
364
  })?;
365
365
  for value in values {
366
366
  let header_value = unsafe { HeaderValue::from_maybe_shared_unchecked(value) };
367
- headers_mut.insert(&header_name, header_value);
367
+ headers_mut.append(&header_name, header_value);
368
368
  }
369
369
  }
370
370
  }
@@ -69,6 +69,7 @@ pub struct ServerParams {
69
69
  pub pin_worker_cores: bool,
70
70
  pub scheduler_class: Option<String>,
71
71
  pub oob_gc_responses_threshold: Option<u64>,
72
+ pub ruby_thread_request_backlog_size: Option<usize>,
72
73
  pub middleware_loader: HeapValue<Proc>,
73
74
  pub middleware: OnceLock<MiddlewareSet>,
74
75
  pub binds: Vec<Bind>,
@@ -222,6 +223,10 @@ impl ServerParams {
222
223
  let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
223
224
  let oob_gc_responses_threshold: Option<u64> =
224
225
  rb_param_hash.fetch("oob_gc_responses_threshold")?;
226
+
227
+ let ruby_thread_request_backlog_size: Option<usize> =
228
+ rb_param_hash.fetch("ruby_thread_request_backlog_size")?;
229
+
225
230
  let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
226
231
  let log_level: Option<String> = rb_param_hash.fetch("log_level")?;
227
232
  let log_target: Option<String> = rb_param_hash.fetch("log_target")?;
@@ -310,6 +315,7 @@ impl ServerParams {
310
315
  scheduler_threads,
311
316
  streamable_body,
312
317
  scheduler_class,
318
+ ruby_thread_request_backlog_size,
313
319
  oob_gc_responses_threshold,
314
320
  binds,
315
321
  itsi_server_token_preference,
@@ -18,6 +18,7 @@ use std::sync::Arc;
18
18
  pub struct RubyApp {
19
19
  app: Arc<HeapValue<Proc>>,
20
20
  request_type: RequestType,
21
+ script_name: Option<String>,
21
22
  sendfile: bool,
22
23
  nonblocking: bool,
23
24
  base_path: Regex,
@@ -53,6 +54,9 @@ impl RubyApp {
53
54
  let base_path_src = params
54
55
  .funcall::<_, _, String>(Symbol::new("[]"), ("base_path",))
55
56
  .unwrap_or("".to_owned());
57
+ let script_name = params
58
+ .funcall::<_, _, Option<String>>(Symbol::new("[]"), ("script_name",))
59
+ .unwrap_or(None);
56
60
  let base_path = Regex::new(&base_path_src).unwrap();
57
61
 
58
62
  let request_type: RequestType = params
@@ -65,6 +69,7 @@ impl RubyApp {
65
69
  app: Arc::new(app.into()),
66
70
  sendfile,
67
71
  nonblocking,
72
+ script_name,
68
73
  request_type,
69
74
  base_path,
70
75
  }))
@@ -82,13 +87,14 @@ impl MiddlewareLayer for RubyApp {
82
87
  match self.request_type {
83
88
  RequestType::Http => {
84
89
  let uri = req.uri().path();
85
- let script_name = self
86
- .base_path
87
- .captures(uri)
88
- .and_then(|caps| caps.name("base_path"))
89
- .map(|m| m.as_str())
90
- .unwrap_or("/")
91
- .to_owned();
90
+ let script_name = self.script_name.clone().unwrap_or_else(|| {
91
+ self.base_path
92
+ .captures(uri)
93
+ .and_then(|caps| caps.name("base_path"))
94
+ .map(|m| m.as_str())
95
+ .unwrap_or("/")
96
+ .to_owned()
97
+ });
92
98
  ItsiHttpRequest::process_request(
93
99
  self.app.clone(),
94
100
  req,
@@ -65,9 +65,12 @@ type ThreadWorkerBuildResult = Result<(
65
65
  pub fn build_thread_workers(params: Arc<ServerParams>, pid: Pid) -> ThreadWorkerBuildResult {
66
66
  let blocking_thread_count = params.threads;
67
67
  let nonblocking_thread_count = params.scheduler_threads;
68
+ let ruby_thread_request_backlog_size: usize = params
69
+ .ruby_thread_request_backlog_size
70
+ .unwrap_or_else(|| (blocking_thread_count as u16 * 30) as usize);
68
71
 
69
72
  let (blocking_sender, blocking_receiver) =
70
- async_channel::bounded((blocking_thread_count as u16 * 30) as usize);
73
+ async_channel::bounded(ruby_thread_request_backlog_size);
71
74
  let blocking_receiver_ref = Arc::new(blocking_receiver);
72
75
  let blocking_sender_ref = blocking_sender;
73
76
  let scheduler_class = load_scheduler_class(params.scheduler_class.clone())?;
@@ -0,0 +1,96 @@
1
+ module Itsi
2
+ module ScheduleRefinement
3
+ # Useful helper functions for using cooperative multi-tasking in Ruby.
4
+ # Opt-in to usage by executing `using Itsi::ScheduleRefinement` in any places
5
+ # you intend to use it.
6
+ #
7
+ # After this you can do things like the following
8
+ #
9
+ # 1. Launch batch concurrent fire-and-forget jobs.
10
+ # * 100.times.schedule_each{ sleep 0.1 }
11
+ #
12
+ # 2. Launch batch concurrent transofmrs
13
+ # See how `schedule_map` retains ordering, despite sleeping for randomized amount of time.
14
+ #
15
+ # * 100.times.schedule_map{|i| sleep Random.rand(0.0..0.05); i }
16
+ #
17
+ # 3. Manually organize fibers to run concurrently.
18
+ #
19
+ # require "net/http"
20
+ # schedule do
21
+ # req1, req2 = Queue.new, Queue.new
22
+ # schedule do
23
+ # puts "Making request 1"
24
+ # req1 << Net::HTTP.get(URI("http://httpbin.org/get"))
25
+ # puts "Finished request 1"
26
+ # end
27
+ #
28
+ # schedule do
29
+ # puts "Making request 2"
30
+ # req2 << Net::HTTP.get(URI("http://httpbin.org/get"))
31
+ # puts "Finished request 2"
32
+ # end
33
+ #
34
+ # res1, res2 = [req1, req2].map(&:pop)
35
+ # end
36
+ refine Kernel do
37
+ private def schedule(&blk) # rubocop:disable Metrics/MethodLength
38
+ return unless blk
39
+
40
+ if Fiber.scheduler.nil?
41
+ result = nil
42
+ Thread.new do
43
+ Fiber.set_scheduler Itsi::Scheduler.new
44
+ Fiber.schedule { result = blk.call }
45
+ end.join
46
+ result
47
+ else
48
+ Fiber.schedule(&blk)
49
+ end
50
+ end
51
+ end
52
+
53
+ module EnumerableExtensions
54
+ using ScheduleRefinement
55
+ def schedule_each(&block)
56
+ enum = Enumerator.new do |y|
57
+ schedule do
58
+ each { |item| schedule{ y.yield(item) } }
59
+ end
60
+ end
61
+
62
+ block_given? ? enum.each(&block) : enum.each
63
+ end
64
+
65
+ def schedule_map(&block)
66
+ return Enumerator.new do |y|
67
+ schedule do
68
+ with_index.each_with_object([]) do |(item, index), agg|
69
+ schedule do
70
+ agg[index] = (y << item)
71
+ end
72
+ end
73
+ end
74
+ end.map unless block_given?
75
+ schedule do
76
+ with_index.each_with_object([]) do |(item, index), agg|
77
+ schedule do
78
+ agg[index] = block[item]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+ refine Enumerator do
87
+ define_method(:schedule_each, EnumerableExtensions.instance_method(:schedule_each))
88
+ define_method(:schedule_map, EnumerableExtensions.instance_method(:schedule_map))
89
+ end
90
+
91
+ refine Enumerable do
92
+ define_method(:schedule_each, EnumerableExtensions.instance_method(:schedule_each))
93
+ define_method(:schedule_map, EnumerableExtensions.instance_method(:schedule_map))
94
+ end
95
+ end
96
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Itsi
4
4
  class Scheduler
5
- VERSION = "0.2.11"
5
+ VERSION = "0.2.12"
6
6
  end
7
7
  end
@@ -2,37 +2,10 @@
2
2
 
3
3
  require_relative "scheduler/version"
4
4
  require_relative "scheduler/itsi_scheduler"
5
+ require_relative "schedule_refinement"
5
6
 
6
7
  module Itsi
7
- module ScheduleUtils
8
- def schedule(&block)
9
- return to_enum(:schedule) unless block_given?
10
-
11
- Object.schedule do
12
- each { |item| Object.schedule { block.call(item) } }
13
- end
14
- end
15
- end
16
-
17
8
  class Scheduler
18
- def self.enable_schedule_keyword!
19
- Object.define_method(:schedule) do |&blk|
20
- result = nil
21
- return result unless blk
22
-
23
- if Fiber.scheduler.nil?
24
- Thread.new do
25
- Fiber.set_scheduler Itsi::Scheduler.new
26
- Fiber.schedule { result = blk[] }
27
- end.join
28
- else
29
- Fiber.schedule { result = blk[] }
30
- end
31
- result
32
- end
33
- Enumerable.include(ScheduleUtils)
34
- end
35
-
36
9
  class Error < StandardError; end
37
10
 
38
11
  def self.resume_token
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: itsi-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
@@ -179,9 +179,9 @@ files:
179
179
  - ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock
180
180
  - ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock
181
181
  - itsi-scheduler-100.png
182
+ - lib/itsi/schedule_refinement.rb
182
183
  - lib/itsi/scheduler.rb
183
184
  - lib/itsi/scheduler/version.rb
184
- - sig/itsi_scheduler.rbs
185
185
  homepage: https://itsi.fyi
186
186
  licenses:
187
187
  - MIT
@@ -1,4 +0,0 @@
1
- module ItsiScheduler
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end