itsi-server 0.1.1 → 0.1.11

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.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2926 -0
  3. data/Cargo.toml +7 -0
  4. data/Rakefile +8 -1
  5. data/exe/itsi +119 -29
  6. data/ext/itsi_error/Cargo.toml +2 -0
  7. data/ext/itsi_error/src/from.rs +68 -0
  8. data/ext/itsi_error/src/lib.rs +13 -38
  9. data/ext/itsi_instrument_entry/Cargo.toml +15 -0
  10. data/ext/itsi_instrument_entry/src/lib.rs +31 -0
  11. data/ext/itsi_rb_helpers/Cargo.toml +2 -0
  12. data/ext/itsi_rb_helpers/src/heap_value.rs +121 -0
  13. data/ext/itsi_rb_helpers/src/lib.rs +112 -9
  14. data/ext/itsi_scheduler/Cargo.toml +24 -0
  15. data/ext/itsi_scheduler/extconf.rb +6 -0
  16. data/ext/itsi_scheduler/src/itsi_scheduler/io_helpers.rs +56 -0
  17. data/ext/itsi_scheduler/src/itsi_scheduler/io_waiter.rs +44 -0
  18. data/ext/itsi_scheduler/src/itsi_scheduler/timer.rs +44 -0
  19. data/ext/itsi_scheduler/src/itsi_scheduler.rs +308 -0
  20. data/ext/itsi_scheduler/src/lib.rs +38 -0
  21. data/ext/itsi_server/Cargo.lock +2956 -0
  22. data/ext/itsi_server/Cargo.toml +25 -4
  23. data/ext/itsi_server/extconf.rb +1 -1
  24. data/ext/itsi_server/src/body_proxy/big_bytes.rs +104 -0
  25. data/ext/itsi_server/src/body_proxy/itsi_body_proxy.rs +122 -0
  26. data/ext/itsi_server/src/body_proxy/mod.rs +2 -0
  27. data/ext/itsi_server/src/env.rs +43 -0
  28. data/ext/itsi_server/src/lib.rs +136 -8
  29. data/ext/itsi_server/src/request/itsi_request.rs +258 -103
  30. data/ext/itsi_server/src/response/itsi_response.rs +357 -0
  31. data/ext/itsi_server/src/response/mod.rs +1 -0
  32. data/ext/itsi_server/src/server/bind.rs +65 -29
  33. data/ext/itsi_server/src/server/bind_protocol.rs +37 -0
  34. data/ext/itsi_server/src/server/io_stream.rs +104 -0
  35. data/ext/itsi_server/src/server/itsi_server.rs +245 -139
  36. data/ext/itsi_server/src/server/lifecycle_event.rs +9 -0
  37. data/ext/itsi_server/src/server/listener.rs +237 -137
  38. data/ext/itsi_server/src/server/mod.rs +7 -1
  39. data/ext/itsi_server/src/server/process_worker.rs +203 -0
  40. data/ext/itsi_server/src/server/serve_strategy/cluster_mode.rs +260 -0
  41. data/ext/itsi_server/src/server/serve_strategy/mod.rs +27 -0
  42. data/ext/itsi_server/src/server/serve_strategy/single_mode.rs +276 -0
  43. data/ext/itsi_server/src/server/signal.rs +74 -0
  44. data/ext/itsi_server/src/server/thread_worker.rs +399 -0
  45. data/ext/itsi_server/src/server/tls/locked_dir_cache.rs +132 -0
  46. data/ext/itsi_server/src/server/tls.rs +187 -60
  47. data/ext/itsi_tracing/Cargo.toml +4 -0
  48. data/ext/itsi_tracing/src/lib.rs +53 -6
  49. data/lib/itsi/index.html +91 -0
  50. data/lib/itsi/request.rb +38 -14
  51. data/lib/itsi/server/Itsi.rb +127 -0
  52. data/lib/itsi/server/config.rb +36 -0
  53. data/lib/itsi/server/options_dsl.rb +401 -0
  54. data/lib/itsi/server/rack/handler/itsi.rb +36 -0
  55. data/lib/itsi/server/rack_interface.rb +75 -0
  56. data/lib/itsi/server/scheduler_interface.rb +21 -0
  57. data/lib/itsi/server/scheduler_mode.rb +6 -0
  58. data/lib/itsi/server/signal_trap.rb +23 -0
  59. data/lib/itsi/server/version.rb +1 -1
  60. data/lib/itsi/server.rb +79 -9
  61. data/lib/itsi/stream_io.rb +38 -0
  62. metadata +49 -27
  63. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.crt +0 -32
  64. data/ext/itsi_server/src/server/itsi_ca/itsi_ca.key +0 -52
  65. data/ext/itsi_server/src/server/transfer_protocol.rs +0 -23
  66. data/ext/itsi_server/src/stream_writer/mod.rs +0 -21
data/Cargo.toml ADDED
@@ -0,0 +1,7 @@
1
+ # This Cargo.toml is here to let externals tools (IDEs, etc.) know that this is
2
+ # a Rust project. Your extensions dependencies should be added to the Cargo.toml
3
+ # in the ext/ directory.
4
+
5
+ [workspace]
6
+ members = ["./ext/itsi_server"]
7
+ resolver = "2"
data/Rakefile CHANGED
@@ -3,7 +3,14 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "minitest/test_task"
5
5
 
6
- Minitest::TestTask.create
6
+
7
+ Minitest::TestTask.create(:test) do |t|
8
+ t.libs << 'test'
9
+ t.libs << 'lib'
10
+ t.warning = false
11
+ t.test_globs = ['test/**/*.rb']
12
+ t.test_prelude = 'require "helpers/test_helper.rb"'
13
+ end
7
14
 
8
15
  require "rubocop/rake_task"
9
16
 
data/exe/itsi CHANGED
@@ -2,26 +2,38 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "optparse"
5
- require "rack"
6
- require "etc"
7
5
 
8
6
  # Default options used when starting Osprey from the CLI using `osprey`
9
7
  DEFAULT_OPTIONS = {
10
8
  # Number of workers
11
- workers: Etc.nprocessors,
9
+ workers: 1,
12
10
  # Number of threads per worker
13
11
  threads: 1,
14
12
  # Graceful shutdown timeout
15
- shutdown_timeout: 0.3,
13
+ shutdown_timeout: 5,
16
14
  # Binds
17
- binds: ['http://0.0.0.0:3000']
15
+ binds: ["http://0.0.0.0:3000"],
16
+ # Preload
17
+ preload: true,
18
+ # Rackup file
19
+ rackup_file: "config.ru",
20
+ # Scheduler class
21
+ scheduler_class: nil,
22
+ # Whether to stream the body or not
23
+ stream_body: false,
24
+ # Config file
25
+ config_file: nil
18
26
  }
19
27
 
20
28
  options = DEFAULT_OPTIONS.to_a.select(&:last).to_h
21
29
 
22
30
  # Define the option parser
23
31
  OptionParser.new do |opts|
24
- opts.banner = "Usage: script.rb [options]"
32
+ opts.banner = "Usage: itsi [options]"
33
+
34
+ opts.on("-C", "--config CONFIG_FILE", String, "Itsi Configuration file to use (default: Itsi.rb)") do |config_file|
35
+ options[:config_file] = config_file
36
+ end
25
37
 
26
38
  opts.on("-w", "--workers WORKERS", Integer, "Number of workers (default: #{options[:workers]})") do |w|
27
39
  options[:workers] = w
@@ -31,29 +43,68 @@ OptionParser.new do |opts|
31
43
  options[:threads] = t
32
44
  end
33
45
 
34
- opts.on("-h", "--host HOST", String, "Host to bind to (default: #{options[:host]})") do |h|
35
- options[:host] = h
46
+ opts.on("-r", "--rackup_file FILE", String, "Rackup file to use (default: #{options[:rackup_file]})") do |rf|
47
+ options[:rackup_file] = rf
36
48
  end
37
49
 
38
- opts.on("-p", "--port PORT", Integer, "Port for the application (default: #{options[:port]})") do |p|
39
- options[:port] = p
50
+ opts.on("--worker-memory-limit MEMORY_LIMIT", Integer,
51
+ "Memory limit for each worker (default: #{options[:worker_memory_limit] || "None"}). If this limit is breached the worker is gracefully restarted") do |ml|
52
+ options[:worker_memory_limit] = ml
40
53
  end
41
54
 
42
- opts.on("-f", "--use_fiber_scheduler PORT", TrueClass,
43
- "Port for the application (default: #{options[:use_scheduler]})") do |p|
44
- options[:use_scheduler] = p
55
+ opts.on("-f", "--fiber_scheduler [CLASS_NAME]", [String],
56
+ "Scheduler class to use (default: nil). Provide blank or true to use Itsi::Scheduler, or a classname to use an alternative scheduler") do |scheduler_class|
57
+ if scheduler_class.nil? || scheduler_class == "true"
58
+ options[:scheduler_class] = "Itsi::Scheduler"
59
+ elsif scheduler_class == "false"
60
+ options.delete(:scheduler_class)
61
+ else
62
+ options[:scheduler_class] = scheduler_class
63
+ end
45
64
  end
46
65
 
47
- opts.on("--http_port HTTP_PORT", Integer, "HTTP port for the application (default: #{options[:http_port]})") do |hp|
48
- options[:http_port] = hp
66
+ opts.on("--preload [true, false, :bundle_group_name]", String, " Toggle preloading the application") do |preload|
67
+ if preload == "true"
68
+ options[:preload] = true
69
+ elsif preload == "false"
70
+ options[:preload] = false
71
+ else
72
+ # Not supported yet
73
+ end
49
74
  end
50
75
 
51
- opts.on("-c", "--cert_path CERT_PATH", String, "Path to the SSL certificate file") do |cp|
52
- options[:cert_path] = cp
76
+ opts.on("-b", "--bind BIND", String,
77
+ "Bind address (default: #{options[:binds].join(", ")}). You can specify this flag multiple times to bind to multiple addresses.") do |bind|
78
+ options[:binds].pop if options[:binds].first.frozen?
79
+ options[:binds] << bind
53
80
  end
54
81
 
55
- opts.on("-k", "--key_path KEY_PATH", String, "Path to the SSL key file") do |kp|
56
- options[:key_path] = kp
82
+ opts.on("-c", "--cert_path CERT_PATH", String,
83
+ "Path to the SSL certificate file (must follow a --bind option). You can specify this flag multiple times.") do |cp|
84
+ raise OptionParser::InvalidOption, "--cert_path must follow a --bind" if options[:binds].empty?
85
+
86
+ require "uri"
87
+
88
+ # Modify the last bind entry to add/update the cert query parameter
89
+ uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
90
+ params = URI.decode_www_form(uri.query.to_s).to_h
91
+ params["cert"] = cp
92
+ query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
93
+ options[:binds][-1] = "#{uri.host}?#{query_string}"
94
+ end
95
+
96
+ opts.on("-k", "--key_path KEY_PATH", String,
97
+ "Path to the SSL key file (must follow a --bind option). You can specify this flag multiple times.") do |kp|
98
+ raise OptionParser::InvalidOption, "--key_path must follow a --bind" if options[:binds].empty?
99
+
100
+ require "uri"
101
+
102
+ # Modify the last bind entry to add/update the key query parameter
103
+ uri = URI.parse("http://#{options[:binds].last}") # Ensure valid URI parsing
104
+ params = URI.decode_www_form(uri.query.to_s).to_h
105
+ params["key"] = kp
106
+ query_string = params.map { |k, v| "#{k}=#{v}" }.join("&")
107
+ options[:binds][-1] = "#{uri.host}?#{query_string}"
57
108
  end
58
109
 
59
110
  opts.on("--shutdown_timeout SHUTDOWN_TIMEOUT", String,
@@ -65,20 +116,59 @@ OptionParser.new do |opts|
65
116
  options[:script_name] = script_name
66
117
  end
67
118
 
68
- opts.on("--help", "Show this help message") do
119
+ opts.on("--stream-body", TrueClass, "Stream body frames (default: false for best compatibility)") do |stream_body|
120
+ options[:stream_body] = stream_body
121
+ end
122
+
123
+ opts.on("-h", "--help", "Show this help message") do
69
124
  puts opts
70
125
  exit
71
126
  end
72
127
  end.parse!
73
128
 
74
- # Parse the Rack application
75
- app, _ = Rack::Builder.parse_file("config.ru")
129
+ require "itsi/server/config"
130
+ if ARGV.pop == "init"
131
+ Itsi::Server::Config.write_default
132
+ exit(0)
133
+ # Add initialization code here
134
+ end
135
+
136
+ options = Itsi::Server::Config.load(options)
137
+ loader = nil
138
+
139
+ if options[:app] || File.exist?(options[:rackup_file])
140
+ loader ||= lambda do
141
+ require "rack"
142
+ require_relative "../lib/itsi/server/scheduler_mode" if options[:scheduler_class]
143
+ require "itsi/scheduler" if [true, "Itsi::Scheduler"].include?(options[:scheduler_class])
144
+ require "itsi/server"
145
+ return options[:app] if options[:app]
146
+ Array(Rack::Builder.parse_file(options[:rackup_file])).first
147
+ end
76
148
 
77
- puts "App is #{app}"
78
- # Make sure osprey is loaded, if not already loaded by the rack_app above.
79
- # Start the Osprey server
149
+ if options[:preload] == true
150
+ # If preload is true, we'll invoke our loader now, and just return a no-op proc
151
+ # which returns the app as loader. We still need to optionally load the scheduler class and rack
152
+ # in case the app has been provided directly in Itsi.rb instead of in config.ru
153
+ require "rack"
154
+ require_relative "../lib/itsi/server/scheduler_mode" if options[:scheduler_class]
155
+ require "itsi/scheduler" if [true, "Itsi::Scheduler"].include?(options[:scheduler_class])
156
+ loader = (options[:app] || loader[]).method(:itself).to_proc
157
+ elsif options[:preload]
158
+ # If a group name is given, we'll stick to just loading the gem group by the same name from the Gemfile.
159
+ # But we'll fall through to the default delayed loader for the full app.
160
+ require "bundler"
161
+ Bundler.setup
162
+ Bundler.require(options[:preload])
163
+ nil
164
+ end
165
+ end
166
+
167
+ # Make sure Itsi is loaded, if not already loaded by the rack_app above.
168
+ # Start the Itsi server
80
169
  require "itsi/server"
81
- Itsi::Server.new(
82
- app: app,
83
- **options
84
- ).start
170
+
171
+ Itsi::Server.start(
172
+ loader: loader,
173
+ **options.except(:preload, :rackup_file)
174
+ )
@@ -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,68 @@
1
+ use magnus::{
2
+ Error,
3
+ error::ErrorType,
4
+ exception::{self, arg_error, exception},
5
+ };
6
+ use nix::errno::Errno;
7
+
8
+ use crate::ItsiError;
9
+ use std::{ffi::NulError, io};
10
+
11
+ pub static CLIENT_CONNECTION_CLOSED: &str = "Client disconnected";
12
+
13
+ impl From<httparse::Error> for ItsiError {
14
+ fn from(err: httparse::Error) -> Self {
15
+ ItsiError::ArgumentError(err.to_string())
16
+ }
17
+ }
18
+
19
+ impl From<Errno> for ItsiError {
20
+ fn from(err: Errno) -> Self {
21
+ ItsiError::ArgumentError(err.to_string())
22
+ }
23
+ }
24
+
25
+ impl From<io::Error> for ItsiError {
26
+ fn from(err: 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<NulError> for ItsiError {
38
+ fn from(err: NulError) -> Self {
39
+ ItsiError::ArgumentError(err.to_string())
40
+ }
41
+ }
42
+
43
+ impl From<Error> for ItsiError {
44
+ fn from(err: Error) -> Self {
45
+ match err.error_type() {
46
+ ErrorType::Jump(tag) => ItsiError::Jump(tag.to_string()),
47
+ ErrorType::Error(_exception_class, cow) => ItsiError::ArgumentError(cow.to_string()),
48
+ ErrorType::Exception(exception) => ItsiError::ArgumentError(exception.to_string()),
49
+ }
50
+ }
51
+ }
52
+
53
+ impl From<ItsiError> for Error {
54
+ fn from(err: ItsiError) -> Self {
55
+ match err {
56
+ ItsiError::InvalidInput(msg) => Error::new(arg_error(), msg),
57
+ ItsiError::InternalServerError(msg) => Error::new(exception(), msg),
58
+ ItsiError::UnsupportedProtocol(msg) => Error::new(arg_error(), msg),
59
+ ItsiError::ArgumentError(msg) => Error::new(arg_error(), msg),
60
+ ItsiError::Jump(msg) => Error::new(exception::local_jump_error(), msg),
61
+ ItsiError::Break() => Error::new(exception::interrupt(), "Break"),
62
+ ItsiError::ClientConnectionClosed => {
63
+ Error::new(exception::eof_error(), CLIENT_CONNECTION_CLOSED)
64
+ }
65
+ ItsiError::Pass() => Error::new(exception::interrupt(), "Pass"),
66
+ }
67
+ }
68
+ }
@@ -1,49 +1,24 @@
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
- #[derive(Error, Debug)]
6
+ #[derive(Error, Debug, Clone)]
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(),
22
+ #[error("Pass")]
23
+ Pass(),
49
24
  }
@@ -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
+ }