itsi-server 0.1.1 → 0.1.13
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +7 -0
- data/Cargo.lock +4417 -0
- data/Cargo.toml +7 -0
- data/README.md +4 -0
- data/Rakefile +8 -1
- data/_index.md +6 -0
- data/exe/itsi +94 -45
- data/ext/itsi_error/Cargo.toml +2 -0
- data/ext/itsi_error/src/from.rs +68 -0
- data/ext/itsi_error/src/lib.rs +18 -34
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_error/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_error/target/debug/build/rb-sys-49f554618693db24/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-1mmt5sux7jb0i/s-h510z7m8v9-0bxu7yd.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-2vn3jey74oiw0/s-h5113n0e7e-1v5qzs6.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510ykifhe-0tbnep2.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510yyocpj-0tz7ug7.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-37uv9dicz7awp/s-h510z0xc8g-14ol18k.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3g5qf4y7d54uj/s-h5113n0e7d-1trk8on.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3lpfftm45d3e2/s-h510z7m8r3-1pxp20o.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510ykifek-1uxasnk.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510yyocki-11u37qm.lock +0 -0
- data/ext/itsi_error/target/debug/incremental/itsi_error-3o4qownhl3d7n/s-h510z0xc93-0pmy0zm.lock +0 -0
- data/ext/itsi_instrument_entry/Cargo.toml +15 -0
- data/ext/itsi_instrument_entry/src/lib.rs +31 -0
- data/ext/itsi_rb_helpers/Cargo.toml +3 -0
- data/ext/itsi_rb_helpers/src/heap_value.rs +139 -0
- data/ext/itsi_rb_helpers/src/lib.rs +140 -10
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/common.rs +355 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/dynamic.rs +276 -0
- data/ext/itsi_rb_helpers/target/debug/build/clang-sys-da71b0344e568175/out/macros.rs +49 -0
- data/ext/itsi_rb_helpers/target/debug/build/rb-sys-eb9ed4ff3a60f995/out/bindings-0.9.110-mri-arm64-darwin23-3.4.2.rs +8865 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-040pxg6yhb3g3/s-h5113n7a1b-03bwlt4.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h51113xnh3-1eik1ip.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-131g1u4dzkt1a/s-h5111704jj-0g4rj8x.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-1q2d3drtxrzs5/s-h5113n79yl-0bxcqc5.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h51113xoox-10de2hp.lock +0 -0
- data/ext/itsi_rb_helpers/target/debug/incremental/itsi_rb_helpers-374a9h7ovycj0/s-h5111704w7-0vdq7gq.lock +0 -0
- data/ext/itsi_scheduler/Cargo.toml +24 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
- data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
- data/ext/itsi_scheduler/src/lib.rs +38 -0
- data/ext/itsi_server/Cargo.lock +2956 -0
- data/ext/itsi_server/Cargo.toml +73 -13
- data/ext/itsi_server/extconf.rb +1 -1
- data/ext/itsi_server/src/env.rs +43 -0
- data/ext/itsi_server/src/lib.rs +100 -40
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/big_bytes.rs +109 -0
- data/ext/itsi_server/src/ruby_types/itsi_body_proxy/mod.rs +141 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/ext/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_request.rs +282 -0
- data/ext/itsi_server/src/ruby_types/itsi_http_response.rs +388 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/ext/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/ext/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/ext/itsi_server/src/ruby_types/mod.rs +55 -0
- data/ext/itsi_server/src/server/bind.rs +75 -31
- data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
- data/ext/itsi_server/src/server/byte_frame.rs +32 -0
- data/ext/itsi_server/src/server/cache_store.rs +74 -0
- data/ext/itsi_server/src/server/io_stream.rs +104 -0
- data/ext/itsi_server/src/server/itsi_service.rs +172 -0
- data/ext/itsi_server/src/server/lifecycle_event.rs +12 -0
- data/ext/itsi_server/src/server/listener.rs +332 -132
- data/ext/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/ext/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/ext/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/ext/itsi_server/src/server/mod.rs +15 -2
- data/ext/itsi_server/src/server/process_worker.rs +229 -0
- data/ext/itsi_server/src/server/rate_limiter.rs +565 -0
- data/ext/itsi_server/src/server/request_job.rs +11 -0
- data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +337 -0
- data/ext/itsi_server/src/server/serve_strategy/mod.rs +30 -0
- data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +421 -0
- data/ext/itsi_server/src/server/signal.rs +93 -0
- data/ext/itsi_server/src/server/static_file_server.rs +984 -0
- data/ext/itsi_server/src/server/thread_worker.rs +444 -0
- data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
- data/ext/itsi_server/src/server/tls.rs +187 -60
- data/ext/itsi_server/src/server/types.rs +43 -0
- data/ext/itsi_tracing/Cargo.toml +5 -0
- data/ext/itsi_tracing/src/lib.rs +225 -7
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0994n8rpvvt9m/s-h510hfz1f6-1kbycmq.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-0bob7bf4yq34i/s-h5113125h5-0lh4rag.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2fcodulrxbbxo/s-h510h2infk-0hp5kjw.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2iak63r1woi1l/s-h510h2in4q-0kxfzw1.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2kk4qj9gn5dg2/s-h5113124kv-0enwon2.lock +0 -0
- data/ext/itsi_tracing/target/debug/incremental/itsi_tracing-2mwo0yas7dtw4/s-h510hfz1ha-1udgpei.lock +0 -0
- data/lib/itsi/http_request.rb +87 -0
- data/lib/itsi/http_response.rb +39 -0
- data/lib/itsi/server/Itsi.rb +119 -0
- data/lib/itsi/server/config/dsl.rb +506 -0
- data/lib/itsi/server/config.rb +131 -0
- data/lib/itsi/server/default_app/default_app.rb +38 -0
- data/lib/itsi/server/default_app/index.html +91 -0
- data/lib/itsi/server/grpc_interface.rb +213 -0
- data/lib/itsi/server/rack/handler/itsi.rb +27 -0
- data/lib/itsi/server/rack_interface.rb +94 -0
- data/lib/itsi/server/scheduler_interface.rb +21 -0
- data/lib/itsi/server/scheduler_mode.rb +10 -0
- data/lib/itsi/server/signal_trap.rb +29 -0
- data/lib/itsi/server/version.rb +1 -1
- data/lib/itsi/server.rb +90 -9
- data/lib/itsi/standard_headers.rb +86 -0
- metadata +122 -31
- data/ext/itsi_server/src/request/itsi_request.rs +0 -143
- data/ext/itsi_server/src/request/mod.rs +0 -1
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
- data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
- data/ext/itsi_server/src/server/itsi_server.rs +0 -182
- data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
- data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
- data/lib/itsi/request.rb +0 -39
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            [package]
         | 
| 2 | 
            +
            name = "itsi-scheduler"
         | 
| 3 | 
            +
            version = "0.1.0"
         | 
| 4 | 
            +
            edition = "2021"
         | 
| 5 | 
            +
            authors = ["Wouter Coppieters <wc@pico.net.nz>"]
         | 
| 6 | 
            +
            license = "MIT"
         | 
| 7 | 
            +
            publish = false
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            [lib]
         | 
| 10 | 
            +
            crate-type = ["cdylib"]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            [dependencies]
         | 
| 13 | 
            +
            magnus = { version = "0.7.1", features = ["rb-sys", "bytes"] }
         | 
| 14 | 
            +
            derive_more = { version = "2.0.1", features = ["debug"] }
         | 
| 15 | 
            +
            itsi_tracing = { path = "../itsi_tracing" }
         | 
| 16 | 
            +
            itsi_rb_helpers = { path = "../itsi_rb_helpers" }
         | 
| 17 | 
            +
            itsi_error = { path = "../itsi_error" }
         | 
| 18 | 
            +
            itsi_instrument_entry = { path = "../itsi_instrument_entry" }
         | 
| 19 | 
            +
            parking_lot = "0.12.3"
         | 
| 20 | 
            +
            mio = { version = "1.0.3", features = ["os-poll", "os-ext"] }
         | 
| 21 | 
            +
            rb-sys = "0.9.105"
         | 
| 22 | 
            +
            bytes = "1.10.1"
         | 
| 23 | 
            +
            nix = "0.29.0"
         | 
| 24 | 
            +
            tracing = "0.1.41"
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            use std::os::fd::RawFd;
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            use itsi_error::{ItsiError, Result};
         | 
| 4 | 
            +
            use mio::Interest;
         | 
| 5 | 
            +
            use nix::libc::{fcntl, poll, pollfd, F_GETFL, F_SETFL, O_NONBLOCK};
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            use super::Readiness;
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            pub fn set_nonblocking(fd: RawFd) -> itsi_error::Result<()> {
         | 
| 10 | 
            +
                unsafe {
         | 
| 11 | 
            +
                    let flags = fcntl(fd, F_GETFL);
         | 
| 12 | 
            +
                    if flags < 0 {
         | 
| 13 | 
            +
                        return Err(ItsiError::ArgumentError(format!(
         | 
| 14 | 
            +
                            "fcntl(F_GETFL) error for fd {}: {}",
         | 
| 15 | 
            +
                            fd,
         | 
| 16 | 
            +
                            std::io::Error::last_os_error()
         | 
| 17 | 
            +
                        )));
         | 
| 18 | 
            +
                    }
         | 
| 19 | 
            +
                    let new_flags = flags | O_NONBLOCK;
         | 
| 20 | 
            +
                    if fcntl(fd, F_SETFL, new_flags) < 0 {
         | 
| 21 | 
            +
                        return Err(ItsiError::ArgumentError(format!(
         | 
| 22 | 
            +
                            "fcntl(F_SETFL) error for fd {}: {}",
         | 
| 23 | 
            +
                            fd,
         | 
| 24 | 
            +
                            std::io::Error::last_os_error()
         | 
| 25 | 
            +
                        )));
         | 
| 26 | 
            +
                    }
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
                Ok(())
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            pub fn poll_readiness(fd: RawFd, events: i16) -> Option<Readiness> {
         | 
| 32 | 
            +
                let mut pfd = pollfd {
         | 
| 33 | 
            +
                    fd,
         | 
| 34 | 
            +
                    events,
         | 
| 35 | 
            +
                    revents: 0,
         | 
| 36 | 
            +
                };
         | 
| 37 | 
            +
                let ret = unsafe { poll(&mut pfd as *mut pollfd, 1, 0) };
         | 
| 38 | 
            +
                if ret > 0 {
         | 
| 39 | 
            +
                    return Some(Readiness(pfd.revents));
         | 
| 40 | 
            +
                }
         | 
| 41 | 
            +
                None
         | 
| 42 | 
            +
            }
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            pub fn build_interest(events: i16) -> Result<Interest> {
         | 
| 45 | 
            +
                let mut interest_opt = None;
         | 
| 46 | 
            +
                if events & 1 != 0 {
         | 
| 47 | 
            +
                    interest_opt = Some(Interest::READABLE);
         | 
| 48 | 
            +
                }
         | 
| 49 | 
            +
                if events & 4 != 0 {
         | 
| 50 | 
            +
                    interest_opt = Some(match interest_opt {
         | 
| 51 | 
            +
                        Some(i) => i | Interest::WRITABLE,
         | 
| 52 | 
            +
                        None => Interest::WRITABLE,
         | 
| 53 | 
            +
                    });
         | 
| 54 | 
            +
                }
         | 
| 55 | 
            +
                interest_opt.ok_or_else(|| ItsiError::ArgumentError("No valid event specified".to_owned()))
         | 
| 56 | 
            +
            }
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            use derive_more::Debug;
         | 
| 2 | 
            +
            use mio::{event::Source, unix::SourceFd, Interest, Token};
         | 
| 3 | 
            +
            use std::os::fd::RawFd;
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #[derive(Debug, Clone, PartialEq, Eq, Hash)]
         | 
| 6 | 
            +
            pub struct IoWaiter {
         | 
| 7 | 
            +
                pub fd: RawFd,
         | 
| 8 | 
            +
                pub readiness: i16,
         | 
| 9 | 
            +
                pub token: Token,
         | 
| 10 | 
            +
            }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            impl IoWaiter {
         | 
| 13 | 
            +
                pub fn new(fd: RawFd, readiness: i16, token: Token) -> Self {
         | 
| 14 | 
            +
                    Self {
         | 
| 15 | 
            +
                        fd,
         | 
| 16 | 
            +
                        readiness,
         | 
| 17 | 
            +
                        token,
         | 
| 18 | 
            +
                    }
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
            }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            impl Source for IoWaiter {
         | 
| 23 | 
            +
                fn register(
         | 
| 24 | 
            +
                    &mut self,
         | 
| 25 | 
            +
                    registry: &mio::Registry,
         | 
| 26 | 
            +
                    token: Token,
         | 
| 27 | 
            +
                    interests: Interest,
         | 
| 28 | 
            +
                ) -> std::io::Result<()> {
         | 
| 29 | 
            +
                    SourceFd(&self.fd).register(registry, token, interests)
         | 
| 30 | 
            +
                }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                fn reregister(
         | 
| 33 | 
            +
                    &mut self,
         | 
| 34 | 
            +
                    registry: &mio::Registry,
         | 
| 35 | 
            +
                    token: Token,
         | 
| 36 | 
            +
                    interests: Interest,
         | 
| 37 | 
            +
                ) -> std::io::Result<()> {
         | 
| 38 | 
            +
                    SourceFd(&self.fd).reregister(registry, token, interests)
         | 
| 39 | 
            +
                }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                fn deregister(&mut self, registry: &mio::Registry) -> std::io::Result<()> {
         | 
| 42 | 
            +
                    SourceFd(&self.fd).deregister(registry)
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
            }
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            use std::{
         | 
| 2 | 
            +
                cmp::Ordering,
         | 
| 3 | 
            +
                time::{Duration, Instant},
         | 
| 4 | 
            +
            };
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            use mio::Token;
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            #[derive(Debug, Clone, PartialEq, Eq)]
         | 
| 9 | 
            +
            pub struct Timer {
         | 
| 10 | 
            +
                pub wake_time: Instant,
         | 
| 11 | 
            +
                pub token: Token,
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
            impl PartialOrd for Timer {
         | 
| 14 | 
            +
                fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
         | 
| 15 | 
            +
                    Some(self.cmp(other))
         | 
| 16 | 
            +
                }
         | 
| 17 | 
            +
            }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            impl Ord for Timer {
         | 
| 20 | 
            +
                fn cmp(&self, other: &Self) -> Ordering {
         | 
| 21 | 
            +
                    // Reverse the order: a timer with an earlier wake_time should be considered greater.
         | 
| 22 | 
            +
                    other
         | 
| 23 | 
            +
                        .wake_time
         | 
| 24 | 
            +
                        .cmp(&self.wake_time)
         | 
| 25 | 
            +
                        .then_with(|| other.token.cmp(&self.token))
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
            }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            impl Timer {
         | 
| 30 | 
            +
                pub fn new(wake_in: Duration, token: Token) -> Self {
         | 
| 31 | 
            +
                    Self {
         | 
| 32 | 
            +
                        wake_time: Instant::now() + wake_in,
         | 
| 33 | 
            +
                        token,
         | 
| 34 | 
            +
                    }
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                pub fn is_due(&self) -> bool {
         | 
| 38 | 
            +
                    self.wake_time <= Instant::now()
         | 
| 39 | 
            +
                }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                pub(crate) fn duration(&self) -> Option<Duration> {
         | 
| 42 | 
            +
                    self.wake_time.checked_duration_since(Instant::now())
         | 
| 43 | 
            +
                }
         | 
| 44 | 
            +
            }
         | 
| @@ -0,0 +1,308 @@ | |
| 1 | 
            +
            mod io_helpers;
         | 
| 2 | 
            +
            mod io_waiter;
         | 
| 3 | 
            +
            mod timer;
         | 
| 4 | 
            +
            use io_helpers::{build_interest, poll_readiness, set_nonblocking};
         | 
| 5 | 
            +
            use io_waiter::IoWaiter;
         | 
| 6 | 
            +
            use itsi_error::ItsiError;
         | 
| 7 | 
            +
            use itsi_rb_helpers::{call_without_gvl, create_ruby_thread};
         | 
| 8 | 
            +
            use magnus::{
         | 
| 9 | 
            +
                error::Result as MagnusResult,
         | 
| 10 | 
            +
                value::{InnerValue, Opaque, ReprValue},
         | 
| 11 | 
            +
                Module, RClass, Ruby, Value,
         | 
| 12 | 
            +
            };
         | 
| 13 | 
            +
            use mio::{Events, Poll, Token, Waker};
         | 
| 14 | 
            +
            use parking_lot::{Mutex, RwLock};
         | 
| 15 | 
            +
            use std::{
         | 
| 16 | 
            +
                collections::{BinaryHeap, HashMap, VecDeque},
         | 
| 17 | 
            +
                os::fd::RawFd,
         | 
| 18 | 
            +
                sync::Arc,
         | 
| 19 | 
            +
                time::Duration,
         | 
| 20 | 
            +
            };
         | 
| 21 | 
            +
            use timer::Timer;
         | 
| 22 | 
            +
            use tracing::{debug, info, warn};
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
         | 
| 25 | 
            +
            pub(crate) struct Readiness(i16);
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            impl std::fmt::Debug for ItsiScheduler {
         | 
| 28 | 
            +
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         | 
| 29 | 
            +
                    f.debug_struct("ItsiScheduler").finish()
         | 
| 30 | 
            +
                }
         | 
| 31 | 
            +
            }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            const WAKE_TOKEN: Token = Token(0);
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            #[magnus::wrap(class = "Itsi::Scheduler", free_immediately, size)]
         | 
| 36 | 
            +
            pub(crate) struct ItsiScheduler {
         | 
| 37 | 
            +
                timers: Mutex<BinaryHeap<Timer>>,
         | 
| 38 | 
            +
                io_waiters: Mutex<HashMap<Token, IoWaiter>>,
         | 
| 39 | 
            +
                registry: Mutex<HashMap<RawFd, VecDeque<IoWaiter>>>,
         | 
| 40 | 
            +
                poll: Mutex<Poll>,
         | 
| 41 | 
            +
                events: Mutex<Events>,
         | 
| 42 | 
            +
                waker: Mutex<Waker>,
         | 
| 43 | 
            +
            }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            impl Default for ItsiScheduler {
         | 
| 46 | 
            +
                fn default() -> Self {
         | 
| 47 | 
            +
                    let poll = Poll::new().unwrap();
         | 
| 48 | 
            +
                    let waker = Waker::new(poll.registry(), WAKE_TOKEN).unwrap();
         | 
| 49 | 
            +
                    let events = Events::with_capacity(1024);
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    ItsiScheduler {
         | 
| 52 | 
            +
                        timers: Mutex::new(BinaryHeap::new()),
         | 
| 53 | 
            +
                        io_waiters: Mutex::new(HashMap::new()),
         | 
| 54 | 
            +
                        registry: Mutex::new(HashMap::new()),
         | 
| 55 | 
            +
                        poll: Mutex::new(poll),
         | 
| 56 | 
            +
                        events: Mutex::new(events),
         | 
| 57 | 
            +
                        waker: Mutex::new(waker),
         | 
| 58 | 
            +
                    }
         | 
| 59 | 
            +
                }
         | 
| 60 | 
            +
            }
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            impl ItsiScheduler {
         | 
| 63 | 
            +
                pub fn initialize(&self) {}
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                pub fn wake(&self) -> MagnusResult<()> {
         | 
| 66 | 
            +
                    self.waker.lock().wake().map_err(|_| {
         | 
| 67 | 
            +
                        magnus::Error::new(
         | 
| 68 | 
            +
                            magnus::exception::exception(),
         | 
| 69 | 
            +
                            "Failed to wake the scheduler",
         | 
| 70 | 
            +
                        )
         | 
| 71 | 
            +
                    })?;
         | 
| 72 | 
            +
                    Ok(())
         | 
| 73 | 
            +
                }
         | 
| 74 | 
            +
                pub fn register_io_wait(
         | 
| 75 | 
            +
                    &self,
         | 
| 76 | 
            +
                    io_obj: i32,
         | 
| 77 | 
            +
                    events: i16,
         | 
| 78 | 
            +
                    timeout: Option<f64>,
         | 
| 79 | 
            +
                    token: usize,
         | 
| 80 | 
            +
                ) -> MagnusResult<Option<i16>> {
         | 
| 81 | 
            +
                    debug!(
         | 
| 82 | 
            +
                        "Registering IO Wait for {:?}, {:?}, {:?}, {:?}",
         | 
| 83 | 
            +
                        io_obj, events, timeout, token
         | 
| 84 | 
            +
                    );
         | 
| 85 | 
            +
                    let fd: RawFd = io_obj;
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                    let readiness = poll_readiness(fd, events).unwrap_or(Readiness(0));
         | 
| 88 | 
            +
                    if readiness == Readiness(events) {
         | 
| 89 | 
            +
                        return Ok(Some(readiness.0));
         | 
| 90 | 
            +
                    }
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    set_nonblocking(fd)?;
         | 
| 93 | 
            +
                    let interest = build_interest(events)?;
         | 
| 94 | 
            +
                    let token = Token(token);
         | 
| 95 | 
            +
                    let mut waiter = IoWaiter::new(fd, events, token);
         | 
| 96 | 
            +
                    self.io_waiters.lock().insert(token, waiter.clone());
         | 
| 97 | 
            +
                    let mut binding = self.registry.lock();
         | 
| 98 | 
            +
                    let queue = binding.entry(fd).or_default();
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    queue.push_back(waiter.clone());
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    if queue.len() == 1 {
         | 
| 103 | 
            +
                        self.poll
         | 
| 104 | 
            +
                            .lock()
         | 
| 105 | 
            +
                            .registry()
         | 
| 106 | 
            +
                            .register(&mut waiter, token, interest)
         | 
| 107 | 
            +
                            .map_err(|e| ItsiError::ArgumentError(format!("register error: {}", e)))?;
         | 
| 108 | 
            +
                    }
         | 
| 109 | 
            +
                    Ok(None)
         | 
| 110 | 
            +
                }
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                pub fn start_timer(&self, timeout: Option<f64>, token: usize) {
         | 
| 113 | 
            +
                    if timeout.is_some_and(|t| t >= 0.0) {
         | 
| 114 | 
            +
                        let timer_entry = Timer::new(Duration::from_secs_f64(timeout.unwrap()), Token(token));
         | 
| 115 | 
            +
                        self.timers.lock().push(timer_entry);
         | 
| 116 | 
            +
                    }
         | 
| 117 | 
            +
                }
         | 
| 118 | 
            +
                pub fn has_pending_io(&self) -> bool {
         | 
| 119 | 
            +
                    !self.timers.lock().is_empty() || !self.io_waiters.lock().is_empty()
         | 
| 120 | 
            +
                }
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                pub fn class_info(msg: String) {
         | 
| 123 | 
            +
                    info!(msg);
         | 
| 124 | 
            +
                }
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                pub fn info(&self, msg: String) {
         | 
| 127 | 
            +
                    info!(msg);
         | 
| 128 | 
            +
                }
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                pub fn warn(&self, msg: String) {
         | 
| 131 | 
            +
                    warn!(msg);
         | 
| 132 | 
            +
                }
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                pub fn debug(&self, msg: String) {
         | 
| 135 | 
            +
                    debug!(msg);
         | 
| 136 | 
            +
                }
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                pub fn fetch_due_events(&self) -> MagnusResult<Option<Vec<(usize, i16)>>> {
         | 
| 139 | 
            +
                    call_without_gvl(|| {
         | 
| 140 | 
            +
                        let timeout = if let Some(timer) = self.timers.lock().peek() {
         | 
| 141 | 
            +
                            timer.duration().or(Some(Duration::ZERO))
         | 
| 142 | 
            +
                        } else {
         | 
| 143 | 
            +
                            None
         | 
| 144 | 
            +
                        };
         | 
| 145 | 
            +
                        let mut due_fibers: Option<Vec<(usize, i16)>> = None;
         | 
| 146 | 
            +
                        let mut io_waiters = self.io_waiters.lock();
         | 
| 147 | 
            +
                        if !io_waiters.is_empty() || timeout.is_none() {
         | 
| 148 | 
            +
                            let mut events = self.events.lock();
         | 
| 149 | 
            +
                            {
         | 
| 150 | 
            +
                                let mut poll = self.poll.lock();
         | 
| 151 | 
            +
                                poll.poll(&mut events, timeout)
         | 
| 152 | 
            +
                                    .map_err(|e| ItsiError::ArgumentError(format!("poll error: {}", e)))?;
         | 
| 153 | 
            +
                            };
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                            for event in events.iter() {
         | 
| 156 | 
            +
                                let token = event.token();
         | 
| 157 | 
            +
                                if token == WAKE_TOKEN {
         | 
| 158 | 
            +
                                    continue;
         | 
| 159 | 
            +
                                }
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                                let waiter = io_waiters.remove(&token);
         | 
| 162 | 
            +
                                if waiter.is_none() {
         | 
| 163 | 
            +
                                    continue;
         | 
| 164 | 
            +
                                }
         | 
| 165 | 
            +
                                let mut waiter = waiter.unwrap();
         | 
| 166 | 
            +
                                let mut evt_readiness = 0;
         | 
| 167 | 
            +
                                if event.is_readable() {
         | 
| 168 | 
            +
                                    evt_readiness |= 1;
         | 
| 169 | 
            +
                                }
         | 
| 170 | 
            +
                                if event.is_priority() {
         | 
| 171 | 
            +
                                    evt_readiness |= 2;
         | 
| 172 | 
            +
                                }
         | 
| 173 | 
            +
                                if event.is_writable() {
         | 
| 174 | 
            +
                                    evt_readiness |= 4
         | 
| 175 | 
            +
                                }
         | 
| 176 | 
            +
                                self.poll
         | 
| 177 | 
            +
                                    .lock()
         | 
| 178 | 
            +
                                    .registry()
         | 
| 179 | 
            +
                                    .deregister(&mut waiter)
         | 
| 180 | 
            +
                                    .map_err(|_| {
         | 
| 181 | 
            +
                                        ItsiError::ArgumentError("Failed to deregister".to_string())
         | 
| 182 | 
            +
                                    })?;
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                                due_fibers
         | 
| 185 | 
            +
                                    .get_or_insert_default()
         | 
| 186 | 
            +
                                    .push((waiter.token.0, evt_readiness));
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                                let mut binding = self.registry.lock();
         | 
| 189 | 
            +
                                // Pop the current item for the current waiter off the queue
         | 
| 190 | 
            +
                                let queue = binding.get_mut(&(waiter.fd)).unwrap();
         | 
| 191 | 
            +
                                queue.pop_front();
         | 
| 192 | 
            +
             | 
| 193 | 
            +
                                if let Some(head) = queue.get_mut(0) {
         | 
| 194 | 
            +
                                    // Register the next item in the queue if there is one.
         | 
| 195 | 
            +
                                    let interest = build_interest(head.readiness)?;
         | 
| 196 | 
            +
                                    self.poll
         | 
| 197 | 
            +
                                        .lock()
         | 
| 198 | 
            +
                                        .registry()
         | 
| 199 | 
            +
                                        .register(head, head.token, interest)
         | 
| 200 | 
            +
                                        .map_err(|_| {
         | 
| 201 | 
            +
                                            ItsiError::ArgumentError("Failed to deregister".to_string())
         | 
| 202 | 
            +
                                        })?;
         | 
| 203 | 
            +
                                } else {
         | 
| 204 | 
            +
                                    // Otherwise we drop the queue altogether.
         | 
| 205 | 
            +
                                    binding.remove(&waiter.fd);
         | 
| 206 | 
            +
                                }
         | 
| 207 | 
            +
                            }
         | 
| 208 | 
            +
                            return Ok(due_fibers);
         | 
| 209 | 
            +
                        }
         | 
| 210 | 
            +
                        Ok(None)
         | 
| 211 | 
            +
                    })
         | 
| 212 | 
            +
                }
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                pub fn run_blocking_in_thread<T, F>(&self, ruby: &Ruby, work: F) -> MagnusResult<Option<T>>
         | 
| 215 | 
            +
                where
         | 
| 216 | 
            +
                    T: Send + Sync + std::fmt::Debug + 'static,
         | 
| 217 | 
            +
                    F: FnOnce() -> Option<T> + Send + 'static,
         | 
| 218 | 
            +
                {
         | 
| 219 | 
            +
                    let result: Arc<RwLock<Option<T>>> = Arc::new(RwLock::new(None));
         | 
| 220 | 
            +
                    let result_clone = Arc::clone(&result);
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                    let current_fiber = Opaque::from(ruby.fiber_current());
         | 
| 223 | 
            +
                    let scheduler = Opaque::from(
         | 
| 224 | 
            +
                        ruby.module_kernel()
         | 
| 225 | 
            +
                            .const_get::<_, RClass>("Fiber")
         | 
| 226 | 
            +
                            .unwrap()
         | 
| 227 | 
            +
                            .funcall::<_, _, Value>("scheduler", ())
         | 
| 228 | 
            +
                            .unwrap(),
         | 
| 229 | 
            +
                    );
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                    create_ruby_thread(move || {
         | 
| 232 | 
            +
                        call_without_gvl(|| {
         | 
| 233 | 
            +
                            let outcome = work();
         | 
| 234 | 
            +
                            *result_clone.write() = outcome;
         | 
| 235 | 
            +
                        });
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                        let ruby = Ruby::get().unwrap();
         | 
| 238 | 
            +
                        scheduler
         | 
| 239 | 
            +
                            .get_inner_with(&ruby)
         | 
| 240 | 
            +
                            .funcall::<_, _, Value>("unblock", (None::<String>, current_fiber))
         | 
| 241 | 
            +
                            .unwrap();
         | 
| 242 | 
            +
                    });
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                    scheduler
         | 
| 245 | 
            +
                        .get_inner_with(ruby)
         | 
| 246 | 
            +
                        .funcall::<_, _, Value>("block", (None::<Value>, None::<u64>))?;
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                    let result_opt = Arc::try_unwrap(result).unwrap().write().take();
         | 
| 249 | 
            +
                    Ok(result_opt)
         | 
| 250 | 
            +
                }
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                pub fn address_resolve(
         | 
| 253 | 
            +
                    ruby: &Ruby,
         | 
| 254 | 
            +
                    rself: &Self,
         | 
| 255 | 
            +
                    hostname: String,
         | 
| 256 | 
            +
                ) -> MagnusResult<Option<Vec<String>>> {
         | 
| 257 | 
            +
                    let result: Option<Vec<String>> = rself.run_blocking_in_thread(ruby, move || {
         | 
| 258 | 
            +
                        use std::net::ToSocketAddrs;
         | 
| 259 | 
            +
                        let addrs_res = (hostname.as_str(), 0).to_socket_addrs();
         | 
| 260 | 
            +
                        match addrs_res {
         | 
| 261 | 
            +
                            Ok(addrs) => {
         | 
| 262 | 
            +
                                let ips: Vec<String> = addrs.map(|s| s.ip().to_string()).collect();
         | 
| 263 | 
            +
                                Some(ips)
         | 
| 264 | 
            +
                            }
         | 
| 265 | 
            +
                            Err(_) => None,
         | 
| 266 | 
            +
                        }
         | 
| 267 | 
            +
                    })?;
         | 
| 268 | 
            +
                    Ok(result)
         | 
| 269 | 
            +
                }
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                pub fn fetch_due_timers(&self) -> MagnusResult<Option<Vec<usize>>> {
         | 
| 272 | 
            +
                    call_without_gvl(|| {
         | 
| 273 | 
            +
                        let mut timers = self.timers.lock();
         | 
| 274 | 
            +
                        let mut io_waiters = self.io_waiters.lock();
         | 
| 275 | 
            +
                        let mut due_fibers: Option<Vec<usize>> = None;
         | 
| 276 | 
            +
                        while let Some(timer) = timers.peek() {
         | 
| 277 | 
            +
                            if timer.is_due() {
         | 
| 278 | 
            +
                                due_fibers.get_or_insert_default().push(timer.token.0);
         | 
| 279 | 
            +
                                if let Some(waiter) = io_waiters.remove(&timer.token) {
         | 
| 280 | 
            +
                                    let mut binding = self.registry.lock();
         | 
| 281 | 
            +
                                    // Pop the current item for the current waiter off the queue
         | 
| 282 | 
            +
                                    let queue = binding.get_mut(&waiter.fd).unwrap();
         | 
| 283 | 
            +
                                    queue.pop_front();
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                                    if let Some(head) = queue.get_mut(0) {
         | 
| 286 | 
            +
                                        // Register the next item in the queue if there is one.
         | 
| 287 | 
            +
                                        let interest = build_interest(head.readiness)?;
         | 
| 288 | 
            +
                                        self.poll
         | 
| 289 | 
            +
                                            .lock()
         | 
| 290 | 
            +
                                            .registry()
         | 
| 291 | 
            +
                                            .register(head, head.token, interest)
         | 
| 292 | 
            +
                                            .map_err(|_| {
         | 
| 293 | 
            +
                                                ItsiError::ArgumentError("Failed to deregister".to_string())
         | 
| 294 | 
            +
                                            })?;
         | 
| 295 | 
            +
                                    } else {
         | 
| 296 | 
            +
                                        // Otherwise we drop the queue altogether.
         | 
| 297 | 
            +
                                        binding.remove(&waiter.fd);
         | 
| 298 | 
            +
                                    }
         | 
| 299 | 
            +
                                }
         | 
| 300 | 
            +
                                timers.pop();
         | 
| 301 | 
            +
                            } else {
         | 
| 302 | 
            +
                                break;
         | 
| 303 | 
            +
                            }
         | 
| 304 | 
            +
                        }
         | 
| 305 | 
            +
                        Ok(due_fibers)
         | 
| 306 | 
            +
                    })
         | 
| 307 | 
            +
                }
         | 
| 308 | 
            +
            }
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            use itsi_scheduler::ItsiScheduler;
         | 
| 2 | 
            +
            use magnus::{function, method, Class, Error, Module, Object, Ruby};
         | 
| 3 | 
            +
            mod itsi_scheduler;
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            #[magnus::init]
         | 
| 6 | 
            +
            fn init(ruby: &Ruby) -> Result<(), Error> {
         | 
| 7 | 
            +
                itsi_tracing::init();
         | 
| 8 | 
            +
                let module = ruby.define_module("Itsi")?;
         | 
| 9 | 
            +
                let scheduler = module.define_class("Scheduler", ruby.class_object())?;
         | 
| 10 | 
            +
                scheduler.define_singleton_method("info", function!(ItsiScheduler::class_info, 1))?;
         | 
| 11 | 
            +
                scheduler.define_alloc_func::<ItsiScheduler>();
         | 
| 12 | 
            +
                scheduler.define_method("initialize", method!(ItsiScheduler::initialize, 0))?;
         | 
| 13 | 
            +
                scheduler.define_method("wake", method!(ItsiScheduler::wake, 0))?;
         | 
| 14 | 
            +
                scheduler.define_method(
         | 
| 15 | 
            +
                    "register_io_wait",
         | 
| 16 | 
            +
                    method!(ItsiScheduler::register_io_wait, 4),
         | 
| 17 | 
            +
                )?;
         | 
| 18 | 
            +
                scheduler.define_method("info", method!(ItsiScheduler::info, 1))?;
         | 
| 19 | 
            +
                scheduler.define_method("debug", method!(ItsiScheduler::debug, 1))?;
         | 
| 20 | 
            +
                scheduler.define_method("warn", method!(ItsiScheduler::warn, 1))?;
         | 
| 21 | 
            +
                scheduler.define_method("start_timer", method!(ItsiScheduler::start_timer, 2))?;
         | 
| 22 | 
            +
                scheduler.define_method(
         | 
| 23 | 
            +
                    "address_resolve",
         | 
| 24 | 
            +
                    method!(ItsiScheduler::address_resolve, 1),
         | 
| 25 | 
            +
                )?;
         | 
| 26 | 
            +
                scheduler.define_method("has_pending_io?", method!(ItsiScheduler::has_pending_io, 0))?;
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                scheduler.define_method(
         | 
| 29 | 
            +
                    "fetch_due_timers",
         | 
| 30 | 
            +
                    method!(ItsiScheduler::fetch_due_timers, 0),
         | 
| 31 | 
            +
                )?;
         | 
| 32 | 
            +
                scheduler.define_method(
         | 
| 33 | 
            +
                    "fetch_due_events",
         | 
| 34 | 
            +
                    method!(ItsiScheduler::fetch_due_events, 0),
         | 
| 35 | 
            +
                )?;
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                Ok(())
         | 
| 38 | 
            +
            }
         |