itsi 0.1.0 → 0.1.3

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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +524 -44
  3. data/Rakefile +22 -33
  4. data/crates/itsi_error/Cargo.toml +2 -0
  5. data/crates/itsi_error/src/from.rs +70 -0
  6. data/crates/itsi_error/src/lib.rs +10 -37
  7. data/crates/itsi_instrument_entry/Cargo.toml +15 -0
  8. data/crates/itsi_instrument_entry/src/lib.rs +31 -0
  9. data/crates/itsi_rb_helpers/Cargo.toml +2 -0
  10. data/crates/itsi_rb_helpers/src/heap_value.rs +121 -0
  11. data/crates/itsi_rb_helpers/src/lib.rs +90 -10
  12. data/crates/itsi_scheduler/Cargo.toml +9 -1
  13. data/crates/itsi_scheduler/extconf.rb +1 -1
  14. data/crates/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  15. data/crates/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  16. data/crates/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  17. data/crates/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  18. data/crates/itsi_scheduler/src/lib.rs +31 -10
  19. data/crates/itsi_server/Cargo.toml +14 -2
  20. data/crates/itsi_server/extconf.rb +1 -1
  21. data/crates/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  22. data/crates/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  23. data/crates/itsi_server/src/body_proxy/mod.rs +2 -0
  24. data/crates/itsi_server/src/lib.rs +58 -7
  25. data/crates/itsi_server/src/request/itsi_request.rs +238 -104
  26. data/crates/itsi_server/src/response/itsi_response.rs +347 -0
  27. data/crates/itsi_server/src/response/mod.rs +1 -0
  28. data/crates/itsi_server/src/server/bind.rs +50 -20
  29. data/crates/itsi_server/src/server/bind_protocol.rs +37 -0
  30. data/crates/itsi_server/src/server/io_stream.rs +104 -0
  31. data/crates/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
  32. data/crates/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
  33. data/crates/itsi_server/src/server/itsi_server.rs +196 -134
  34. data/crates/itsi_server/src/server/lifecycle_event.rs +9 -0
  35. data/crates/itsi_server/src/server/listener.rs +184 -127
  36. data/crates/itsi_server/src/server/mod.rs +7 -1
  37. data/crates/itsi_server/src/server/process_worker.rs +196 -0
  38. data/crates/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
  39. data/crates/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  40. data/crates/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
  41. data/crates/itsi_server/src/server/signal.rs +70 -0
  42. data/crates/itsi_server/src/server/thread_worker.rs +368 -0
  43. data/crates/itsi_server/src/server/tls.rs +42 -28
  44. data/crates/itsi_tracing/Cargo.toml +4 -0
  45. data/crates/itsi_tracing/src/lib.rs +36 -6
  46. data/gems/scheduler/Cargo.lock +219 -23
  47. data/gems/scheduler/Rakefile +7 -1
  48. data/gems/scheduler/ext/itsi_error/Cargo.toml +2 -0
  49. data/gems/scheduler/ext/itsi_error/src/from.rs +70 -0
  50. data/gems/scheduler/ext/itsi_error/src/lib.rs +10 -37
  51. data/gems/scheduler/ext/itsi_instrument_entry/Cargo.toml +15 -0
  52. data/gems/scheduler/ext/itsi_instrument_entry/src/lib.rs +31 -0
  53. data/gems/scheduler/ext/itsi_rb_helpers/Cargo.toml +2 -0
  54. data/gems/scheduler/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  55. data/gems/scheduler/ext/itsi_rb_helpers/src/lib.rs +90 -10
  56. data/gems/scheduler/ext/itsi_scheduler/Cargo.toml +9 -1
  57. data/gems/scheduler/ext/itsi_scheduler/extconf.rb +1 -1
  58. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  59. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  60. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  61. data/gems/scheduler/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  62. data/gems/scheduler/ext/itsi_scheduler/src/lib.rs +31 -10
  63. data/gems/scheduler/ext/itsi_server/Cargo.toml +41 -0
  64. data/gems/scheduler/ext/itsi_server/extconf.rb +6 -0
  65. data/gems/scheduler/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  66. data/gems/scheduler/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  67. data/gems/scheduler/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  68. data/gems/scheduler/ext/itsi_server/src/lib.rs +103 -0
  69. data/gems/scheduler/ext/itsi_server/src/request/itsi_request.rs +277 -0
  70. data/gems/scheduler/ext/itsi_server/src/request/mod.rs +1 -0
  71. data/gems/scheduler/ext/itsi_server/src/response/itsi_response.rs +347 -0
  72. data/gems/scheduler/ext/itsi_server/src/response/mod.rs +1 -0
  73. data/gems/scheduler/ext/itsi_server/src/server/bind.rs +168 -0
  74. data/gems/scheduler/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  75. data/gems/scheduler/ext/itsi_server/src/server/io_stream.rs +104 -0
  76. data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +13 -0
  77. data/gems/scheduler/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +5 -0
  78. data/gems/scheduler/ext/itsi_server/src/server/itsi_server.rs +244 -0
  79. data/gems/scheduler/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
  80. data/gems/scheduler/ext/itsi_server/src/server/listener.rs +275 -0
  81. data/gems/scheduler/ext/itsi_server/src/server/mod.rs +11 -0
  82. data/gems/scheduler/ext/itsi_server/src/server/process_worker.rs +196 -0
  83. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
  84. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  85. data/gems/scheduler/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
  86. data/gems/scheduler/ext/itsi_server/src/server/signal.rs +70 -0
  87. data/gems/scheduler/ext/itsi_server/src/server/thread_worker.rs +368 -0
  88. data/gems/scheduler/ext/itsi_server/src/server/tls.rs +152 -0
  89. data/gems/scheduler/ext/itsi_tracing/Cargo.toml +4 -0
  90. data/gems/scheduler/ext/itsi_tracing/src/lib.rs +36 -6
  91. data/gems/scheduler/itsi-scheduler.gemspec +2 -3
  92. data/gems/scheduler/lib/itsi/scheduler/version.rb +1 -1
  93. data/gems/scheduler/lib/itsi/scheduler.rb +137 -1
  94. data/gems/scheduler/test/helpers/test_helper.rb +24 -0
  95. data/gems/scheduler/test/test_active_record.rb +158 -0
  96. data/gems/scheduler/test/test_address_resolve.rb +23 -0
  97. data/gems/scheduler/test/test_block_unblock.rb +229 -0
  98. data/gems/scheduler/test/test_file_io.rb +193 -0
  99. data/gems/scheduler/test/test_itsi_scheduler.rb +24 -1
  100. data/gems/scheduler/test/test_kernel_sleep.rb +91 -0
  101. data/gems/scheduler/test/test_nested_fibers.rb +286 -0
  102. data/gems/scheduler/test/test_network_io.rb +274 -0
  103. data/gems/scheduler/test/test_process_wait.rb +26 -0
  104. data/gems/server/exe/itsi +88 -28
  105. data/gems/server/ext/itsi_error/Cargo.toml +2 -0
  106. data/gems/server/ext/itsi_error/src/from.rs +70 -0
  107. data/gems/server/ext/itsi_error/src/lib.rs +10 -37
  108. data/gems/server/ext/itsi_instrument_entry/Cargo.toml +15 -0
  109. data/gems/server/ext/itsi_instrument_entry/src/lib.rs +31 -0
  110. data/gems/server/ext/itsi_rb_helpers/Cargo.toml +2 -0
  111. data/gems/server/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  112. data/gems/server/ext/itsi_rb_helpers/src/lib.rs +90 -10
  113. data/gems/server/ext/itsi_scheduler/Cargo.toml +24 -0
  114. data/gems/server/ext/itsi_scheduler/extconf.rb +6 -0
  115. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  116. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  117. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  118. data/gems/server/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  119. data/gems/server/ext/itsi_scheduler/src/lib.rs +38 -0
  120. data/gems/server/ext/itsi_server/Cargo.toml +14 -2
  121. data/gems/server/ext/itsi_server/extconf.rb +1 -1
  122. data/gems/server/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  123. data/gems/server/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  124. data/gems/server/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  125. data/gems/server/ext/itsi_server/src/lib.rs +58 -7
  126. data/gems/server/ext/itsi_server/src/request/itsi_request.rs +238 -104
  127. data/gems/server/ext/itsi_server/src/response/itsi_response.rs +347 -0
  128. data/gems/server/ext/itsi_server/src/response/mod.rs +1 -0
  129. data/gems/server/ext/itsi_server/src/server/bind.rs +50 -20
  130. data/gems/server/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  131. data/gems/server/ext/itsi_server/src/server/io_stream.rs +104 -0
  132. data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +11 -30
  133. data/gems/server/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +3 -50
  134. data/gems/server/ext/itsi_server/src/server/itsi_server.rs +196 -134
  135. data/gems/server/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
  136. data/gems/server/ext/itsi_server/src/server/listener.rs +184 -127
  137. data/gems/server/ext/itsi_server/src/server/mod.rs +7 -1
  138. data/gems/server/ext/itsi_server/src/server/process_worker.rs +196 -0
  139. data/gems/server/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +254 -0
  140. data/gems/server/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  141. data/gems/server/ext/itsi_server/src/server/serve_strategy/single_mode.rs +241 -0
  142. data/gems/server/ext/itsi_server/src/server/signal.rs +70 -0
  143. data/gems/server/ext/itsi_server/src/server/thread_worker.rs +368 -0
  144. data/gems/server/ext/itsi_server/src/server/tls.rs +42 -28
  145. data/gems/server/ext/itsi_tracing/Cargo.toml +4 -0
  146. data/gems/server/ext/itsi_tracing/src/lib.rs +36 -6
  147. data/gems/server/itsi-server.gemspec +4 -5
  148. data/gems/server/lib/itsi/request.rb +30 -14
  149. data/gems/server/lib/itsi/server/rack/handler/itsi.rb +25 -0
  150. data/gems/server/lib/itsi/server/scheduler_mode.rb +6 -0
  151. data/gems/server/lib/itsi/server/version.rb +1 -1
  152. data/gems/server/lib/itsi/server.rb +82 -2
  153. data/gems/server/lib/itsi/signals.rb +23 -0
  154. data/gems/server/lib/itsi/stream_io.rb +38 -0
  155. data/gems/server/test/test_helper.rb +2 -0
  156. data/gems/server/test/test_itsi_server.rb +1 -1
  157. data/lib/itsi/version.rb +1 -1
  158. data/tasks.txt +18 -0
  159. metadata +102 -12
  160. data/crates/itsi_server/src/server/transfer_protocol.rs +0 -23
  161. data/crates/itsi_server/src/stream_writer/mod.rs +0 -21
  162. data/gems/scheduler/test/test_helper.rb +0 -6
  163. data/gems/server/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  164. data/gems/server/ext/itsi_server/src/stream_writer/mod.rs +0 -21
data/Rakefile CHANGED
@@ -9,65 +9,54 @@ $LOAD_PATH.unshift(File.expand_path('server/lib', __dir__))
9
9
 
10
10
  GEMS = [
11
11
  {
12
+ shortname: :scheduler,
12
13
  name: 'itsi-scheduler',
13
14
  dir: 'gems/scheduler', # subfolder that holds gem code
14
15
  gemspec: 'itsi-scheduler.gemspec',
15
16
  rust_name: 'itsi_scheduler' # name of the ext subfolder
16
17
  },
17
18
  {
19
+ shortname: :server,
18
20
  name: 'itsi-server',
19
21
  dir: 'gems/server',
20
22
  gemspec: 'itsi-server.gemspec',
21
23
  rust_name: 'itsi_server'
22
24
  }
23
25
  ]
26
+ SHARED_TASKS = %i[compile compile:dev test]
24
27
 
25
- Minitest::TestTask.create do |t|
26
- t.test_globs = ['**/test/**/*.rb']
27
- t.warning = true
28
- t.verbose = true
29
- end
30
-
31
- namespace :scheduler do
32
- desc 'Run tasks in the scheduler directory'
33
- task :default do
34
- sh 'cd gems/scheduler && rake'
35
- end
28
+ GEMS.each do |gem|
29
+ namespace gem[:shortname] do
30
+ desc "Run tasks in the #{gem[:dir]} directory"
31
+ task :default do
32
+ sh "cd #{gem[:dir]} && rake"
33
+ end
36
34
 
37
- task :compile do
38
- sh 'cd gems/scheduler && rake compile'
35
+ SHARED_TASKS.each do |task|
36
+ task task do
37
+ sh "cd #{gem[:dir]} && rake #{task}"
38
+ end
39
+ end
39
40
  end
40
41
  end
41
42
 
42
- namespace :server do
43
- desc 'Run tasks in the server directory'
44
- task :default do
45
- sh 'cd gems/server && rake'
46
- end
47
-
48
- task :compile do
49
- sh 'cd gems/server && rake compile'
43
+ SHARED_TASKS.each do |task|
44
+ desc "#{task} in all Gem directories"
45
+ task task do
46
+ GEMS.each do |gem|
47
+ Rake::Task["#{gem[:shortname]}:#{task}"].invoke
48
+ end
50
49
  end
50
+ Rake::Task[task].enhance([:sync_crates])
51
51
  end
52
52
 
53
- desc 'Compile in both scheduler and server directories'
54
- task :compile do
55
- Rake::Task['scheduler:compile'].invoke
56
- Rake::Task['server:compile'].invoke
57
- end
58
-
59
- Rake::Task[:compile].enhance([:sync_crates])
60
53
  Rake::Task[:build].enhance([:build_all])
61
54
 
62
55
  task :sync_crates do
63
56
  require 'fileutils'
64
-
65
57
  GEMS.each do |gem_info|
66
58
  Dir.chdir('crates') do
67
- to_sync = Dir['*'].select do |fn|
68
- rust_name = fn.split('/', 2).last
69
- rust_name == gem_info[:rust_name] || GEMS.none? { |g| g[:rust_name] == rust_name }
70
- end.each do |to_sync|
59
+ to_sync = Dir['*'].each do |to_sync|
71
60
  system("rsync -q -av #{to_sync}/ ../#{gem_info[:dir]}/ext/#{to_sync} --delete")
72
61
  end
73
62
  end
@@ -7,3 +7,5 @@ edition = "2024"
7
7
  thiserror = "2.0.11"
8
8
  magnus = { version = "0.7.1" }
9
9
  rcgen = "0.13.2"
10
+ nix = "0.29.0"
11
+ httparse = "1.10.1"
@@ -0,0 +1,70 @@
1
+ use crate::ItsiError;
2
+ use std::ffi::NulError;
3
+
4
+ pub static CLIENT_CONNECTION_CLOSED: &str = "Client disconnected";
5
+
6
+ impl From<httparse::Error> for ItsiError {
7
+ fn from(err: httparse::Error) -> Self {
8
+ ItsiError::ArgumentError(err.to_string())
9
+ }
10
+ }
11
+
12
+ impl From<nix::errno::Errno> for ItsiError {
13
+ fn from(err: nix::errno::Errno) -> Self {
14
+ ItsiError::ArgumentError(err.to_string())
15
+ }
16
+ }
17
+
18
+ impl From<std::io::Error> for ItsiError {
19
+ fn from(err: std::io::Error) -> Self {
20
+ ItsiError::ArgumentError(err.to_string())
21
+ }
22
+ }
23
+
24
+ impl From<rcgen::Error> for ItsiError {
25
+ fn from(err: rcgen::Error) -> Self {
26
+ ItsiError::ArgumentError(err.to_string())
27
+ }
28
+ }
29
+
30
+ impl From<NulError> for ItsiError {
31
+ fn from(err: NulError) -> Self {
32
+ ItsiError::ArgumentError(err.to_string())
33
+ }
34
+ }
35
+
36
+ impl From<magnus::Error> for ItsiError {
37
+ fn from(err: magnus::Error) -> Self {
38
+ match err.error_type() {
39
+ magnus::error::ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
40
+ magnus::error::ErrorType::Error(_exception_class, cow) => {
41
+ ItsiError::ArgumentError(cow.to_string())
42
+ }
43
+ magnus::error::ErrorType::Exception(exception) => {
44
+ ItsiError::ArgumentError(exception.to_string())
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ impl From<ItsiError> for magnus::Error {
51
+ fn from(err: ItsiError) -> Self {
52
+ match err {
53
+ ItsiError::InvalidInput(msg) => magnus::Error::new(magnus::exception::arg_error(), msg),
54
+ ItsiError::InternalServerError(msg) => {
55
+ magnus::Error::new(magnus::exception::exception(), msg)
56
+ }
57
+ ItsiError::UnsupportedProtocol(msg) => {
58
+ magnus::Error::new(magnus::exception::arg_error(), msg)
59
+ }
60
+ ItsiError::ArgumentError(msg) => {
61
+ magnus::Error::new(magnus::exception::arg_error(), msg)
62
+ }
63
+ ItsiError::Jump(msg) => magnus::Error::new(magnus::exception::local_jump_error(), msg),
64
+ ItsiError::Break() => magnus::Error::new(magnus::exception::interrupt(), "Break"),
65
+ ItsiError::ClientConnectionClosed => {
66
+ magnus::Error::new(magnus::exception::eof_error(), CLIENT_CONNECTION_CLOSED)
67
+ }
68
+ }
69
+ }
70
+ }
@@ -1,49 +1,22 @@
1
+ pub mod from;
1
2
  use thiserror::Error;
2
3
 
3
4
  pub type Result<T> = std::result::Result<T, ItsiError>;
4
5
 
5
6
  #[derive(Error, Debug)]
6
7
  pub enum ItsiError {
7
- #[error("Invalid input")]
8
+ #[error("Invalid input {0}")]
8
9
  InvalidInput(String),
9
- #[error("Internal server error")]
10
- InternalServerError,
11
- #[error("Unsupported protocol")]
10
+ #[error("Internal server error {0}")]
11
+ InternalServerError(String),
12
+ #[error("Unsupported protocol {0}")]
12
13
  UnsupportedProtocol(String),
13
- #[error("Argument error")]
14
+ #[error("Argument error: {0}")]
14
15
  ArgumentError(String),
16
+ #[error("Client Connection Closed")]
17
+ ClientConnectionClosed,
15
18
  #[error("Jump")]
16
19
  Jump(String),
17
- }
18
-
19
- impl From<ItsiError> for magnus::Error {
20
- fn from(err: ItsiError) -> Self {
21
- magnus::Error::new(magnus::exception::runtime_error(), err.to_string())
22
- }
23
- }
24
-
25
- impl From<std::io::Error> for ItsiError {
26
- fn from(err: std::io::Error) -> Self {
27
- ItsiError::ArgumentError(err.to_string())
28
- }
29
- }
30
-
31
- impl From<rcgen::Error> for ItsiError {
32
- fn from(err: rcgen::Error) -> Self {
33
- ItsiError::ArgumentError(err.to_string())
34
- }
35
- }
36
-
37
- impl From<magnus::Error> for ItsiError {
38
- fn from(err: magnus::Error) -> Self {
39
- match err.error_type() {
40
- magnus::error::ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
41
- magnus::error::ErrorType::Error(_exception_class, cow) => {
42
- ItsiError::ArgumentError(cow.to_string())
43
- }
44
- magnus::error::ErrorType::Exception(exception) => {
45
- ItsiError::ArgumentError(exception.to_string())
46
- }
47
- }
48
- }
20
+ #[error("Break")]
21
+ Break(),
49
22
  }
@@ -0,0 +1,15 @@
1
+ [package]
2
+ name = "itsi_instrument_entry"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ authors = ["Wouter Coppieters <wc@pico.net.nz>"]
6
+ license = "MIT"
7
+ publish = false
8
+
9
+
10
+ [lib]
11
+ proc-macro = true
12
+ [dependencies]
13
+ proc-macro2 = "1.0"
14
+ quote = "1.0"
15
+ syn = { version = "1.0", features = ["full"] }
@@ -0,0 +1,31 @@
1
+ use proc_macro::TokenStream;
2
+ use proc_macro2::TokenStream as TokenStream2;
3
+ use quote::quote;
4
+ use syn::{parse_macro_input, ItemFn};
5
+
6
+ #[proc_macro_attribute]
7
+ pub fn instrument_with_entry(attr: TokenStream, item: TokenStream) -> TokenStream {
8
+ let attr_tokens = TokenStream2::from(attr);
9
+ let input_fn = parse_macro_input!(item as ItemFn);
10
+ let attrs = input_fn.attrs;
11
+ let vis = input_fn.vis;
12
+ let sig = input_fn.sig;
13
+ let block = input_fn.block;
14
+ let output = quote! {
15
+ #[cfg(debug_assertions)]
16
+ #[tracing::instrument(#attr_tokens)]
17
+ #(#attrs)*
18
+ #vis #sig {
19
+ tracing::trace!("");
20
+ #block
21
+ }
22
+
23
+ #[cfg(not(debug_assertions))]
24
+ #(#attrs)*
25
+ #vis #sig {
26
+ #block
27
+ }
28
+ };
29
+
30
+ output.into()
31
+ }
@@ -4,5 +4,7 @@ version = "0.1.0"
4
4
  edition = "2024"
5
5
 
6
6
  [dependencies]
7
+ cfg-if = "1.0.0"
7
8
  magnus = { version = "0.7.1", features = ["rb-sys", "bytes"] }
9
+ nix = "0.29.0"
8
10
  rb-sys = "0.9.105"
@@ -0,0 +1,121 @@
1
+ use magnus::IntoValue;
2
+ use magnus::rb_sys::AsRawValue;
3
+ use magnus::value::BoxValue;
4
+ use magnus::{Ruby, Value, value::ReprValue};
5
+ use std::fmt::{self, Debug, Formatter};
6
+ use std::ops::Deref;
7
+
8
+ /// HeapVal is a wrapper for heap-allocated magnus ReprVa;ies
9
+ /// that is marked as thread-safe(Send and Sync)
10
+ /// It's up to the user to actually ensure this though,
11
+ /// typically by only interacting with the value from a thread which
12
+ /// holds the GVL.
13
+ pub struct HeapValue<T>(pub BoxValue<T>)
14
+ where
15
+ T: ReprValue;
16
+
17
+ impl<T> PartialEq for HeapValue<T>
18
+ where
19
+ T: ReprValue,
20
+ {
21
+ fn eq(&self, other: &Self) -> bool {
22
+ self.0.as_raw() == other.0.as_raw()
23
+ }
24
+ }
25
+
26
+ impl<T> Deref for HeapValue<T>
27
+ where
28
+ T: ReprValue,
29
+ {
30
+ type Target = T;
31
+
32
+ fn deref(&self) -> &Self::Target {
33
+ &self.0
34
+ }
35
+ }
36
+
37
+ impl<T> HeapValue<T>
38
+ where
39
+ T: ReprValue,
40
+ {
41
+ pub fn inner(self) -> T {
42
+ *self.0
43
+ }
44
+ }
45
+
46
+ impl<T> IntoValue for HeapValue<T>
47
+ where
48
+ T: ReprValue,
49
+ {
50
+ fn into_value_with(self, _: &Ruby) -> Value {
51
+ self.0.into_value()
52
+ }
53
+ }
54
+
55
+ impl<T> From<T> for HeapValue<T>
56
+ where
57
+ T: ReprValue,
58
+ {
59
+ fn from(value: T) -> Self {
60
+ HeapValue(BoxValue::new(value))
61
+ }
62
+ }
63
+
64
+ impl<T> Clone for HeapValue<T>
65
+ where
66
+ T: ReprValue + Clone,
67
+ {
68
+ fn clone(&self) -> Self {
69
+ HeapValue(BoxValue::new(*self.0.deref()))
70
+ }
71
+ }
72
+
73
+ impl<T> Debug for HeapValue<T>
74
+ where
75
+ T: ReprValue + Debug,
76
+ {
77
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78
+ write!(f, "{:?}", self.0)
79
+ }
80
+ }
81
+
82
+ unsafe impl<T> Send for HeapValue<T> where T: ReprValue {}
83
+ unsafe impl<T> Sync for HeapValue<T> where T: ReprValue {}
84
+
85
+ /// HeapVal is a wrapper for heap-allocated magnus Values
86
+ /// that is marked as thread-safe(Send and Sync)
87
+ /// It's up to the user to actually ensure this though,
88
+ /// typically by only interacting with the value from a thread which
89
+ /// holds the GVL.
90
+ pub struct HeapVal(HeapValue<Value>);
91
+ impl Deref for HeapVal {
92
+ type Target = Value;
93
+
94
+ fn deref(&self) -> &Self::Target {
95
+ &self.0
96
+ }
97
+ }
98
+
99
+ impl IntoValue for HeapVal {
100
+ fn into_value_with(self, _: &Ruby) -> Value {
101
+ self.0.into_value()
102
+ }
103
+ }
104
+
105
+ impl From<Value> for HeapVal {
106
+ fn from(value: Value) -> Self {
107
+ HeapVal(HeapValue(BoxValue::new(value)))
108
+ }
109
+ }
110
+
111
+ impl Clone for HeapVal {
112
+ fn clone(&self) -> Self {
113
+ HeapVal(HeapValue(BoxValue::new(*self.0.deref())))
114
+ }
115
+ }
116
+
117
+ impl Debug for HeapVal {
118
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119
+ write!(f, "{:?}", self.0)
120
+ }
121
+ }
@@ -1,23 +1,43 @@
1
- use std::{os::raw::c_void, ptr::null_mut};
1
+ use std::{os::raw::c_void, ptr::null_mut, sync::Arc};
2
2
 
3
+ use magnus::{
4
+ RArray, Ruby, Thread, Value,
5
+ rb_sys::FromRawValue,
6
+ value::{LazyId, ReprValue},
7
+ };
3
8
  use rb_sys::{
4
- rb_thread_call_with_gvl, rb_thread_call_without_gvl, rb_thread_create, rb_thread_wakeup,
9
+ rb_thread_call_with_gvl, rb_thread_call_without_gvl, rb_thread_create, rb_thread_schedule,
10
+ rb_thread_wakeup,
5
11
  };
6
12
 
7
- pub fn create_ruby_thread<F>(f: F)
13
+ mod heap_value;
14
+ pub use heap_value::{HeapVal, HeapValue};
15
+ static ID_FORK: LazyId = LazyId::new("fork");
16
+ static ID_LIST: LazyId = LazyId::new("list");
17
+ static ID_EQ: LazyId = LazyId::new("==");
18
+ static ID_ALIVE: LazyId = LazyId::new("alive?");
19
+ static ID_THREAD_VARIABLE_GET: LazyId = LazyId::new("thread_variable_get");
20
+
21
+ pub fn schedule_thread() {
22
+ unsafe {
23
+ rb_thread_schedule();
24
+ };
25
+ }
26
+ pub fn create_ruby_thread<F>(f: F) -> Thread
8
27
  where
9
- F: FnOnce() -> u64 + Send + 'static,
28
+ F: FnOnce() + Send + 'static,
10
29
  {
11
30
  extern "C" fn trampoline<F>(ptr: *mut c_void) -> u64
12
31
  where
13
- F: FnOnce() -> u64,
32
+ F: FnOnce(),
14
33
  {
15
34
  // Reconstruct the boxed Option<F> that holds our closure.
16
35
  let boxed_closure: Box<Option<F>> = unsafe { Box::from_raw(ptr as *mut Option<F>) };
17
36
  // Extract the closure. (The Option should be Some; panic otherwise.)
18
37
  let closure = (*boxed_closure).expect("Closure already taken");
19
38
  // Call the closure and return its result.
20
- closure()
39
+ closure();
40
+ 0
21
41
  }
22
42
 
23
43
  // Box the closure (wrapped in an Option) to create a stable pointer.
@@ -26,7 +46,10 @@ where
26
46
 
27
47
  // Call rb_thread_create with our trampoline and boxed closure.
28
48
  unsafe {
29
- rb_thread_wakeup(rb_thread_create(Some(trampoline::<F>), ptr));
49
+ let thread = rb_thread_create(Some(trampoline::<F>), ptr);
50
+ rb_thread_wakeup(thread);
51
+ rb_thread_schedule();
52
+ Thread::from_value(Value::from_raw(thread)).unwrap()
30
53
  }
31
54
  }
32
55
 
@@ -67,18 +90,18 @@ where
67
90
 
68
91
  pub fn call_with_gvl<F, R>(f: F) -> R
69
92
  where
70
- F: FnOnce() -> R,
93
+ F: FnOnce(Ruby) -> R,
71
94
  {
72
95
  extern "C" fn trampoline<F, R>(arg: *mut c_void) -> *mut c_void
73
96
  where
74
- F: FnOnce() -> R,
97
+ F: FnOnce(Ruby) -> R,
75
98
  {
76
99
  // 1) Reconstruct the Box that holds our closure
77
100
  let closure_ptr = arg as *mut Option<F>;
78
101
  let closure = unsafe { (*closure_ptr).take().expect("Closure already taken") };
79
102
 
80
103
  // 2) Call the user’s closure
81
- let result = closure();
104
+ let result = closure(Ruby::get().unwrap());
82
105
 
83
106
  // 3) Box up the result so we can return a pointer to it
84
107
  let boxed_result = Box::new(result);
@@ -96,3 +119,60 @@ where
96
119
  let result_box = unsafe { Box::from_raw(raw_result_ptr as *mut R) };
97
120
  *result_box
98
121
  }
122
+
123
+ pub fn fork(after_fork: Arc<Option<impl Fn()>>) -> Option<i32> {
124
+ let ruby = Ruby::get().unwrap();
125
+ let fork_result = ruby
126
+ .module_kernel()
127
+ .funcall::<_, _, Option<i32>>(*ID_FORK, ())
128
+ .unwrap();
129
+ if fork_result.is_none() {
130
+ if let Some(f) = &*after_fork {
131
+ f()
132
+ }
133
+ }
134
+ fork_result
135
+ }
136
+
137
+ pub fn kill_threads<T>(threads: Vec<T>)
138
+ where
139
+ T: ReprValue,
140
+ {
141
+ for thr in &threads {
142
+ let alive: bool = thr
143
+ .funcall(*ID_ALIVE, ())
144
+ .expect("Failed to check if thread is alive");
145
+ if !alive {
146
+ eprintln!("Thread killed");
147
+ break;
148
+ }
149
+ eprintln!("Killing thread {:?}", thr.as_value());
150
+ thr.funcall::<_, _, Value>("terminate", ())
151
+ .expect("Failed to kill thread");
152
+ }
153
+ }
154
+
155
+ pub fn terminate_non_fork_safe_threads() {
156
+ let ruby = Ruby::get().unwrap();
157
+ let thread_class = ruby.class_thread();
158
+ let current: Thread = ruby.thread_current();
159
+ let threads: RArray = thread_class
160
+ .funcall(*ID_LIST, ())
161
+ .expect("Failed to list Ruby threads");
162
+
163
+ let non_fork_safe_threads = threads
164
+ .into_iter()
165
+ .filter_map(|v| {
166
+ let v_thread = Thread::from_value(v).unwrap();
167
+ let non_fork_safe = !v_thread
168
+ .funcall::<_, _, bool>(*ID_EQ, (current,))
169
+ .unwrap_or(false)
170
+ && !v_thread
171
+ .funcall::<_, _, bool>(*ID_THREAD_VARIABLE_GET, (ruby.sym_new("fork_safe"),))
172
+ .unwrap_or(false);
173
+ if non_fork_safe { Some(v_thread) } else { None }
174
+ })
175
+ .collect::<Vec<_>>();
176
+
177
+ kill_threads(non_fork_safe_threads);
178
+ }
@@ -10,7 +10,15 @@ publish = false
10
10
  crate-type = ["cdylib"]
11
11
 
12
12
  [dependencies]
13
- magnus = { version = "0.6.2" }
13
+ magnus = { version = "0.7.1", features = ["rb-sys", "bytes"] }
14
+ derive_more = { version = "2.0.1", features = ["debug"] }
14
15
  itsi_tracing = { path = "../itsi_tracing" }
15
16
  itsi_rb_helpers = { path = "../itsi_rb_helpers" }
16
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"
@@ -3,4 +3,4 @@
3
3
  require "mkmf"
4
4
  require "rb_sys/mkmf"
5
5
 
6
- create_rust_makefile("itsi_scheduler/itsi_scheduler")
6
+ create_rust_makefile("itsi/scheduler/itsi_scheduler")
@@ -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
+ }