itsi 0.1.11 → 0.1.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 +4 -4
- data/Cargo.lock +1535 -45
- data/{sandbox/itsi_itsi_file/Itsi.rb → Itsi.rb} +19 -13
- data/Rakefile +8 -7
- data/crates/itsi_error/src/lib.rs +9 -0
- data/crates/itsi_rb_helpers/Cargo.toml +1 -0
- data/crates/itsi_rb_helpers/src/heap_value.rs +18 -0
- data/crates/itsi_rb_helpers/src/lib.rs +34 -7
- data/crates/itsi_server/Cargo.toml +69 -30
- data/crates/itsi_server/src/lib.rs +79 -147
- data/crates/itsi_server/src/{body_proxy → ruby_types/itsi_body_proxy}/big_bytes.rs +10 -5
- data/crates/itsi_server/src/{body_proxy/itsi_body_proxy.rs → ruby_types/itsi_body_proxy/mod.rs} +22 -3
- data/crates/itsi_server/src/ruby_types/itsi_grpc_request.rs +147 -0
- data/crates/itsi_server/src/ruby_types/itsi_grpc_response.rs +19 -0
- data/crates/itsi_server/src/ruby_types/itsi_grpc_stream/mod.rs +216 -0
- data/{gems/server/ext/itsi_server/src/request/itsi_request.rs → crates/itsi_server/src/ruby_types/itsi_http_request.rs} +101 -117
- data/crates/itsi_server/src/{response/itsi_response.rs → ruby_types/itsi_http_response.rs} +72 -41
- data/crates/itsi_server/src/ruby_types/itsi_server/file_watcher.rs +225 -0
- data/crates/itsi_server/src/ruby_types/itsi_server/itsi_server_config.rs +355 -0
- data/crates/itsi_server/src/ruby_types/itsi_server.rs +82 -0
- data/crates/itsi_server/src/ruby_types/mod.rs +55 -0
- data/crates/itsi_server/src/server/bind.rs +13 -5
- data/crates/itsi_server/src/server/byte_frame.rs +32 -0
- data/crates/itsi_server/src/server/cache_store.rs +74 -0
- data/crates/itsi_server/src/server/itsi_service.rs +172 -0
- data/crates/itsi_server/src/server/lifecycle_event.rs +3 -0
- data/crates/itsi_server/src/server/listener.rs +102 -2
- data/crates/itsi_server/src/server/middleware_stack/middleware.rs +153 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/allow_list.rs +47 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_api_key.rs +58 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_basic.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/auth_jwt.rs +321 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cache_control.rs +139 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/compression.rs +300 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/cors.rs +287 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/deny_list.rs +48 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/error_response.rs +127 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/etag.rs +191 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/grpc_service.rs +72 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/header_interpretation.rs +85 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/intrusion_protection.rs +195 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/log_requests.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/mod.rs +82 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/proxy.rs +216 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/rate_limit.rs +124 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/redirect.rs +76 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/request_headers.rs +43 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/response_headers.rs +34 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/ruby_app.rs +93 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/static_assets.rs +162 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/string_rewrite.rs +158 -0
- data/crates/itsi_server/src/server/middleware_stack/middlewares/token_source.rs +12 -0
- data/crates/itsi_server/src/server/middleware_stack/mod.rs +315 -0
- data/crates/itsi_server/src/server/mod.rs +8 -1
- data/crates/itsi_server/src/server/process_worker.rs +38 -12
- data/crates/itsi_server/src/server/rate_limiter.rs +565 -0
- data/crates/itsi_server/src/server/request_job.rs +11 -0
- data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +119 -42
- data/crates/itsi_server/src/server/serve_strategy/mod.rs +9 -6
- data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +256 -111
- data/crates/itsi_server/src/server/signal.rs +19 -0
- data/crates/itsi_server/src/server/static_file_server.rs +984 -0
- data/crates/itsi_server/src/server/thread_worker.rs +139 -94
- data/crates/itsi_server/src/server/types.rs +43 -0
- data/crates/itsi_server/test.md +14 -0
- data/crates/itsi_tracing/Cargo.toml +1 -0
- data/crates/itsi_tracing/src/lib.rs +216 -45
- data/docs/.gitignore +7 -0
- data/docs/.gitpod.yml +15 -0
- data/docs/Itsi.rb +17 -0
- data/docs/content/_index.md +17 -0
- data/docs/content/about.md +6 -0
- data/docs/content/docs/_index.md +18 -0
- data/docs/content/docs/first-page.md +9 -0
- data/docs/content/docs/folder/_index.md +10 -0
- data/docs/content/docs/folder/leaf.md +7 -0
- data/docs/go.mod +5 -0
- data/docs/go.sum +2 -0
- data/docs/hugo.yaml +77 -0
- data/examples/static_assets_example.rb +83 -0
- data/gems/_index.md +18 -0
- data/gems/scheduler/CODE_OF_CONDUCT.md +7 -0
- data/gems/scheduler/Cargo.lock +75 -14
- data/gems/scheduler/README.md +5 -0
- data/gems/scheduler/_index.md +7 -0
- data/gems/scheduler/itsi-scheduler.gemspec +4 -1
- data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
- data/gems/scheduler/lib/itsi/scheduler.rb +2 -2
- data/gems/scheduler/test/test_file_io.rb +0 -1
- data/gems/scheduler/test/test_itsi_scheduler.rb +1 -1
- data/gems/server/CHANGELOG.md +5 -0
- data/gems/server/CODE_OF_CONDUCT.md +7 -0
- data/gems/server/Cargo.lock +1536 -45
- data/gems/server/README.md +4 -0
- data/gems/server/_index.md +6 -0
- data/gems/server/exe/itsi +33 -74
- data/gems/server/itsi-server.gemspec +3 -2
- data/gems/server/lib/itsi/{request.rb → http_request.rb} +29 -5
- data/gems/server/lib/itsi/http_response.rb +39 -0
- data/gems/server/lib/itsi/server/Itsi.rb +11 -19
- data/gems/server/lib/itsi/server/config/dsl.rb +506 -0
- data/gems/server/lib/itsi/server/config.rb +103 -8
- data/gems/server/lib/itsi/server/default_app/default_app.rb +38 -0
- data/gems/server/lib/itsi/server/grpc_interface.rb +213 -0
- data/gems/server/lib/itsi/server/rack/handler/itsi.rb +8 -17
- data/gems/server/lib/itsi/server/rack_interface.rb +23 -4
- data/gems/server/lib/itsi/server/scheduler_interface.rb +1 -1
- data/gems/server/lib/itsi/server/scheduler_mode.rb +4 -0
- data/gems/server/lib/itsi/server/signal_trap.rb +7 -1
- data/gems/server/lib/itsi/server/version.rb +1 -1
- data/gems/server/lib/itsi/server.rb +74 -63
- data/gems/server/lib/itsi/standard_headers.rb +86 -0
- data/gems/server/test/helpers/test_helper.rb +12 -12
- data/gems/server/test/test_itsi_server.rb +2 -2
- data/lib/itsi/version.rb +1 -1
- data/sandbox/itsi_file/Gemfile +11 -0
- data/sandbox/itsi_file/Gemfile.lock +69 -0
- data/sandbox/itsi_file/Itsi.rb +276 -0
- data/sandbox/itsi_file/error.html +2 -0
- data/sandbox/itsi_file/organisations_controller.rb +20 -0
- data/sandbox/itsi_file/public/assets/image.png +0 -0
- data/sandbox/itsi_file/public/assets/index.html +1 -0
- data/sandbox/itsi_sandbox_hanami/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rack/Gemfile.lock +2 -2
- data/sandbox/itsi_sandbox_rack/config.ru +2 -15
- data/sandbox/itsi_sandbox_rails/.dockerignore +2 -5
- data/sandbox/itsi_sandbox_rails/.github/workflows/ci.yml +1 -1
- data/sandbox/itsi_sandbox_rails/.gitignore +2 -1
- data/sandbox/itsi_sandbox_rails/Dockerfile +6 -9
- data/sandbox/itsi_sandbox_rails/Gemfile +16 -22
- data/sandbox/itsi_sandbox_rails/Gemfile.lock +100 -225
- data/sandbox/itsi_sandbox_rails/app/assets/config/manifest.js +4 -0
- data/sandbox/itsi_sandbox_rails/app/assets/stylesheets/application.css +11 -6
- data/sandbox/itsi_sandbox_rails/app/channels/application_cable/channel.rb +4 -0
- data/sandbox/itsi_sandbox_rails/app/channels/application_cable/connection.rb +4 -0
- data/sandbox/itsi_sandbox_rails/app/controllers/live_controller.rb +7 -8
- data/sandbox/itsi_sandbox_rails/app/controllers/uploads_controller.rb +0 -3
- data/sandbox/itsi_sandbox_rails/app/views/layouts/application.html.erb +2 -7
- data/sandbox/itsi_sandbox_rails/bin/docker-entrypoint +3 -4
- data/sandbox/itsi_sandbox_rails/bin/setup +8 -5
- data/sandbox/itsi_sandbox_rails/config/application.rb +1 -35
- data/sandbox/itsi_sandbox_rails/config/cable.yml +3 -10
- data/sandbox/itsi_sandbox_rails/config/credentials.yml.enc +1 -1
- data/sandbox/itsi_sandbox_rails/config/database.yml +9 -19
- data/sandbox/itsi_sandbox_rails/config/environment.rb +1 -1
- data/sandbox/itsi_sandbox_rails/config/environments/development.rb +21 -12
- data/sandbox/itsi_sandbox_rails/config/environments/production.rb +49 -34
- data/sandbox/itsi_sandbox_rails/config/environments/test.rb +19 -5
- data/sandbox/itsi_sandbox_rails/config/initializers/assets.rb +5 -0
- data/sandbox/itsi_sandbox_rails/config/initializers/filter_parameter_logging.rb +1 -1
- data/sandbox/itsi_sandbox_rails/config/initializers/permissions_policy.rb +13 -0
- data/sandbox/itsi_sandbox_rails/config/puma.rb +2 -9
- data/sandbox/itsi_sandbox_rails/config.ru +0 -1
- data/sandbox/itsi_sandbox_rails/db/migrate/20250301041554_create_posts.rb +1 -1
- data/sandbox/itsi_sandbox_rails/db/schema.rb +2 -2
- data/sandbox/itsi_sandbox_rails/lib/assets/.keep +0 -0
- data/sandbox/itsi_sandbox_rails/public/404.html +66 -113
- data/sandbox/itsi_sandbox_rails/public/406-unsupported-browser.html +65 -113
- data/sandbox/itsi_sandbox_rails/public/422.html +66 -113
- data/sandbox/itsi_sandbox_rails/public/500.html +65 -113
- data/sandbox/itsi_sandbox_rails/public/icon.png +0 -0
- data/sandbox/itsi_sandbox_rails/public/icon.svg +2 -2
- data/sandbox/itsi_sandbox_rails/test/channels/application_cable/connection_test.rb +13 -0
- data/sandbox/itsi_sandbox_roda/Gemfile.lock +3 -10
- data/tasks.txt +72 -35
- metadata +89 -139
- data/crates/itsi_server/src/body_proxy/mod.rs +0 -2
- data/crates/itsi_server/src/request/itsi_request.rs +0 -298
- data/crates/itsi_server/src/request/mod.rs +0 -1
- data/crates/itsi_server/src/response/mod.rs +0 -1
- data/crates/itsi_server/src/server/itsi_server.rs +0 -288
- data/gems/scheduler/ext/itsi_error/Cargo.lock +0 -368
- data/gems/scheduler/ext/itsi_error/Cargo.toml +0 -11
- data/gems/scheduler/ext/itsi_error/src/from.rs +0 -68
- data/gems/scheduler/ext/itsi_error/src/lib.rs +0 -24
- data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +0 -15
- data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +0 -31
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.lock +0 -355
- data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +0 -10
- data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
- data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +0 -201
- data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +0 -24
- data/gems/scheduler/ext/itsi_scheduler/extconf.rb +0 -6
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
- data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
- data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +0 -38
- data/gems/scheduler/ext/itsi_server/Cargo.lock +0 -2956
- data/gems/scheduler/ext/itsi_server/Cargo.toml +0 -50
- data/gems/scheduler/ext/itsi_server/extconf.rb +0 -6
- data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
- data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
- data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/gems/scheduler/ext/itsi_server/src/env.rs +0 -43
- data/gems/scheduler/ext/itsi_server/src/lib.rs +0 -180
- data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +0 -298
- data/gems/scheduler/ext/itsi_server/src/request/mod.rs +0 -1
- data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +0 -357
- data/gems/scheduler/ext/itsi_server/src/response/mod.rs +0 -1
- data/gems/scheduler/ext/itsi_server/src/server/bind.rs +0 -174
- data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +0 -37
- data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +0 -104
- data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +0 -288
- data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
- data/gems/scheduler/ext/itsi_server/src/server/listener.rs +0 -318
- data/gems/scheduler/ext/itsi_server/src/server/mod.rs +0 -11
- data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +0 -203
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -260
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
- data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -276
- data/gems/scheduler/ext/itsi_server/src/server/signal.rs +0 -74
- data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +0 -399
- data/gems/scheduler/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
- data/gems/scheduler/ext/itsi_server/src/server/tls.rs +0 -265
- data/gems/scheduler/ext/itsi_tracing/Cargo.lock +0 -274
- data/gems/scheduler/ext/itsi_tracing/Cargo.toml +0 -16
- data/gems/scheduler/ext/itsi_tracing/src/lib.rs +0 -58
- data/gems/server/ext/itsi_error/Cargo.lock +0 -368
- data/gems/server/ext/itsi_error/Cargo.toml +0 -11
- data/gems/server/ext/itsi_error/src/from.rs +0 -68
- data/gems/server/ext/itsi_error/src/lib.rs +0 -24
- data/gems/server/ext/itsi_instrument_entry/Cargo.toml +0 -15
- data/gems/server/ext/itsi_instrument_entry/src/lib.rs +0 -31
- data/gems/server/ext/itsi_rb_helpers/Cargo.lock +0 -355
- data/gems/server/ext/itsi_rb_helpers/Cargo.toml +0 -10
- data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +0 -121
- data/gems/server/ext/itsi_rb_helpers/src/lib.rs +0 -201
- data/gems/server/ext/itsi_scheduler/Cargo.toml +0 -24
- data/gems/server/ext/itsi_scheduler/extconf.rb +0 -6
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +0 -56
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +0 -44
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +0 -44
- data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +0 -308
- data/gems/server/ext/itsi_scheduler/src/lib.rs +0 -38
- data/gems/server/ext/itsi_server/Cargo.lock +0 -2956
- data/gems/server/ext/itsi_server/Cargo.toml +0 -50
- data/gems/server/ext/itsi_server/extconf.rb +0 -6
- data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +0 -104
- data/gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +0 -122
- data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +0 -2
- data/gems/server/ext/itsi_server/src/env.rs +0 -43
- data/gems/server/ext/itsi_server/src/lib.rs +0 -180
- data/gems/server/ext/itsi_server/src/request/mod.rs +0 -1
- data/gems/server/ext/itsi_server/src/response/itsi_response.rs +0 -357
- data/gems/server/ext/itsi_server/src/response/mod.rs +0 -1
- data/gems/server/ext/itsi_server/src/server/bind.rs +0 -174
- data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +0 -37
- data/gems/server/ext/itsi_server/src/server/io_stream.rs +0 -104
- data/gems/server/ext/itsi_server/src/server/itsi_server.rs +0 -288
- data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +0 -9
- data/gems/server/ext/itsi_server/src/server/listener.rs +0 -318
- data/gems/server/ext/itsi_server/src/server/mod.rs +0 -11
- data/gems/server/ext/itsi_server/src/server/process_worker.rs +0 -203
- data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +0 -260
- data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +0 -27
- data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +0 -276
- data/gems/server/ext/itsi_server/src/server/signal.rs +0 -74
- data/gems/server/ext/itsi_server/src/server/thread_worker.rs +0 -399
- data/gems/server/ext/itsi_server/src/server/tls/locked_dir_cache.rs +0 -132
- data/gems/server/ext/itsi_server/src/server/tls.rs +0 -265
- data/gems/server/ext/itsi_tracing/Cargo.lock +0 -274
- data/gems/server/ext/itsi_tracing/Cargo.toml +0 -16
- data/gems/server/ext/itsi_tracing/src/lib.rs +0 -58
- data/gems/server/lib/itsi/server/options_dsl.rb +0 -401
- data/gems/server/lib/itsi/stream_io.rb +0 -38
- data/location_dsl.rb +0 -381
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/docker-setup.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-app-boot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-deploy.sample +0 -14
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/post-proxy-reboot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-app-boot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-build.sample +0 -51
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-connect.sample +0 -47
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-deploy.sample +0 -109
- data/sandbox/itsi_sandbox_rails/.kamal/hooks/pre-proxy-reboot.sample +0 -3
- data/sandbox/itsi_sandbox_rails/.kamal/secrets +0 -17
- data/sandbox/itsi_sandbox_rails/bin/dev +0 -2
- data/sandbox/itsi_sandbox_rails/bin/jobs +0 -6
- data/sandbox/itsi_sandbox_rails/bin/kamal +0 -27
- data/sandbox/itsi_sandbox_rails/bin/thrust +0 -5
- data/sandbox/itsi_sandbox_rails/config/cache.yml +0 -16
- data/sandbox/itsi_sandbox_rails/config/deploy.yml +0 -116
- data/sandbox/itsi_sandbox_rails/config/queue.yml +0 -18
- data/sandbox/itsi_sandbox_rails/config/recurring.yml +0 -10
- data/sandbox/itsi_sandbox_rails/db/cable_schema.rb +0 -11
- data/sandbox/itsi_sandbox_rails/db/cache_schema.rb +0 -14
- data/sandbox/itsi_sandbox_rails/db/queue_schema.rb +0 -129
- data/sandbox/itsi_sandbox_rails/public/400.html +0 -114
- data/sandbox/itsi_sandbox_rails/test/fixtures/posts.yml +0 -9
- data/sandbox/itsi_sandbox_rails/test/models/post_test.rb +0 -7
- /data/{sandbox/itsi_sandbox_rails/script/.keep → crates/_index.md} +0 -0
- /data/gems/server/lib/itsi/{index.html → server/default_app/index.html} +0 -0
@@ -0,0 +1,225 @@
|
|
1
|
+
use derive_more::Debug;
|
2
|
+
use globset::{Glob, GlobSet, GlobSetBuilder};
|
3
|
+
use magnus::error::Result;
|
4
|
+
use nix::unistd::{close, fork, pipe, read};
|
5
|
+
use notify::{event::ModifyKind, EventKind, RecommendedWatcher};
|
6
|
+
use notify::{Event, RecursiveMode, Watcher};
|
7
|
+
use std::path::Path;
|
8
|
+
use std::sync::mpsc::Sender;
|
9
|
+
use std::time::{Duration, Instant};
|
10
|
+
use std::{collections::HashSet, fs};
|
11
|
+
use std::{
|
12
|
+
os::fd::{AsRawFd, IntoRawFd, OwnedFd},
|
13
|
+
path::PathBuf,
|
14
|
+
process::Command,
|
15
|
+
sync::mpsc,
|
16
|
+
thread::{self},
|
17
|
+
};
|
18
|
+
|
19
|
+
/// Represents a set of patterns and commands.
|
20
|
+
#[derive(Debug, Clone)]
|
21
|
+
struct PatternGroup {
|
22
|
+
base_dir: PathBuf,
|
23
|
+
glob_set: GlobSet,
|
24
|
+
pattern: String,
|
25
|
+
commands: Vec<Vec<String>>,
|
26
|
+
last_triggered: Option<Instant>,
|
27
|
+
}
|
28
|
+
|
29
|
+
/// Extracts the base directory from a wildcard pattern by taking the portion up to the first
|
30
|
+
/// component that contains a wildcard character.
|
31
|
+
fn extract_and_canonicalize_base_dir(pattern: &str) -> PathBuf {
|
32
|
+
if !(pattern.contains("*") || pattern.contains("?") || pattern.contains('[')) {
|
33
|
+
if let Ok(metadata) = fs::metadata(pattern) {
|
34
|
+
if metadata.is_dir() {
|
35
|
+
return fs::canonicalize(pattern).unwrap();
|
36
|
+
}
|
37
|
+
if metadata.is_file() {
|
38
|
+
return fs::canonicalize(pattern)
|
39
|
+
.unwrap()
|
40
|
+
.parent()
|
41
|
+
.unwrap()
|
42
|
+
.to_path_buf();
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
let path = Path::new(pattern);
|
48
|
+
let mut base = PathBuf::new();
|
49
|
+
for comp in path.components() {
|
50
|
+
let comp_str = comp.as_os_str().to_string_lossy();
|
51
|
+
if comp_str.contains('*') || comp_str.contains('?') || comp_str.contains('[') {
|
52
|
+
break;
|
53
|
+
} else {
|
54
|
+
base.push(comp);
|
55
|
+
}
|
56
|
+
}
|
57
|
+
// If no base was built, default to "."
|
58
|
+
let base = if base.as_os_str().is_empty() || !base.exists() {
|
59
|
+
PathBuf::from(".")
|
60
|
+
} else {
|
61
|
+
base
|
62
|
+
};
|
63
|
+
|
64
|
+
// Canonicalize to get the absolute path.
|
65
|
+
fs::canonicalize(&base).unwrap_or(base)
|
66
|
+
}
|
67
|
+
|
68
|
+
/// Minimum time between triggering the same pattern group (debounce time)
|
69
|
+
const DEBOUNCE_DURATION: Duration = Duration::from_millis(500);
|
70
|
+
|
71
|
+
pub fn watch_groups(pattern_groups: Vec<(String, Vec<Vec<String>>)>) -> Result<Option<OwnedFd>> {
|
72
|
+
let (r_fd, w_fd): (OwnedFd, OwnedFd) = pipe().map_err(|e| {
|
73
|
+
magnus::Error::new(
|
74
|
+
magnus::exception::exception(),
|
75
|
+
format!("Failed to create watcher pipe: {}", e),
|
76
|
+
)
|
77
|
+
})?;
|
78
|
+
|
79
|
+
let fork_result = unsafe {
|
80
|
+
fork().map_err(|e| {
|
81
|
+
magnus::Error::new(
|
82
|
+
magnus::exception::exception(),
|
83
|
+
format!("Failed to fork file watcher: {}", e),
|
84
|
+
)
|
85
|
+
})
|
86
|
+
}?;
|
87
|
+
|
88
|
+
if fork_result.is_child() {
|
89
|
+
let _ = close(w_fd.into_raw_fd());
|
90
|
+
thread::spawn(move || {
|
91
|
+
let mut buf = [0u8; 1];
|
92
|
+
loop {
|
93
|
+
match read(r_fd.as_raw_fd(), &mut buf) {
|
94
|
+
Ok(0) => {
|
95
|
+
std::process::exit(0);
|
96
|
+
}
|
97
|
+
Ok(_) => {}
|
98
|
+
Err(_) => {
|
99
|
+
std::process::exit(0);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
});
|
104
|
+
|
105
|
+
let mut groups = Vec::new();
|
106
|
+
for (pattern, commands) in pattern_groups.into_iter() {
|
107
|
+
let base_dir = extract_and_canonicalize_base_dir(&pattern);
|
108
|
+
let glob = Glob::new(&pattern).map_err(|e| {
|
109
|
+
magnus::Error::new(
|
110
|
+
magnus::exception::exception(),
|
111
|
+
format!("Failed to create watch glob: {}", e),
|
112
|
+
)
|
113
|
+
})?;
|
114
|
+
let glob_set = GlobSetBuilder::new().add(glob).build().map_err(|e| {
|
115
|
+
magnus::Error::new(
|
116
|
+
magnus::exception::exception(),
|
117
|
+
format!("Failed to create watch glob set: {}", e),
|
118
|
+
)
|
119
|
+
})?;
|
120
|
+
groups.push(PatternGroup {
|
121
|
+
base_dir,
|
122
|
+
glob_set,
|
123
|
+
pattern,
|
124
|
+
commands,
|
125
|
+
last_triggered: None,
|
126
|
+
});
|
127
|
+
}
|
128
|
+
|
129
|
+
// Create a channel and a watcher.
|
130
|
+
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
|
131
|
+
let sender = tx.clone();
|
132
|
+
fn event_fn(sender: Sender<notify::Result<Event>>) -> impl Fn(notify::Result<Event>) {
|
133
|
+
move |res| match res {
|
134
|
+
Ok(event) => {
|
135
|
+
sender.send(Ok(event)).unwrap();
|
136
|
+
}
|
137
|
+
Err(e) => println!("watch error: {:?}", e),
|
138
|
+
}
|
139
|
+
}
|
140
|
+
|
141
|
+
let mut watched_dirs = HashSet::new();
|
142
|
+
let mut watcher: RecommendedWatcher =
|
143
|
+
notify::recommended_watcher(event_fn(sender)).expect("Failed to create watcher");
|
144
|
+
for group in &groups {
|
145
|
+
if watched_dirs.insert(group.base_dir.clone()) {
|
146
|
+
watcher
|
147
|
+
.watch(&group.base_dir, RecursiveMode::Recursive)
|
148
|
+
.expect("Failed to add watch");
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
// Main event loop.
|
153
|
+
for res in rx {
|
154
|
+
match res {
|
155
|
+
Ok(event) => {
|
156
|
+
if !matches!(event.kind, EventKind::Modify(ModifyKind::Metadata(_))) {
|
157
|
+
continue;
|
158
|
+
}
|
159
|
+
let now = Instant::now();
|
160
|
+
for group in &mut groups {
|
161
|
+
for path in event.paths.iter() {
|
162
|
+
if let Ok(rel_path) = path.strip_prefix(&group.base_dir) {
|
163
|
+
if group.glob_set.is_match(rel_path)
|
164
|
+
|| rel_path.to_str().is_some_and(|s| s == group.pattern)
|
165
|
+
{
|
166
|
+
// Check if we should debounce this event
|
167
|
+
if let Some(last_triggered) = group.last_triggered {
|
168
|
+
if now.duration_since(last_triggered) < DEBOUNCE_DURATION {
|
169
|
+
// Skip this event as we've recently triggered for this pattern
|
170
|
+
continue;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
// Update the last triggered time
|
175
|
+
group.last_triggered = Some(now);
|
176
|
+
|
177
|
+
// Execute the commands for this group.
|
178
|
+
for command in &group.commands {
|
179
|
+
if command.is_empty() {
|
180
|
+
continue;
|
181
|
+
}
|
182
|
+
let mut cmd = Command::new(&command[0]);
|
183
|
+
if command.len() > 1 {
|
184
|
+
cmd.args(&command[1..]);
|
185
|
+
}
|
186
|
+
match cmd.spawn() {
|
187
|
+
Ok(mut child) => {
|
188
|
+
if let Err(e) = child.wait() {
|
189
|
+
eprintln!(
|
190
|
+
"Command {:?} failed: {:?}",
|
191
|
+
command, e
|
192
|
+
);
|
193
|
+
}
|
194
|
+
}
|
195
|
+
Err(e) => {
|
196
|
+
eprintln!(
|
197
|
+
"Failed to execute command {:?}: {:?}",
|
198
|
+
command, e
|
199
|
+
);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
}
|
203
|
+
break;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
}
|
207
|
+
}
|
208
|
+
}
|
209
|
+
Err(e) => println!("Watch error: {:?}", e),
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
// Clean up the watches.
|
214
|
+
for group in &groups {
|
215
|
+
watcher
|
216
|
+
.unwatch(&group.base_dir)
|
217
|
+
.expect("Failed to remove watch");
|
218
|
+
}
|
219
|
+
drop(watcher);
|
220
|
+
std::process::exit(0);
|
221
|
+
} else {
|
222
|
+
let _ = close(r_fd.into_raw_fd());
|
223
|
+
Ok(Some(w_fd))
|
224
|
+
}
|
225
|
+
}
|
@@ -0,0 +1,355 @@
|
|
1
|
+
use super::file_watcher::{self};
|
2
|
+
use crate::{
|
3
|
+
ruby_types::ITSI_SERVER_CONFIG,
|
4
|
+
server::{bind::Bind, listener::Listener, middleware_stack::MiddlewareSet},
|
5
|
+
};
|
6
|
+
use derive_more::Debug;
|
7
|
+
use itsi_rb_helpers::{call_with_gvl, print_rb_backtrace, HeapVal, HeapValue};
|
8
|
+
use itsi_tracing::set_level;
|
9
|
+
use magnus::{
|
10
|
+
block::Proc,
|
11
|
+
error::Result,
|
12
|
+
value::{LazyId, ReprValue},
|
13
|
+
RArray, RHash, Ruby, Symbol, Value,
|
14
|
+
};
|
15
|
+
use nix::{
|
16
|
+
fcntl::{fcntl, FcntlArg, FdFlag},
|
17
|
+
unistd::{close, dup},
|
18
|
+
};
|
19
|
+
use parking_lot::{Mutex, RwLock};
|
20
|
+
use std::{
|
21
|
+
collections::HashMap,
|
22
|
+
os::fd::{AsRawFd, OwnedFd, RawFd},
|
23
|
+
path::PathBuf,
|
24
|
+
sync::{Arc, OnceLock},
|
25
|
+
};
|
26
|
+
|
27
|
+
static DEFAULT_BIND: &str = "http://localhost:3000";
|
28
|
+
static ID_BUILD_CONFIG: LazyId = LazyId::new("build_config");
|
29
|
+
static ID_RELOAD_EXEC: LazyId = LazyId::new("reload_exec");
|
30
|
+
|
31
|
+
#[derive(Debug, Clone)]
|
32
|
+
pub struct ItsiServerConfig {
|
33
|
+
pub cli_params: Arc<HeapValue<RHash>>,
|
34
|
+
pub itsifile_path: Option<PathBuf>,
|
35
|
+
pub itsi_config_proc: Arc<Option<HeapValue<Proc>>>,
|
36
|
+
#[debug(skip)]
|
37
|
+
pub server_params: Arc<RwLock<Arc<ServerParams>>>,
|
38
|
+
pub watcher_fd: Arc<Option<OwnedFd>>,
|
39
|
+
}
|
40
|
+
|
41
|
+
#[derive(Debug)]
|
42
|
+
pub struct ServerParams {
|
43
|
+
/// Cluster params
|
44
|
+
pub workers: u8,
|
45
|
+
pub worker_memory_limit: Option<u64>,
|
46
|
+
pub silence: bool,
|
47
|
+
pub shutdown_timeout: f64,
|
48
|
+
pub hooks: HashMap<String, HeapValue<Proc>>,
|
49
|
+
pub preload: bool,
|
50
|
+
|
51
|
+
pub notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>>,
|
52
|
+
/// Worker params
|
53
|
+
pub threads: u8,
|
54
|
+
pub script_name: String,
|
55
|
+
pub streamable_body: bool,
|
56
|
+
pub multithreaded_reactor: bool,
|
57
|
+
pub scheduler_class: Option<String>,
|
58
|
+
pub oob_gc_responses_threshold: Option<u64>,
|
59
|
+
pub middleware_loader: HeapValue<Proc>,
|
60
|
+
pub default_app_loader: HeapValue<Proc>,
|
61
|
+
pub middleware: OnceLock<MiddlewareSet>,
|
62
|
+
pub binds: Vec<Bind>,
|
63
|
+
#[debug(skip)]
|
64
|
+
pub(crate) listeners: Mutex<Vec<Listener>>,
|
65
|
+
listener_info: Mutex<HashMap<String, i32>>,
|
66
|
+
}
|
67
|
+
|
68
|
+
impl ServerParams {
|
69
|
+
pub fn preload_ruby(self: &Arc<Self>) -> Result<()> {
|
70
|
+
call_with_gvl(|ruby| -> Result<()> {
|
71
|
+
if self
|
72
|
+
.scheduler_class
|
73
|
+
.as_ref()
|
74
|
+
.is_some_and(|t| t == "Itsi::Scheduler")
|
75
|
+
{
|
76
|
+
ruby.require("itsi/scheduler")?;
|
77
|
+
}
|
78
|
+
let default_app: HeapVal = self.default_app_loader.call::<_, Value>(())?.into();
|
79
|
+
let middleware = MiddlewareSet::new(
|
80
|
+
self.middleware_loader
|
81
|
+
.call::<_, Option<Value>>(())
|
82
|
+
.inspect_err(|e| {
|
83
|
+
if let Some(err_value) = e.value() {
|
84
|
+
print_rb_backtrace(err_value);
|
85
|
+
}
|
86
|
+
})?
|
87
|
+
.map(|mw| mw.into()),
|
88
|
+
default_app,
|
89
|
+
)?;
|
90
|
+
self.middleware.set(middleware).map_err(|_| {
|
91
|
+
magnus::Error::new(
|
92
|
+
magnus::exception::runtime_error(),
|
93
|
+
"Failed to set middleware",
|
94
|
+
)
|
95
|
+
})?;
|
96
|
+
Ok(())
|
97
|
+
})?;
|
98
|
+
Ok(())
|
99
|
+
}
|
100
|
+
|
101
|
+
fn from_rb_hash(rb_param_hash: RHash) -> Result<ServerParams> {
|
102
|
+
let workers = rb_param_hash
|
103
|
+
.fetch::<_, Option<u8>>("workers")?
|
104
|
+
.unwrap_or(num_cpus::get() as u8);
|
105
|
+
let worker_memory_limit: Option<u64> = rb_param_hash.fetch("worker_memory_limit")?;
|
106
|
+
let silence: bool = rb_param_hash.fetch("silence")?;
|
107
|
+
let multithreaded_reactor: bool = rb_param_hash.fetch("multithreaded_reactor")?;
|
108
|
+
let shutdown_timeout: f64 = rb_param_hash.fetch("shutdown_timeout")?;
|
109
|
+
|
110
|
+
let hooks: Option<RHash> = rb_param_hash.fetch("hooks")?;
|
111
|
+
let hooks = hooks
|
112
|
+
.map(|rhash| -> Result<HashMap<String, HeapValue<Proc>>> {
|
113
|
+
let mut hook_map: HashMap<String, HeapValue<Proc>> = HashMap::new();
|
114
|
+
for pair in rhash.enumeratorize::<_, ()>("each", ()) {
|
115
|
+
if let Some(pair_value) = RArray::from_value(pair?) {
|
116
|
+
if let (Ok(key), Ok(value)) =
|
117
|
+
(pair_value.entry::<Value>(0), pair_value.entry::<Proc>(1))
|
118
|
+
{
|
119
|
+
hook_map.insert(key.to_string(), HeapValue::from(value));
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
Ok(hook_map)
|
124
|
+
})
|
125
|
+
.transpose()?
|
126
|
+
.unwrap_or_default();
|
127
|
+
let preload: bool = rb_param_hash.fetch("preload")?;
|
128
|
+
let notify_watchers: Option<Vec<(String, Vec<Vec<String>>)>> =
|
129
|
+
rb_param_hash.fetch("notify_watchers")?;
|
130
|
+
let threads: u8 = rb_param_hash.fetch("threads")?;
|
131
|
+
let script_name: String = rb_param_hash.fetch("script_name")?;
|
132
|
+
let streamable_body: bool = rb_param_hash.fetch("streamable_body")?;
|
133
|
+
let scheduler_class: Option<String> = rb_param_hash.fetch("scheduler_class")?;
|
134
|
+
let oob_gc_responses_threshold: Option<u64> =
|
135
|
+
rb_param_hash.fetch("oob_gc_responses_threshold")?;
|
136
|
+
let middleware_loader: Proc = rb_param_hash.fetch("middleware_loader")?;
|
137
|
+
let default_app_loader: Proc = rb_param_hash.fetch("default_app_loader")?;
|
138
|
+
let log_level: Option<String> = rb_param_hash.fetch("log_level")?;
|
139
|
+
|
140
|
+
if let Some(level) = log_level {
|
141
|
+
set_level(&level);
|
142
|
+
}
|
143
|
+
|
144
|
+
let binds: Option<Vec<String>> = rb_param_hash.fetch("binds")?;
|
145
|
+
let binds = binds
|
146
|
+
.unwrap_or_else(|| vec![DEFAULT_BIND.to_string()])
|
147
|
+
.into_iter()
|
148
|
+
.map(|s| s.parse())
|
149
|
+
.collect::<itsi_error::Result<Vec<Bind>>>()?;
|
150
|
+
|
151
|
+
let listeners = if let Some(preexisting_listeners) =
|
152
|
+
rb_param_hash.delete::<_, Option<String>>("listeners")?
|
153
|
+
{
|
154
|
+
let bind_to_fd_map: HashMap<String, i32> = serde_json::from_str(&preexisting_listeners)
|
155
|
+
.map_err(|e| {
|
156
|
+
magnus::Error::new(
|
157
|
+
magnus::exception::exception(),
|
158
|
+
format!("Invalid listener info: {}", e),
|
159
|
+
)
|
160
|
+
})?;
|
161
|
+
|
162
|
+
binds
|
163
|
+
.iter()
|
164
|
+
.cloned()
|
165
|
+
.map(|bind| {
|
166
|
+
if let Some(fd) = bind_to_fd_map.get(&bind.listener_address_string()) {
|
167
|
+
Listener::inherit_fd(bind, *fd)
|
168
|
+
} else {
|
169
|
+
Listener::try_from(bind)
|
170
|
+
}
|
171
|
+
})
|
172
|
+
.collect::<std::result::Result<Vec<Listener>, _>>()?
|
173
|
+
.into_iter()
|
174
|
+
.collect::<Vec<_>>()
|
175
|
+
} else {
|
176
|
+
binds
|
177
|
+
.iter()
|
178
|
+
.cloned()
|
179
|
+
.map(Listener::try_from)
|
180
|
+
.collect::<std::result::Result<Vec<Listener>, _>>()?
|
181
|
+
.into_iter()
|
182
|
+
.collect::<Vec<_>>()
|
183
|
+
};
|
184
|
+
|
185
|
+
let listener_info = listeners
|
186
|
+
.iter()
|
187
|
+
.map(|listener| {
|
188
|
+
listener.handover().map_err(|e| {
|
189
|
+
magnus::Error::new(magnus::exception::runtime_error(), e.to_string())
|
190
|
+
})
|
191
|
+
})
|
192
|
+
.collect::<Result<HashMap<String, i32>>>()?;
|
193
|
+
|
194
|
+
Ok(ServerParams {
|
195
|
+
workers,
|
196
|
+
worker_memory_limit,
|
197
|
+
silence,
|
198
|
+
multithreaded_reactor,
|
199
|
+
shutdown_timeout,
|
200
|
+
hooks,
|
201
|
+
preload,
|
202
|
+
notify_watchers,
|
203
|
+
threads,
|
204
|
+
script_name,
|
205
|
+
streamable_body,
|
206
|
+
scheduler_class,
|
207
|
+
oob_gc_responses_threshold,
|
208
|
+
binds,
|
209
|
+
listener_info: Mutex::new(listener_info),
|
210
|
+
listeners: Mutex::new(listeners),
|
211
|
+
middleware_loader: middleware_loader.into(),
|
212
|
+
default_app_loader: default_app_loader.into(),
|
213
|
+
middleware: OnceLock::new(),
|
214
|
+
})
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
impl ItsiServerConfig {
|
219
|
+
pub fn new(
|
220
|
+
ruby: &Ruby,
|
221
|
+
cli_params: RHash,
|
222
|
+
itsifile_path: Option<PathBuf>,
|
223
|
+
itsi_config_proc: Option<Proc>,
|
224
|
+
) -> Result<Self> {
|
225
|
+
let itsi_config_proc = Arc::new(itsi_config_proc.map(HeapValue::from));
|
226
|
+
let server_params = Self::combine_params(
|
227
|
+
ruby,
|
228
|
+
cli_params,
|
229
|
+
itsifile_path.as_ref(),
|
230
|
+
itsi_config_proc.clone(),
|
231
|
+
)?;
|
232
|
+
cli_params.delete::<_, Value>(Symbol::new("listeners"))?;
|
233
|
+
|
234
|
+
let watcher_fd = if let Some(watchers) = server_params.notify_watchers.clone() {
|
235
|
+
file_watcher::watch_groups(watchers)?
|
236
|
+
} else {
|
237
|
+
None
|
238
|
+
};
|
239
|
+
|
240
|
+
Ok(ItsiServerConfig {
|
241
|
+
cli_params: Arc::new(cli_params.into()),
|
242
|
+
server_params: RwLock::new(server_params.clone()).into(),
|
243
|
+
itsi_config_proc,
|
244
|
+
itsifile_path,
|
245
|
+
watcher_fd: watcher_fd.into(),
|
246
|
+
})
|
247
|
+
}
|
248
|
+
|
249
|
+
/// Reload
|
250
|
+
pub fn reload(self: Arc<Self>, cluster_worker: bool) -> Result<bool> {
|
251
|
+
let server_params = call_with_gvl(|ruby| {
|
252
|
+
Self::combine_params(
|
253
|
+
&ruby,
|
254
|
+
self.cli_params.cloned(),
|
255
|
+
self.itsifile_path.as_ref(),
|
256
|
+
self.itsi_config_proc.clone(),
|
257
|
+
)
|
258
|
+
})?;
|
259
|
+
|
260
|
+
let is_single_mode = self.server_params.read().workers == 1;
|
261
|
+
|
262
|
+
let requires_exec = if !is_single_mode && !server_params.preload {
|
263
|
+
// In cluster mode children are cycled during a reload
|
264
|
+
// and if preload is disabled, will get a clean memory slate,
|
265
|
+
// so we don't need to exec.
|
266
|
+
false
|
267
|
+
} else {
|
268
|
+
// In non-cluster mode, or when preloading is enabled, we shouldn't try to
|
269
|
+
// reload inside the existing process (as new code may conflict with old),
|
270
|
+
// and should re-exec instead.
|
271
|
+
true
|
272
|
+
};
|
273
|
+
|
274
|
+
*self.server_params.write() = server_params.clone();
|
275
|
+
Ok(requires_exec && (cluster_worker || is_single_mode))
|
276
|
+
}
|
277
|
+
|
278
|
+
fn combine_params(
|
279
|
+
ruby: &Ruby,
|
280
|
+
cli_params: RHash,
|
281
|
+
itsifile_path: Option<&PathBuf>,
|
282
|
+
itsi_config_proc: Arc<Option<HeapValue<Proc>>>,
|
283
|
+
) -> Result<Arc<ServerParams>> {
|
284
|
+
let inner = itsi_config_proc
|
285
|
+
.as_ref()
|
286
|
+
.clone()
|
287
|
+
.map(|hv| hv.clone().inner());
|
288
|
+
let rb_param_hash: RHash = ruby.get_inner_ref(&ITSI_SERVER_CONFIG).funcall(
|
289
|
+
*ID_BUILD_CONFIG,
|
290
|
+
(cli_params, itsifile_path.cloned(), inner),
|
291
|
+
)?;
|
292
|
+
Ok(Arc::new(ServerParams::from_rb_hash(rb_param_hash)?))
|
293
|
+
}
|
294
|
+
|
295
|
+
fn clear_cloexec(fd: RawFd) -> nix::Result<()> {
|
296
|
+
let current_flags = fcntl(fd, FcntlArg::F_GETFD)?;
|
297
|
+
let mut flags = FdFlag::from_bits_truncate(current_flags);
|
298
|
+
// Remove the FD_CLOEXEC flag
|
299
|
+
flags.remove(FdFlag::FD_CLOEXEC);
|
300
|
+
// Set the new flags back on the file descriptor
|
301
|
+
fcntl(fd, FcntlArg::F_SETFD(flags))?;
|
302
|
+
Ok(())
|
303
|
+
}
|
304
|
+
|
305
|
+
pub fn dup_fds(self: &Arc<Self>) -> Result<()> {
|
306
|
+
let binding = self.server_params.read();
|
307
|
+
let mut listener_info_guard = binding.listener_info.lock();
|
308
|
+
let dupped_fd_map = listener_info_guard
|
309
|
+
.iter()
|
310
|
+
.map(|(str, fd)| {
|
311
|
+
let dupped_fd = dup(*fd).map_err(|errno| {
|
312
|
+
magnus::Error::new(
|
313
|
+
magnus::exception::exception(),
|
314
|
+
format!("Errno {} while trying to dup {}", errno, fd),
|
315
|
+
)
|
316
|
+
})?;
|
317
|
+
Self::clear_cloexec(dupped_fd).map_err(|e| {
|
318
|
+
magnus::Error::new(
|
319
|
+
magnus::exception::exception(),
|
320
|
+
format!("Failed to clear cloexec flag for fd {}: {}", dupped_fd, e),
|
321
|
+
)
|
322
|
+
})?;
|
323
|
+
Ok((str.clone(), dupped_fd))
|
324
|
+
})
|
325
|
+
.collect::<Result<HashMap<String, i32>>>()?;
|
326
|
+
*listener_info_guard = dupped_fd_map;
|
327
|
+
Ok(())
|
328
|
+
}
|
329
|
+
|
330
|
+
pub fn stop_watcher(self: &Arc<Self>) -> Result<()> {
|
331
|
+
if let Some(r_fd) = self.watcher_fd.as_ref() {
|
332
|
+
close(r_fd.as_raw_fd()).ok();
|
333
|
+
}
|
334
|
+
Ok(())
|
335
|
+
}
|
336
|
+
|
337
|
+
pub fn reload_exec(self: &Arc<Self>) -> Result<()> {
|
338
|
+
let listener_json =
|
339
|
+
serde_json::to_string(&self.server_params.read().listener_info.lock().clone())
|
340
|
+
.map_err(|e| {
|
341
|
+
magnus::Error::new(
|
342
|
+
magnus::exception::exception(),
|
343
|
+
format!("Invalid listener info: {}", e),
|
344
|
+
)
|
345
|
+
})?;
|
346
|
+
|
347
|
+
self.stop_watcher()?;
|
348
|
+
call_with_gvl(|ruby| -> Result<()> {
|
349
|
+
ruby.get_inner_ref(&ITSI_SERVER_CONFIG)
|
350
|
+
.funcall::<_, _, Value>(*ID_RELOAD_EXEC, (listener_json,))?;
|
351
|
+
Ok(())
|
352
|
+
})?;
|
353
|
+
Ok(())
|
354
|
+
}
|
355
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
use crate::server::{
|
2
|
+
serve_strategy::{cluster_mode::ClusterMode, single_mode::SingleMode, ServeStrategy},
|
3
|
+
signal::{clear_signal_handlers, reset_signal_handlers, send_shutdown_event},
|
4
|
+
};
|
5
|
+
use itsi_rb_helpers::{call_without_gvl, print_rb_backtrace};
|
6
|
+
use itsi_server_config::ItsiServerConfig;
|
7
|
+
use itsi_tracing::{error, run_silently};
|
8
|
+
use magnus::{block::Proc, error::Result, RHash, Ruby};
|
9
|
+
use parking_lot::Mutex;
|
10
|
+
use std::{path::PathBuf, sync::Arc};
|
11
|
+
use tracing::{info, instrument};
|
12
|
+
mod file_watcher;
|
13
|
+
pub mod itsi_server_config;
|
14
|
+
#[magnus::wrap(class = "Itsi::Server", free_immediately, size)]
|
15
|
+
#[derive(Clone)]
|
16
|
+
pub struct ItsiServer {
|
17
|
+
pub config: Arc<Mutex<Arc<ItsiServerConfig>>>,
|
18
|
+
}
|
19
|
+
|
20
|
+
impl ItsiServer {
|
21
|
+
pub fn new(
|
22
|
+
ruby: &Ruby,
|
23
|
+
cli_params: RHash,
|
24
|
+
itsifile_path: Option<PathBuf>,
|
25
|
+
itsi_config_proc: Option<Proc>,
|
26
|
+
) -> Result<Self> {
|
27
|
+
Ok(Self {
|
28
|
+
config: Arc::new(Mutex::new(Arc::new(ItsiServerConfig::new(
|
29
|
+
ruby,
|
30
|
+
cli_params,
|
31
|
+
itsifile_path,
|
32
|
+
itsi_config_proc,
|
33
|
+
)?))),
|
34
|
+
})
|
35
|
+
}
|
36
|
+
|
37
|
+
pub fn stop(&self) -> Result<()> {
|
38
|
+
send_shutdown_event();
|
39
|
+
Ok(())
|
40
|
+
}
|
41
|
+
|
42
|
+
#[instrument(skip(self))]
|
43
|
+
pub fn start(&self) -> Result<()> {
|
44
|
+
let result = if self.config.lock().server_params.read().silence {
|
45
|
+
run_silently(|| self.build_and_run_strategy())
|
46
|
+
} else {
|
47
|
+
info!("Itsi - Rolling into action. 💨 ⚪ ");
|
48
|
+
self.build_and_run_strategy()
|
49
|
+
};
|
50
|
+
if let Err(e) = result {
|
51
|
+
if let Some(err_value) = e.value() {
|
52
|
+
print_rb_backtrace(err_value);
|
53
|
+
}
|
54
|
+
return Err(e);
|
55
|
+
}
|
56
|
+
Ok(())
|
57
|
+
}
|
58
|
+
|
59
|
+
pub(crate) fn build_strategy(&self) -> Result<ServeStrategy> {
|
60
|
+
let server_config = self.config.lock();
|
61
|
+
Ok(if server_config.server_params.read().workers > 1 {
|
62
|
+
ServeStrategy::Cluster(Arc::new(ClusterMode::new(server_config.clone())))
|
63
|
+
} else {
|
64
|
+
ServeStrategy::Single(Arc::new(SingleMode::new(server_config.clone())?))
|
65
|
+
})
|
66
|
+
}
|
67
|
+
|
68
|
+
fn build_and_run_strategy(&self) -> Result<()> {
|
69
|
+
reset_signal_handlers();
|
70
|
+
call_without_gvl(move || -> Result<()> {
|
71
|
+
let strategy = self.build_strategy()?;
|
72
|
+
if let Err(e) = strategy.clone().run() {
|
73
|
+
error!("Error running server: {}", e);
|
74
|
+
strategy.stop()?;
|
75
|
+
}
|
76
|
+
Ok(())
|
77
|
+
})?;
|
78
|
+
clear_signal_handlers();
|
79
|
+
info!("Server stopped");
|
80
|
+
Ok(())
|
81
|
+
}
|
82
|
+
}
|