omq-backend-rust 0.2.0 → 0.3.0

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.
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ require_relative "../version"
6
+
7
+ module OMQ
8
+ module Rust
9
+ module Java
10
+ class UnsupportedPlatformError < LoadError; end
11
+
12
+ class << self
13
+ def classifier
14
+ override = ENV.fetch("OMQ_JAVA_CLASSIFIER", "").strip
15
+ return validate_classifier(override) unless override.empty?
16
+
17
+ classifier_for(os_name: host_os_name, os_arch: host_os_arch)
18
+ end
19
+
20
+
21
+ def classifier_for(os_name:, os_arch:)
22
+ os = normalize_os(os_name)
23
+ arch = normalize_arch(os_arch)
24
+
25
+ classifier = "#{os}-#{arch}" if os && arch
26
+ return classifier if OMQ::Rust::OMQ_JAVA_CLASSIFIERS.include?(classifier)
27
+
28
+ raise UnsupportedPlatformError,
29
+ "unsupported OMQ.java platform: #{os_name.inspect}/#{os_arch.inspect} " \
30
+ "(supported: #{OMQ::Rust::OMQ_JAVA_CLASSIFIERS.join(", ")})"
31
+ end
32
+
33
+
34
+ private
35
+
36
+
37
+ def validate_classifier(classifier)
38
+ return classifier if OMQ::Rust::OMQ_JAVA_CLASSIFIERS.include?(classifier)
39
+
40
+ raise UnsupportedPlatformError,
41
+ "unsupported OMQ.java classifier: #{classifier.inspect} " \
42
+ "(supported: #{OMQ::Rust::OMQ_JAVA_CLASSIFIERS.join(", ")})"
43
+ end
44
+
45
+
46
+ def host_os_name
47
+ java_system_property("os.name") ||
48
+ RbConfig::CONFIG["host_os"] ||
49
+ RbConfig::CONFIG["target_os"] ||
50
+ RUBY_PLATFORM
51
+ end
52
+
53
+
54
+ def host_os_arch
55
+ java_system_property("os.arch") ||
56
+ RbConfig::CONFIG["host_cpu"] ||
57
+ RbConfig::CONFIG["target_cpu"] ||
58
+ RUBY_PLATFORM
59
+ end
60
+
61
+
62
+ def java_system_property(name)
63
+ return unless RUBY_ENGINE == "jruby"
64
+
65
+ require "java"
66
+ ::Java::JavaLang::System.get_property(name)
67
+ rescue LoadError, NameError
68
+ nil
69
+ end
70
+
71
+
72
+ def normalize_os(value)
73
+ case value.to_s.downcase
74
+ when /linux/
75
+ "linux"
76
+ when /darwin|mac\s*os|macos/
77
+ "macos"
78
+ when /windows|mswin|mingw|cygwin/
79
+ "windows"
80
+ end
81
+ end
82
+
83
+
84
+ def normalize_arch(value)
85
+ case value.to_s.downcase
86
+ when "amd64", "x64", "x86-64", "x86_64"
87
+ "x86_64"
88
+ when "aarch64", "arm64"
89
+ "aarch64"
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,7 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OMQ
4
+ module Backend
5
+ module Rust
6
+ VERSION = "0.3.0"
7
+ end
8
+ end
9
+
4
10
  module Rust
5
- VERSION = "0.2.0"
11
+ OMQ_JAVA_VERSION = "0.3.0"
12
+ OMQ_JAVA_CLASSIFIERS = [
13
+ "linux-x86_64",
14
+ "macos-aarch64",
15
+ "macos-x86_64",
16
+ "windows-x86_64",
17
+ ].freeze
6
18
  end
7
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-backend-rust
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -24,47 +24,36 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0.28'
26
26
  - !ruby/object:Gem::Dependency
27
- name: rb_sys
27
+ name: omq-rs
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.9'
32
+ version: '0.1'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0.9'
39
+ version: '0.1'
40
40
  description: Drop-in Rust backend for OMQ. Same socket API (REQ/REP, PUB/SUB, PUSH/PULL,
41
- DEALER/ROUTER, and all draft types), but networking runs on a Tokio runtime inside
42
- a native extension compiled via rb_sys. Fully interoperable with the default Ruby
43
- engine.
41
+ DEALER/ROUTER, and all draft types), backed by the first-class omq-rs Ruby binding.
42
+ Fully interoperable with the default Ruby engine.
44
43
  email:
45
44
  - paddor@gmail.com
46
45
  executables: []
47
- extensions:
48
- - ext/omq_backend_rust/extconf.rb
46
+ extensions: []
49
47
  extra_rdoc_files: []
50
48
  files:
51
49
  - CHANGELOG.md
52
- - Cargo.toml
53
50
  - LICENSE
54
51
  - README.md
55
- - ext/omq_backend_rust/Cargo.toml
56
- - ext/omq_backend_rust/build.rs
57
- - ext/omq_backend_rust/extconf.rb
58
- - ext/omq_backend_rust/src/error.rs
59
- - ext/omq_backend_rust/src/lib.rs
60
- - ext/omq_backend_rust/src/notify.rs
61
- - ext/omq_backend_rust/src/options.rs
62
- - ext/omq_backend_rust/src/rb.rs
63
- - ext/omq_backend_rust/src/runtime.rs
64
- - ext/omq_backend_rust/src/socket.rs
65
52
  - lib/omq/backend/rust.rb
66
53
  - lib/omq/rust/engine.rb
67
54
  - lib/omq/rust/fd_watcher.rb
55
+ - lib/omq/rust/java/engine.rb
56
+ - lib/omq/rust/java/platform.rb
68
57
  - lib/omq/rust/version.rb
69
58
  homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-backend-rust
70
59
  licenses:
@@ -86,5 +75,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
75
  requirements: []
87
76
  rubygems_version: 4.0.16
88
77
  specification_version: 4
89
- summary: Rust-backed engine for OMQ using omq-tokio
78
+ summary: OMQ.rs backend for OMQ.rb
90
79
  test_files: []
data/Cargo.toml DELETED
@@ -1,3 +0,0 @@
1
- [workspace]
2
- members = ["ext/omq_backend_rust"]
3
- resolver = "3"
@@ -1,33 +0,0 @@
1
- [package]
2
- name = "omq_backend_rust"
3
- version = "0.2.0"
4
- edition = "2024"
5
- license = "ISC"
6
- authors = ["Patrik Wenger <paddor@gmail.com>"]
7
- publish = false
8
-
9
- [lib]
10
- name = "omq_backend_rust"
11
- crate-type = ["cdylib"]
12
-
13
- [features]
14
- default = ["plain", "curve", "lz4", "zstd"]
15
- plain = ["omq-tokio/plain"]
16
- curve = ["omq-tokio/curve"]
17
- lz4 = ["omq-tokio/lz4"]
18
- zstd = ["omq-tokio/zstd"]
19
-
20
- [dependencies]
21
- omq-proto = { version = "=0.25.1", default-features = false }
22
- omq-tokio = { version = "=0.21.1", default-features = false }
23
- tokio = { version = "1.52.0", features = ["rt", "rt-multi-thread", "time", "sync", "io-util", "net"] }
24
- yring = { version = "0.3.11", features = ["async"] }
25
-
26
- bytes = "1.12.0"
27
- flume = { version = "0.12", default-features = false, features = ["async"] }
28
- futures = { version = "0.3", default-features = false, features = ["std", "async-await"] }
29
- rb-sys = "0.9"
30
- libc = "0.2"
31
-
32
- [build-dependencies]
33
- rb-sys = "0.9"
@@ -1,23 +0,0 @@
1
- use std::process::Command;
2
-
3
- fn main() {
4
- println!("cargo:rerun-if-env-changed=RUBY");
5
- println!("cargo:rustc-check-cfg=cfg(ruby_engine, values(\"mri\", \"truffleruby\"))");
6
-
7
- let ruby = std::env::var("RUBY").unwrap_or_else(|_| "ruby".to_string());
8
- let output = Command::new(&ruby)
9
- .arg("-rrbconfig")
10
- .arg("-e")
11
- .arg("print RbConfig::CONFIG.fetch('ruby_install_name')")
12
- .output()
13
- .unwrap_or_else(|err| panic!("failed to run {ruby}: {err}"));
14
-
15
- if !output.status.success() {
16
- panic!("failed to query Ruby engine with {ruby}");
17
- }
18
-
19
- match String::from_utf8_lossy(&output.stdout).trim() {
20
- "truffleruby" => println!("cargo:rustc-cfg=ruby_engine=\"truffleruby\""),
21
- _ => println!("cargo:rustc-cfg=ruby_engine=\"mri\""),
22
- }
23
- }
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "mkmf"
4
- require "rb_sys/mkmf"
5
-
6
- create_rust_makefile("omq/rust/omq_backend_rust") do |r|
7
- r.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :release).to_sym
8
- end
@@ -1,16 +0,0 @@
1
- use omq_proto::error::Error as OmqError;
2
-
3
- use crate::rb::RubyErr;
4
-
5
- pub fn map_err(e: OmqError) -> RubyErr {
6
- match e {
7
- OmqError::Closed => RubyErr::io("socket closed"),
8
- OmqError::Timeout => RubyErr::runtime("operation timed out"),
9
- OmqError::Unroutable => RubyErr::runtime("no route to peer"),
10
- OmqError::InvalidEndpoint(msg) => RubyErr::arg(msg),
11
- OmqError::Protocol(msg) => RubyErr::runtime(msg),
12
- OmqError::Io(e) => RubyErr::runtime(e.to_string()),
13
- OmqError::HandshakeFailed(msg) => RubyErr::runtime(msg),
14
- _ => RubyErr::runtime(format!("{e}")),
15
- }
16
- }
@@ -1,51 +0,0 @@
1
- mod error;
2
- mod notify;
3
- mod options;
4
- mod rb;
5
- mod runtime;
6
- mod socket;
7
-
8
- use rb_sys::VALUE;
9
-
10
- use crate::rb::{RbResult, RubyErr};
11
-
12
- fn set_io_threads_impl(n: VALUE) -> RbResult<VALUE> {
13
- let n = rb::value_to_i64(n)?;
14
- if n < 0 {
15
- return Err(RubyErr::arg("io_threads must be non-negative"));
16
- }
17
- let n = usize::try_from(n).map_err(|_| RubyErr::arg("io_threads too large"))?;
18
- socket::set_io_threads(n);
19
- Ok(rb::qnil())
20
- }
21
-
22
- unsafe extern "C" fn set_io_threads(_module: VALUE, n: VALUE) -> VALUE {
23
- rb::wrap(|| set_io_threads_impl(n))
24
- }
25
-
26
- #[unsafe(no_mangle)]
27
- /// # Safety
28
- ///
29
- /// Ruby calls this once while loading the native extension.
30
- pub unsafe extern "C" fn Init_omq_backend_rust() {
31
- rb::wrap_init(init);
32
- }
33
-
34
- fn init() -> RbResult<()> {
35
- #[cfg(ruby_engine = "mri")]
36
- unsafe {
37
- rb_sys::rb_ext_ractor_safe(true);
38
- }
39
-
40
- let omq = unsafe { rb::define_module(c"OMQ")? };
41
- let rust = unsafe { rb::define_module_under(omq, c"Rust")? };
42
- let native = unsafe { rb::define_module_under(rust, c"Native")? };
43
-
44
- unsafe {
45
- rb::define_module_function_1(native, c"io_threads=", set_io_threads)?;
46
- }
47
-
48
- socket::register(native)?;
49
-
50
- Ok(())
51
- }
@@ -1,69 +0,0 @@
1
- use std::os::fd::RawFd;
2
- use std::sync::atomic::{AtomicBool, Ordering};
3
-
4
- pub struct PipeNotify {
5
- read_fd: RawFd,
6
- write_fd: RawFd,
7
- parking: AtomicBool,
8
- }
9
-
10
- unsafe impl Send for PipeNotify {}
11
- unsafe impl Sync for PipeNotify {}
12
-
13
- impl PipeNotify {
14
- pub fn new() -> Self {
15
- let mut fds = [0i32; 2];
16
- let ret = unsafe { libc::pipe2(fds.as_mut_ptr(), libc::O_NONBLOCK | libc::O_CLOEXEC) };
17
- assert!(
18
- ret == 0,
19
- "pipe2 failed: {}",
20
- std::io::Error::last_os_error()
21
- );
22
- Self {
23
- read_fd: fds[0],
24
- write_fd: fds[1],
25
- parking: AtomicBool::new(false),
26
- }
27
- }
28
-
29
- pub fn notify(&self) {
30
- if self.parking.load(Ordering::Acquire) {
31
- self.write_byte();
32
- }
33
- }
34
-
35
- pub fn force_wake(&self) {
36
- self.write_byte();
37
- }
38
-
39
- pub fn read_fd(&self) -> RawFd {
40
- self.read_fd
41
- }
42
-
43
- pub fn park_begin(&self) {
44
- self.parking.store(true, Ordering::Release);
45
- }
46
-
47
- fn write_byte(&self) {
48
- let val: u8 = 1;
49
- loop {
50
- let ret =
51
- unsafe { libc::write(self.write_fd, &val as *const u8 as *const libc::c_void, 1) };
52
- if ret >= 0 {
53
- break;
54
- }
55
- if std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR) {
56
- break;
57
- }
58
- }
59
- }
60
- }
61
-
62
- impl Drop for PipeNotify {
63
- fn drop(&mut self) {
64
- unsafe {
65
- libc::close(self.read_fd);
66
- libc::close(self.write_fd);
67
- }
68
- }
69
- }
@@ -1,220 +0,0 @@
1
- use std::time::Duration;
2
-
3
- use bytes::Bytes;
4
-
5
- use rb_sys::VALUE;
6
-
7
- use crate::rb::{self, RbResult, RubyErr};
8
-
9
- pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
10
- rb::check_hash(hash)?;
11
-
12
- let mut opts = omq_tokio::Options::default();
13
-
14
- if let Some(v) = get_opt_i64(hash, "send_hwm")? {
15
- opts.send_hwm = v.max(0) as u32;
16
- }
17
- if let Some(v) = get_opt_i64(hash, "recv_hwm")? {
18
- opts.recv_hwm = v.max(0) as u32;
19
- }
20
- if let Some(v) = get_opt_f64(hash, "linger")? {
21
- opts.linger = if v.is_infinite() && v.is_sign_positive() {
22
- None
23
- } else {
24
- Some(duration_from_seconds("linger", v)?)
25
- };
26
- }
27
- if let Some(v) = get_opt_bytes(hash, "identity")?
28
- && !v.is_empty()
29
- {
30
- opts.identity = Bytes::from(v);
31
- }
32
- if let Some(v) = get_opt_bool(hash, "router_mandatory")? {
33
- opts.router_mandatory = v;
34
- }
35
- if let Some(v) = get_opt_bool(hash, "conflate")? {
36
- opts.conflate = v;
37
- }
38
- if let Some(v) = get_opt_duration(hash, "heartbeat_interval")? {
39
- opts.heartbeat_interval = Some(v);
40
- }
41
- if let Some(v) = get_opt_duration(hash, "heartbeat_ttl")? {
42
- opts.heartbeat_ttl = Some(v);
43
- }
44
- if let Some(v) = get_opt_duration(hash, "heartbeat_timeout")? {
45
- opts.heartbeat_timeout = Some(v);
46
- }
47
- if let Some(v) = get_opt_usize(hash, "max_message_size")? {
48
- opts.max_message_size = Some(v);
49
- }
50
- if let Some(v) = get_opt_usize(hash, "sndbuf")? {
51
- opts.send_buffer_size = Some(v);
52
- }
53
- if let Some(v) = get_opt_usize(hash, "rcvbuf")? {
54
- opts.recv_buffer_size = Some(v);
55
- }
56
- if let Some(v) = get_opt_bytes(hash, "compression_dict")?
57
- && !v.is_empty()
58
- {
59
- opts.compression_dict = Some(Bytes::from(v));
60
- }
61
- if let Some(v) = get_opt_bool(hash, "compression_auto_train")? {
62
- opts.compression_auto_train = v;
63
- }
64
- if let Some(v) = get_opt_usize(hash, "compression_threshold")? {
65
- opts.compression_threshold = Some(v);
66
- }
67
- if let Some(v) = get_opt_i64(hash, "compression_level")? {
68
- opts.compression_level = Some(v as i32);
69
- }
70
- if let Some(v) = get_opt_usize(hash, "compression_dict_capacity")? {
71
- opts.compression_dict_capacity = Some(v);
72
- }
73
- if let Some(v) = get_opt_usize(hash, "max_recv_dict_size")? {
74
- opts.max_recv_dict_size = Some(v);
75
- }
76
- if let Some(v) = get_opt_i64(hash, "compression_offload_threshold")? {
77
- opts.compression_offload_threshold = if v < 0 { None } else { Some(v as usize) };
78
- }
79
- if let Some(v) = get_opt_string(hash, "on_mute")? {
80
- opts.on_mute = match v.as_str() {
81
- "drop_newest" | "drop" => omq_tokio::OnMute::DropNewest,
82
- _ => omq_tokio::OnMute::Block,
83
- };
84
- }
85
-
86
- if let Some(v) = get_opt_f64(hash, "reconnect_interval")? {
87
- opts.reconnect = omq_proto::options::ReconnectPolicy::Fixed(duration_from_seconds(
88
- "reconnect_interval",
89
- v,
90
- )?);
91
- }
92
- if let Some(min) = get_opt_f64(hash, "reconnect_interval_min")? {
93
- let max = get_opt_f64(hash, "reconnect_interval_max")?.unwrap_or(min * 16.0);
94
- opts.reconnect = omq_proto::options::ReconnectPolicy::Exponential {
95
- min: duration_from_seconds("reconnect_interval min", min)?,
96
- max: duration_from_seconds("reconnect_interval max", max)?,
97
- };
98
- }
99
-
100
- if let Some(mech_type) = get_opt_string(hash, "mechanism_type")? {
101
- apply_mechanism(hash, &mech_type, &mut opts)?;
102
- }
103
-
104
- Ok(opts)
105
- }
106
-
107
- fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options) -> RbResult<()> {
108
- match mech_type {
109
- "null" => {}
110
-
111
- #[cfg(feature = "curve")]
112
- "curve" => {
113
- let is_server = get_opt_bool(hash, "mechanism_server")?.unwrap_or(false);
114
- let pub_key = get_opt_bytes(hash, "mechanism_public_key")?;
115
- let sec_key = get_opt_bytes(hash, "mechanism_secret_key")?;
116
-
117
- if is_server {
118
- if let (Some(pk), Some(sk)) = (pub_key, sec_key) {
119
- let keypair = omq_proto::CurveKeypair {
120
- public: omq_proto::CurvePublicKey::from_bytes(to_32(&pk, "public key")?),
121
- secret: omq_proto::CurveSecretKey::from_bytes(to_32(&sk, "secret key")?),
122
- };
123
- opts.mechanism = omq_proto::MechanismSetup::CurveServer {
124
- our_keypair: keypair,
125
- options: omq_proto::CurveServerOptions::default(),
126
- };
127
- }
128
- } else {
129
- let srv_key = get_opt_bytes(hash, "mechanism_server_key")?;
130
- if let (Some(pk), Some(sk), Some(svk)) = (pub_key, sec_key, srv_key) {
131
- let keypair = omq_proto::CurveKeypair {
132
- public: omq_proto::CurvePublicKey::from_bytes(to_32(&pk, "public key")?),
133
- secret: omq_proto::CurveSecretKey::from_bytes(to_32(&sk, "secret key")?),
134
- };
135
- opts.mechanism = omq_proto::MechanismSetup::CurveClient {
136
- our_keypair: keypair,
137
- server_public: omq_proto::CurvePublicKey::from_bytes(to_32(
138
- &svk,
139
- "server key",
140
- )?),
141
- };
142
- }
143
- }
144
- }
145
-
146
- _ => {}
147
- }
148
- Ok(())
149
- }
150
-
151
- fn to_32(bytes: &[u8], label: &str) -> RbResult<[u8; 32]> {
152
- bytes.try_into().map_err(|_| {
153
- RubyErr::arg(format!(
154
- "{label} must be exactly 32 bytes, got {}",
155
- bytes.len()
156
- ))
157
- })
158
- }
159
-
160
- fn get_opt_bytes(hash: VALUE, key: &str) -> RbResult<Option<Vec<u8>>> {
161
- match rb::hash_get(hash, key)? {
162
- Some(v) if v == rb::qnil() => Ok(None),
163
- Some(v) => Ok(Some(rb::value_to_bytes(v)?)),
164
- None => Ok(None),
165
- }
166
- }
167
-
168
- fn get_opt_string(hash: VALUE, key: &str) -> RbResult<Option<String>> {
169
- match rb::hash_get(hash, key)? {
170
- Some(v) if v == rb::qnil() => Ok(None),
171
- Some(v) => Ok(Some(rb::value_to_string(v)?)),
172
- None => Ok(None),
173
- }
174
- }
175
-
176
- fn get_opt_i64(hash: VALUE, key: &str) -> RbResult<Option<i64>> {
177
- match rb::hash_get(hash, key)? {
178
- Some(v) if v == rb::qnil() => Ok(None),
179
- Some(v) => Ok(Some(rb::value_to_i64(v)?)),
180
- None => Ok(None),
181
- }
182
- }
183
-
184
- fn get_opt_f64(hash: VALUE, key: &str) -> RbResult<Option<f64>> {
185
- match rb::hash_get(hash, key)? {
186
- Some(v) if v == rb::qnil() => Ok(None),
187
- Some(v) => Ok(Some(rb::value_to_f64(v)?)),
188
- None => Ok(None),
189
- }
190
- }
191
-
192
- fn get_opt_usize(hash: VALUE, key: &str) -> RbResult<Option<usize>> {
193
- let Some(v) = get_opt_i64(hash, key)? else {
194
- return Ok(None);
195
- };
196
-
197
- usize::try_from(v)
198
- .map(Some)
199
- .map_err(|_| RubyErr::arg(format!("{key} must be non-negative")))
200
- }
201
-
202
- fn get_opt_bool(hash: VALUE, key: &str) -> RbResult<Option<bool>> {
203
- match rb::hash_get(hash, key)? {
204
- Some(v) if v == rb::qnil() => Ok(None),
205
- Some(v) => Ok(Some(rb::value_to_bool(v)?)),
206
- None => Ok(None),
207
- }
208
- }
209
-
210
- fn get_opt_duration(hash: VALUE, key: &str) -> RbResult<Option<Duration>> {
211
- match get_opt_f64(hash, key)? {
212
- Some(v) => Ok(Some(duration_from_seconds(key, v)?)),
213
- None => Ok(None),
214
- }
215
- }
216
-
217
- fn duration_from_seconds(label: &str, value: f64) -> RbResult<Duration> {
218
- Duration::try_from_secs_f64(value)
219
- .map_err(|_| RubyErr::arg(format!("{label} must be finite and non-negative")))
220
- }