itsi-scheduler 0.2.22-aarch64-linux

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 (149) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +8 -0
  3. data/Cargo.lock +997 -0
  4. data/Cargo.toml +7 -0
  5. data/Rakefile +39 -0
  6. data/ext/itsi_acme/Cargo.toml +86 -0
  7. data/ext/itsi_acme/examples/high_level.rs +63 -0
  8. data/ext/itsi_acme/examples/high_level_warp.rs +52 -0
  9. data/ext/itsi_acme/examples/low_level.rs +87 -0
  10. data/ext/itsi_acme/examples/low_level_axum.rs +66 -0
  11. data/ext/itsi_acme/src/acceptor.rs +81 -0
  12. data/ext/itsi_acme/src/acme.rs +354 -0
  13. data/ext/itsi_acme/src/axum.rs +86 -0
  14. data/ext/itsi_acme/src/cache.rs +39 -0
  15. data/ext/itsi_acme/src/caches/boxed.rs +80 -0
  16. data/ext/itsi_acme/src/caches/composite.rs +69 -0
  17. data/ext/itsi_acme/src/caches/dir.rs +106 -0
  18. data/ext/itsi_acme/src/caches/mod.rs +11 -0
  19. data/ext/itsi_acme/src/caches/no.rs +78 -0
  20. data/ext/itsi_acme/src/caches/test.rs +136 -0
  21. data/ext/itsi_acme/src/config.rs +172 -0
  22. data/ext/itsi_acme/src/https_helper.rs +69 -0
  23. data/ext/itsi_acme/src/incoming.rs +142 -0
  24. data/ext/itsi_acme/src/jose.rs +161 -0
  25. data/ext/itsi_acme/src/lib.rs +142 -0
  26. data/ext/itsi_acme/src/resolver.rs +59 -0
  27. data/ext/itsi_acme/src/state.rs +424 -0
  28. data/ext/itsi_error/Cargo.lock +368 -0
  29. data/ext/itsi_error/Cargo.toml +12 -0
  30. data/ext/itsi_error/src/lib.rs +140 -0
  31. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  32. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  33. data/ext/itsi_rb_helpers/Cargo.lock +355 -0
  34. data/ext/itsi_rb_helpers/Cargo.toml +11 -0
  35. data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
  36. data/ext/itsi_rb_helpers/src/lib.rs +232 -0
  37. data/ext/itsi_scheduler/Cargo.toml +24 -0
  38. data/ext/itsi_scheduler/extconf.rb +11 -0
  39. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  40. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  41. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  42. data/ext/itsi_scheduler/src/itsi_scheduler.rs +320 -0
  43. data/ext/itsi_scheduler/src/lib.rs +39 -0
  44. data/ext/itsi_server/Cargo.lock +2956 -0
  45. data/ext/itsi_server/Cargo.toml +94 -0
  46. data/ext/itsi_server/src/default_responses/mod.rs +14 -0
  47. data/ext/itsi_server/src/env.rs +43 -0
  48. data/ext/itsi_server/src/lib.rs +154 -0
  49. data/ext/itsi_server/src/prelude.rs +2 -0
  50. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +116 -0
  51. data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +149 -0
  52. data/ext/itsi_server/src/ruby_types/itsi_grpc_call.rs +346 -0
  53. data/ext/itsi_server/src/ruby_types/itsi_grpc_response_stream/mod.rs +265 -0
  54. data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +399 -0
  55. data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +447 -0
  56. data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +545 -0
  57. data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +650 -0
  58. data/ext/itsi_server/src/ruby_types/itsi_server.rs +102 -0
  59. data/ext/itsi_server/src/ruby_types/mod.rs +48 -0
  60. data/ext/itsi_server/src/server/binds/bind.rs +204 -0
  61. data/ext/itsi_server/src/server/binds/bind_protocol.rs +37 -0
  62. data/ext/itsi_server/src/server/binds/listener.rs +485 -0
  63. data/ext/itsi_server/src/server/binds/mod.rs +4 -0
  64. data/ext/itsi_server/src/server/binds/tls/locked_dir_cache.rs +132 -0
  65. data/ext/itsi_server/src/server/binds/tls.rs +278 -0
  66. data/ext/itsi_server/src/server/byte_frame.rs +32 -0
  67. data/ext/itsi_server/src/server/frame_stream.rs +143 -0
  68. data/ext/itsi_server/src/server/http_message_types.rs +230 -0
  69. data/ext/itsi_server/src/server/io_stream.rs +128 -0
  70. data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
  71. data/ext/itsi_server/src/server/middleware_stack/middleware.rs +170 -0
  72. data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +63 -0
  73. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +94 -0
  74. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +93 -0
  75. data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +343 -0
  76. data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +151 -0
  77. data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +329 -0
  78. data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +300 -0
  79. data/ext/itsi_server/src/server/middleware_stack/middlewares/csp.rs +193 -0
  80. data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +64 -0
  81. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response/default_responses.rs +188 -0
  82. data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +168 -0
  83. data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +183 -0
  84. data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +82 -0
  85. data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +209 -0
  86. data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +133 -0
  87. data/ext/itsi_server/src/server/middleware_stack/middlewares/max_body.rs +47 -0
  88. data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +122 -0
  89. data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +407 -0
  90. data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +155 -0
  91. data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +54 -0
  92. data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +54 -0
  93. data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +51 -0
  94. data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +138 -0
  95. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +269 -0
  96. data/ext/itsi_server/src/server/middleware_stack/middlewares/static_response.rs +62 -0
  97. data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +218 -0
  98. data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +31 -0
  99. data/ext/itsi_server/src/server/middleware_stack/mod.rs +381 -0
  100. data/ext/itsi_server/src/server/mod.rs +14 -0
  101. data/ext/itsi_server/src/server/process_worker.rs +247 -0
  102. data/ext/itsi_server/src/server/redirect_type.rs +26 -0
  103. data/ext/itsi_server/src/server/request_job.rs +11 -0
  104. data/ext/itsi_server/src/server/serve_strategy/acceptor.rs +100 -0
  105. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +411 -0
  106. data/ext/itsi_server/src/server/serve_strategy/mod.rs +31 -0
  107. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +449 -0
  108. data/ext/itsi_server/src/server/signal.rs +129 -0
  109. data/ext/itsi_server/src/server/size_limited_incoming.rs +107 -0
  110. data/ext/itsi_server/src/server/thread_worker.rs +504 -0
  111. data/ext/itsi_server/src/services/cache_store.rs +74 -0
  112. data/ext/itsi_server/src/services/itsi_http_service.rs +270 -0
  113. data/ext/itsi_server/src/services/mime_types.rs +2896 -0
  114. data/ext/itsi_server/src/services/mod.rs +6 -0
  115. data/ext/itsi_server/src/services/password_hasher.rs +89 -0
  116. data/ext/itsi_server/src/services/rate_limiter.rs +609 -0
  117. data/ext/itsi_server/src/services/static_file_server.rs +1400 -0
  118. data/ext/itsi_tracing/Cargo.lock +274 -0
  119. data/ext/itsi_tracing/Cargo.toml +17 -0
  120. data/ext/itsi_tracing/src/lib.rs +370 -0
  121. data/itsi-scheduler-100.png +0 -0
  122. data/lib/itsi/schedule_refinement.rb +96 -0
  123. data/lib/itsi/scheduler/3.1/itsi_scheduler.so +0 -0
  124. data/lib/itsi/scheduler/3.2/itsi_scheduler.so +0 -0
  125. data/lib/itsi/scheduler/3.3/itsi_scheduler.so +0 -0
  126. data/lib/itsi/scheduler/3.4/itsi_scheduler.so +0 -0
  127. data/lib/itsi/scheduler/4.0/itsi_scheduler.so +0 -0
  128. data/lib/itsi/scheduler/native_extension.rb +34 -0
  129. data/lib/itsi/scheduler/version.rb +7 -0
  130. data/lib/itsi/scheduler.rb +153 -0
  131. data/vendor/rb-sys-build/.cargo-ok +1 -0
  132. data/vendor/rb-sys-build/.cargo_vcs_info.json +6 -0
  133. data/vendor/rb-sys-build/Cargo.lock +294 -0
  134. data/vendor/rb-sys-build/Cargo.toml +71 -0
  135. data/vendor/rb-sys-build/Cargo.toml.orig +32 -0
  136. data/vendor/rb-sys-build/LICENSE-APACHE +190 -0
  137. data/vendor/rb-sys-build/LICENSE-MIT +21 -0
  138. data/vendor/rb-sys-build/src/bindings/sanitizer.rs +185 -0
  139. data/vendor/rb-sys-build/src/bindings/stable_api.rs +247 -0
  140. data/vendor/rb-sys-build/src/bindings/wrapper.h +71 -0
  141. data/vendor/rb-sys-build/src/bindings.rs +280 -0
  142. data/vendor/rb-sys-build/src/cc.rs +421 -0
  143. data/vendor/rb-sys-build/src/lib.rs +12 -0
  144. data/vendor/rb-sys-build/src/rb_config/flags.rs +101 -0
  145. data/vendor/rb-sys-build/src/rb_config/library.rs +132 -0
  146. data/vendor/rb-sys-build/src/rb_config/search_path.rs +57 -0
  147. data/vendor/rb-sys-build/src/rb_config.rs +906 -0
  148. data/vendor/rb-sys-build/src/utils.rs +53 -0
  149. metadata +210 -0
@@ -0,0 +1,504 @@
1
+ use async_channel::Sender;
2
+ use itsi_error::ItsiError;
3
+ use itsi_rb_helpers::{
4
+ call_with_gvl, call_without_gvl, create_ruby_thread, kill_threads, HeapValue,
5
+ };
6
+ use itsi_tracing::{debug, error};
7
+ use magnus::{
8
+ error::Result,
9
+ value::{InnerValue, Lazy, LazyId, Opaque, ReprValue},
10
+ Module, RClass, Ruby, Thread, Value,
11
+ };
12
+ use parking_lot::{Mutex, RwLock};
13
+ use std::{
14
+ ops::Deref,
15
+ sync::{
16
+ atomic::{AtomicBool, AtomicU64, Ordering},
17
+ Arc,
18
+ },
19
+ time::{Instant, SystemTime, UNIX_EPOCH},
20
+ };
21
+ use tokio::{runtime::Builder as RuntimeBuilder, sync::watch};
22
+ use tracing::instrument;
23
+
24
+ use crate::{
25
+ ruby_types::{
26
+ itsi_grpc_call::ItsiGrpcCall, itsi_http_request::ItsiHttpRequest,
27
+ itsi_server::itsi_server_config::ServerParams, ITSI_SERVER,
28
+ },
29
+ server::process_worker::CORE_IDS,
30
+ };
31
+
32
+ use super::request_job::RequestJob;
33
+ pub struct ThreadWorker {
34
+ pub params: Arc<ServerParams>,
35
+ pub id: u8,
36
+ pub worker_id: usize,
37
+ pub request_id: AtomicU64,
38
+ pub current_request_start: AtomicU64,
39
+ pub receiver: Arc<async_channel::Receiver<RequestJob>>,
40
+ pub sender: Sender<RequestJob>,
41
+ pub thread: RwLock<Option<HeapValue<Thread>>>,
42
+ pub terminated: Arc<AtomicBool>,
43
+ pub scheduler_class: Option<Opaque<Value>>,
44
+ }
45
+
46
+ static ID_ALIVE: LazyId = LazyId::new("alive?");
47
+ static ID_SCHEDULER: LazyId = LazyId::new("scheduler");
48
+ static ID_SCHEDULE: LazyId = LazyId::new("schedule");
49
+ static ID_BLOCK: LazyId = LazyId::new("block");
50
+ static ID_YIELD: LazyId = LazyId::new("yield");
51
+ static ID_CONST_GET: LazyId = LazyId::new("const_get");
52
+ static CLASS_FIBER: Lazy<RClass> = Lazy::new(|ruby| {
53
+ ruby.module_kernel()
54
+ .const_get::<_, RClass>("Fiber")
55
+ .unwrap()
56
+ });
57
+
58
+ pub struct TerminateWakerSignal(bool);
59
+ type ThreadWorkerBuildResult = Result<(
60
+ Arc<Vec<Arc<ThreadWorker>>>,
61
+ Sender<RequestJob>,
62
+ Sender<RequestJob>,
63
+ )>;
64
+
65
+ #[instrument(name = "boot", parent=None, skip(params, worker_id))]
66
+ pub fn build_thread_workers(
67
+ params: Arc<ServerParams>,
68
+ worker_id: usize,
69
+ ) -> ThreadWorkerBuildResult {
70
+ let blocking_thread_count = params.threads;
71
+ let nonblocking_thread_count = params.scheduler_threads;
72
+ let ruby_thread_request_backlog_size: usize = params
73
+ .ruby_thread_request_backlog_size
74
+ .unwrap_or_else(|| (blocking_thread_count as u16 * 30) as usize);
75
+
76
+ let (blocking_sender, blocking_receiver) =
77
+ async_channel::bounded(ruby_thread_request_backlog_size);
78
+ let blocking_receiver_ref = Arc::new(blocking_receiver);
79
+ let blocking_sender_ref = blocking_sender;
80
+ let scheduler_class = load_scheduler_class(params.scheduler_class.clone())?;
81
+
82
+ let mut workers = (1..=blocking_thread_count)
83
+ .map(|id| {
84
+ ThreadWorker::new(
85
+ params.clone(),
86
+ id,
87
+ worker_id,
88
+ blocking_receiver_ref.clone(),
89
+ blocking_sender_ref.clone(),
90
+ if nonblocking_thread_count.is_some() {
91
+ None
92
+ } else {
93
+ scheduler_class
94
+ },
95
+ )
96
+ })
97
+ .collect::<Result<Vec<_>>>()?;
98
+
99
+ let nonblocking_sender_ref = if let (Some(nonblocking_thread_count), Some(scheduler_class)) =
100
+ (nonblocking_thread_count, scheduler_class)
101
+ {
102
+ let (nonblocking_sender, nonblocking_receiver) =
103
+ async_channel::bounded((nonblocking_thread_count as u16 * 30) as usize);
104
+ let nonblocking_receiver_ref = Arc::new(nonblocking_receiver);
105
+ let nonblocking_sender_ref = nonblocking_sender.clone();
106
+ for id in 0..nonblocking_thread_count {
107
+ workers.push(ThreadWorker::new(
108
+ params.clone(),
109
+ id,
110
+ worker_id,
111
+ nonblocking_receiver_ref.clone(),
112
+ nonblocking_sender_ref.clone(),
113
+ Some(scheduler_class),
114
+ )?)
115
+ }
116
+ nonblocking_sender
117
+ } else {
118
+ blocking_sender_ref.clone()
119
+ };
120
+
121
+ Ok((
122
+ Arc::new(workers),
123
+ blocking_sender_ref,
124
+ nonblocking_sender_ref,
125
+ ))
126
+ }
127
+
128
+ pub fn load_scheduler_class(scheduler_class: Option<String>) -> Result<Option<Opaque<Value>>> {
129
+ call_with_gvl(|ruby| {
130
+ let scheduler_class = if let Some(scheduler_class) = scheduler_class {
131
+ Some(Opaque::from(
132
+ ruby.module_kernel()
133
+ .funcall::<_, _, Value>(*ID_CONST_GET, (scheduler_class,))?,
134
+ ))
135
+ } else {
136
+ None
137
+ };
138
+ Ok(scheduler_class)
139
+ })
140
+ }
141
+ impl ThreadWorker {
142
+ pub fn new(
143
+ params: Arc<ServerParams>,
144
+ id: u8,
145
+ worker_id: usize,
146
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
147
+ sender: Sender<RequestJob>,
148
+ scheduler_class: Option<Opaque<Value>>,
149
+ ) -> Result<Arc<Self>> {
150
+ let worker = Arc::new(Self {
151
+ params,
152
+ id,
153
+ worker_id,
154
+ request_id: AtomicU64::new(0),
155
+ current_request_start: AtomicU64::new(0),
156
+ receiver,
157
+ sender,
158
+ thread: RwLock::new(None),
159
+ terminated: Arc::new(AtomicBool::new(false)),
160
+ scheduler_class,
161
+ });
162
+ worker.clone().run()?;
163
+ Ok(worker)
164
+ }
165
+
166
+ #[instrument(skip(self, deadline), fields(id = self.id))]
167
+ pub fn poll_shutdown(&self, deadline: Instant) -> bool {
168
+ if let Some(thread) = self.thread.read().deref() {
169
+ if Instant::now() > deadline {
170
+ debug!("Worker shutdown timed out. Killing thread {:?}", thread);
171
+ self.terminated.store(true, Ordering::SeqCst);
172
+ kill_threads(vec![thread.as_value()]);
173
+ }
174
+ if thread.funcall::<_, _, bool>(*ID_ALIVE, ()).unwrap_or(false) {
175
+ return true;
176
+ }
177
+ debug!("Thread has shut down");
178
+ }
179
+ self.thread.write().take();
180
+
181
+ false
182
+ }
183
+
184
+ pub fn run(self: Arc<Self>) -> Result<()> {
185
+ let receiver = self.receiver.clone();
186
+ let terminated = self.terminated.clone();
187
+ let scheduler_class = self.scheduler_class;
188
+ let params = self.params.clone();
189
+ let self_ref = self.clone();
190
+ let worker_id = self.worker_id;
191
+ call_with_gvl(|_| {
192
+ *self.thread.write() = Some(
193
+ create_ruby_thread(move || {
194
+ if params.pin_worker_cores {
195
+ core_affinity::set_for_current(
196
+ CORE_IDS[((2 * worker_id) + 1) % CORE_IDS.len()],
197
+ );
198
+ }
199
+ debug!("Ruby thread worker started");
200
+ if let Some(scheduler_class) = scheduler_class {
201
+ if let Err(err) = self_ref.fiber_accept_loop(
202
+ params,
203
+ receiver,
204
+ scheduler_class,
205
+ terminated,
206
+ ) {
207
+ error!("Error in fiber_accept_loop: {:?}", err);
208
+ }
209
+ } else {
210
+ self_ref.accept_loop(params, receiver, terminated);
211
+ }
212
+ })
213
+ .ok_or_else(|| {
214
+ ItsiError::InternalServerError("Failed to create Ruby thread".to_owned())
215
+ })?
216
+ .into(),
217
+ );
218
+ Ok::<(), magnus::Error>(())
219
+ })?;
220
+ Ok(())
221
+ }
222
+
223
+ pub fn build_scheduler_proc(
224
+ self: Arc<Self>,
225
+ leader: &Arc<Mutex<Option<RequestJob>>>,
226
+ receiver: &Arc<async_channel::Receiver<RequestJob>>,
227
+ terminated: &Arc<AtomicBool>,
228
+ waker_sender: &watch::Sender<TerminateWakerSignal>,
229
+ oob_gc_responses_threshold: Option<u64>,
230
+ ) -> magnus::block::Proc {
231
+ let leader = leader.clone();
232
+ let receiver = receiver.clone();
233
+ let terminated = terminated.clone();
234
+ let waker_sender = waker_sender.clone();
235
+ Ruby::get().unwrap().proc_from_fn(move |ruby, _args, _blk| {
236
+ let scheduler = ruby
237
+ .get_inner(&CLASS_FIBER)
238
+ .funcall::<_, _, Value>(*ID_SCHEDULER, ())
239
+ .unwrap();
240
+ let server = ruby.get_inner(&ITSI_SERVER);
241
+ let thread_current = ruby.thread_current();
242
+ let leader_clone = leader.clone();
243
+ let receiver = receiver.clone();
244
+ let terminated = terminated.clone();
245
+ let waker_sender = waker_sender.clone();
246
+ let self_ref = self.clone();
247
+ let mut batch = Vec::with_capacity(MAX_BATCH_SIZE as usize);
248
+
249
+ static MAX_BATCH_SIZE: i32 = 25;
250
+ call_without_gvl(move || loop {
251
+ let mut idle_counter = 0;
252
+ if let Some(v) = leader_clone.lock().take() {
253
+ match v {
254
+ RequestJob::ProcessHttpRequest(itsi_request, app_proc) => {
255
+ batch.push(RequestJob::ProcessHttpRequest(itsi_request, app_proc))
256
+ }
257
+ RequestJob::ProcessGrpcRequest(itsi_request, app_proc) => {
258
+ batch.push(RequestJob::ProcessGrpcRequest(itsi_request, app_proc))
259
+ }
260
+ RequestJob::Shutdown => {
261
+ waker_sender.send(TerminateWakerSignal(true)).unwrap();
262
+ break;
263
+ }
264
+ }
265
+ }
266
+
267
+ for _ in 0..MAX_BATCH_SIZE {
268
+ if let Ok(req) = receiver.try_recv() {
269
+ let should_break = matches!(req, RequestJob::Shutdown);
270
+ batch.push(req);
271
+ if should_break {
272
+ break;
273
+ }
274
+ } else {
275
+ break;
276
+ }
277
+ }
278
+
279
+ let shutdown_requested = call_with_gvl(|_| {
280
+ for req in batch.drain(..) {
281
+ match req {
282
+ RequestJob::ProcessHttpRequest(request, app_proc) => {
283
+ self_ref.request_id.fetch_add(1, Ordering::Relaxed);
284
+ self_ref.current_request_start.store(
285
+ SystemTime::now()
286
+ .duration_since(UNIX_EPOCH)
287
+ .unwrap()
288
+ .as_secs(),
289
+ Ordering::Relaxed,
290
+ );
291
+ let response = request.response.clone();
292
+ if let Err(err) = server.funcall::<_, _, Value>(
293
+ *ID_SCHEDULE,
294
+ (app_proc.as_value(), request),
295
+ ) {
296
+ ItsiHttpRequest::internal_error(ruby, response, err)
297
+ }
298
+ }
299
+ RequestJob::ProcessGrpcRequest(request, app_proc) => {
300
+ self_ref.request_id.fetch_add(1, Ordering::Relaxed);
301
+ self_ref.current_request_start.store(
302
+ SystemTime::now()
303
+ .duration_since(UNIX_EPOCH)
304
+ .unwrap()
305
+ .as_secs(),
306
+ Ordering::Relaxed,
307
+ );
308
+ let response = request.stream.clone();
309
+ if let Err(err) = server.funcall::<_, _, Value>(
310
+ *ID_SCHEDULE,
311
+ (app_proc.as_value(), request),
312
+ ) {
313
+ ItsiGrpcCall::internal_error(ruby, response, err)
314
+ }
315
+ }
316
+ RequestJob::Shutdown => {
317
+ return true;
318
+ }
319
+ }
320
+ }
321
+ false
322
+ });
323
+
324
+ if shutdown_requested || terminated.load(Ordering::Relaxed) {
325
+ waker_sender.send(TerminateWakerSignal(true)).unwrap();
326
+ break;
327
+ }
328
+
329
+ let yield_result = if receiver.is_empty() {
330
+ let should_gc = if let Some(oob_gc_threshold) = oob_gc_responses_threshold {
331
+ idle_counter = (idle_counter + 1) % oob_gc_threshold;
332
+ idle_counter == 0
333
+ } else {
334
+ false
335
+ };
336
+ waker_sender.send(TerminateWakerSignal(false)).unwrap();
337
+ call_with_gvl(|ruby| {
338
+ if should_gc {
339
+ ruby.gc_start();
340
+ }
341
+ scheduler.funcall::<_, _, Value>(*ID_BLOCK, (thread_current, None::<u8>))
342
+ })
343
+ } else {
344
+ call_with_gvl(|_| scheduler.funcall::<_, _, Value>(*ID_YIELD, ()))
345
+ };
346
+
347
+ if yield_result.is_err() {
348
+ break;
349
+ }
350
+ });
351
+ })
352
+ }
353
+
354
+ #[instrument(skip_all, fields(thread_worker=format!("{}:{}", self.id, self.worker_id)))]
355
+ pub fn fiber_accept_loop(
356
+ self: Arc<Self>,
357
+ params: Arc<ServerParams>,
358
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
359
+ scheduler_class: Opaque<Value>,
360
+ terminated: Arc<AtomicBool>,
361
+ ) -> Result<()> {
362
+ let ruby = Ruby::get().unwrap();
363
+ let (waker_sender, waker_receiver) = watch::channel(TerminateWakerSignal(false));
364
+ let leader: Arc<Mutex<Option<RequestJob>>> = Arc::new(Mutex::new(None));
365
+ let server_class = ruby.get_inner(&ITSI_SERVER);
366
+ let scheduler_proc = self.build_scheduler_proc(
367
+ &leader,
368
+ &receiver,
369
+ &terminated,
370
+ &waker_sender,
371
+ params.oob_gc_responses_threshold,
372
+ );
373
+ let (scheduler, scheduler_fiber) = server_class.funcall::<_, _, (Value, Value)>(
374
+ "start_scheduler_loop",
375
+ (scheduler_class, scheduler_proc),
376
+ )?;
377
+ Self::start_waker_thread(
378
+ scheduler.into(),
379
+ scheduler_fiber.into(),
380
+ leader,
381
+ receiver,
382
+ waker_receiver,
383
+ );
384
+ Ok(())
385
+ }
386
+
387
+ #[allow(clippy::await_holding_lock)]
388
+ pub fn start_waker_thread(
389
+ scheduler: Opaque<Value>,
390
+ scheduler_fiber: Opaque<Value>,
391
+ leader: Arc<Mutex<Option<RequestJob>>>,
392
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
393
+ mut waker_receiver: watch::Receiver<TerminateWakerSignal>,
394
+ ) {
395
+ create_ruby_thread(move || {
396
+ let scheduler = scheduler.get_inner_with(&Ruby::get().unwrap());
397
+ let leader = leader.clone();
398
+ call_without_gvl(|| {
399
+ RuntimeBuilder::new_current_thread()
400
+ .build()
401
+ .expect("Failed to build Tokio runtime")
402
+ .block_on(async {
403
+ loop {
404
+ waker_receiver.changed().await.ok();
405
+ if waker_receiver.borrow().0 {
406
+ break;
407
+ }
408
+ tokio::select! {
409
+ _ = waker_receiver.changed() => {
410
+ if waker_receiver.borrow().0 {
411
+ break;
412
+ }
413
+ },
414
+ next_msg = receiver.recv() => {
415
+ *leader.lock() = next_msg.ok();
416
+ call_with_gvl(|_| {
417
+ scheduler
418
+ .funcall::<_, _, Value>(
419
+ "unblock",
420
+ (None::<u8>, scheduler_fiber),
421
+ )
422
+ .ok();
423
+ });
424
+ }
425
+ }
426
+ }
427
+ })
428
+ });
429
+ });
430
+ }
431
+
432
+ #[instrument(skip_all, fields(thread_worker=format!("{}:{}", self.id, self.worker_id)))]
433
+ pub fn accept_loop(
434
+ self: Arc<Self>,
435
+ params: Arc<ServerParams>,
436
+ receiver: Arc<async_channel::Receiver<RequestJob>>,
437
+ terminated: Arc<AtomicBool>,
438
+ ) {
439
+ let mut idle_counter = 0;
440
+ call_without_gvl(|| loop {
441
+ match receiver.recv_blocking() {
442
+ Err(_) => break,
443
+ Ok(RequestJob::Shutdown) => break,
444
+ Ok(request_job) => call_with_gvl(|ruby| {
445
+ self.process_one(&ruby, request_job, &terminated);
446
+ while let Ok(request_job) = receiver.try_recv() {
447
+ if matches!(request_job, RequestJob::Shutdown) {
448
+ terminated.store(true, Ordering::Relaxed);
449
+ break;
450
+ }
451
+ self.process_one(&ruby, request_job, &terminated);
452
+ }
453
+ if let Some(thresh) = params.oob_gc_responses_threshold {
454
+ idle_counter = (idle_counter + 1) % thresh;
455
+ if idle_counter == 0 {
456
+ ruby.gc_start();
457
+ }
458
+ }
459
+ }),
460
+ };
461
+ if terminated.load(Ordering::Relaxed) {
462
+ break;
463
+ }
464
+ });
465
+ }
466
+
467
+ fn process_one(self: &Arc<Self>, ruby: &Ruby, job: RequestJob, terminated: &Arc<AtomicBool>) {
468
+ match job {
469
+ RequestJob::ProcessHttpRequest(request, app_proc) => {
470
+ if terminated.load(Ordering::Relaxed) {
471
+ request.response().unwrap().service_unavailable();
472
+ return;
473
+ }
474
+ self.request_id.fetch_add(1, Ordering::Relaxed);
475
+ self.current_request_start.store(
476
+ SystemTime::now()
477
+ .duration_since(UNIX_EPOCH)
478
+ .unwrap()
479
+ .as_secs(),
480
+ Ordering::Relaxed,
481
+ );
482
+ request.process(ruby, app_proc).ok();
483
+ }
484
+
485
+ RequestJob::ProcessGrpcRequest(request, app_proc) => {
486
+ if terminated.load(Ordering::Relaxed) {
487
+ request.stream().unwrap().close().ok();
488
+ return;
489
+ }
490
+ self.request_id.fetch_add(1, Ordering::Relaxed);
491
+ self.current_request_start.store(
492
+ SystemTime::now()
493
+ .duration_since(UNIX_EPOCH)
494
+ .unwrap()
495
+ .as_secs(),
496
+ Ordering::Relaxed,
497
+ );
498
+ request.process(ruby, app_proc).ok();
499
+ }
500
+
501
+ RequestJob::Shutdown => unreachable!(),
502
+ }
503
+ }
504
+ }
@@ -0,0 +1,74 @@
1
+ use async_trait::async_trait;
2
+ use redis::aio::ConnectionManager;
3
+ use redis::{Client, RedisError, Script};
4
+ use std::sync::Arc;
5
+ use std::time::Duration;
6
+
7
+ #[derive(Debug)]
8
+ pub enum CacheError {
9
+ RedisError(RedisError),
10
+ // Other error variants as needed.
11
+ }
12
+ /// A general-purpose cache trait with an atomic “increment with timeout” operation.
13
+ #[async_trait]
14
+ pub trait CacheStore: Send + Sync + std::fmt::Debug {
15
+ /// Increments the counter associated with `key` and sets (or extends) its expiration.
16
+ /// Returns the new counter value.
17
+ async fn increment(&self, key: &str, timeout: Duration) -> Result<u64, CacheError>;
18
+ }
19
+
20
+ /// A Redis-backed cache store using an async connection manager.
21
+ /// This uses a TLS-enabled connection when the URL is prefixed with "rediss://".
22
+ #[derive(Clone)]
23
+ pub struct RedisCacheStore {
24
+ connection: Arc<ConnectionManager>,
25
+ }
26
+
27
+ impl std::fmt::Debug for RedisCacheStore {
28
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29
+ f.debug_struct("RedisCacheStore").finish()
30
+ }
31
+ }
32
+
33
+ impl RedisCacheStore {
34
+ /// Constructs a new RedisCacheStore.
35
+ ///
36
+ /// Use a connection URL like "rediss://host:port" to enable TLS (with rustls under the hood).
37
+ /// This constructor is async because it sets up the connection manager.
38
+ pub async fn new(connection_url: &str) -> Result<Self, CacheError> {
39
+ let client = Client::open(connection_url).map_err(CacheError::RedisError)?;
40
+ let connection_manager = ConnectionManager::new(client)
41
+ .await
42
+ .map_err(CacheError::RedisError)?;
43
+ Ok(Self {
44
+ connection: Arc::new(connection_manager),
45
+ })
46
+ }
47
+ }
48
+
49
+ #[async_trait]
50
+ impl CacheStore for RedisCacheStore {
51
+ async fn increment(&self, key: &str, timeout: Duration) -> Result<u64, CacheError> {
52
+ let timeout_secs = timeout.as_secs();
53
+ // Lua script to:
54
+ // 1. INCR the key.
55
+ // 2. If the key doesn't have a TTL, set it.
56
+ let script = r#"
57
+ local current = redis.call('INCR', KEYS[1])
58
+ if redis.call('TTL', KEYS[1]) < 0 then
59
+ redis.call('EXPIRE', KEYS[1], ARGV[1])
60
+ end
61
+ return current
62
+ "#;
63
+ let script = Script::new(script);
64
+ // The ConnectionManager is cloneable and can be used concurrently.
65
+ let mut connection = (*self.connection).clone();
66
+ let value: i64 = script
67
+ .key(key)
68
+ .arg(timeout_secs)
69
+ .invoke_async(&mut connection)
70
+ .await
71
+ .map_err(CacheError::RedisError)?;
72
+ Ok(value as u64)
73
+ }
74
+ }