Troubleshooting
This guide helps you diagnose and fix common issues when developing Ruby extensions with rb-sys.
Rust
Common Build Issues
Missing Ruby Headers
Compilation failed: Cannot find ruby.h
Solution:
-
Install Ruby development files:
- Ubuntu/Debian:
apt-get install ruby-dev
- Fedora/RHEL:
dnf install ruby-devel
- macOS: Headers come with Ruby
- Windows: Headers come with Ruby installation
- Ubuntu/Debian:
-
Verify Ruby installation:
ruby -v
which ruby
Linker Errors
Undefined reference to `rb_define_module`
Solution:
- Check your Cargo.toml:
[dependencies]
rb-sys = { version = "0.9", features = ["stable-api"] }
- Verify extconf.rb:
require "rb_sys/mkmf"
create_rust_makefile("my_extension")
Version Mismatch
Error: Incompatible rb-sys version
Solution: Ensure consistent versions:
# Gemfile
gem "rb_sys", "~> 0.9.0"
# Cargo.toml
[dependencies]
rb-sys = { version = "0.9.0", features = ["stable-api"] }
Runtime Issues
Segmentation Faults
[BUG] Segmentation fault
Common Causes:
- Accessing deallocated Ruby objects
- Missing GC mark implementation
- Thread safety issues
Solution:
use magnus::{DataTypeFunctions, TypedData, Value, gc::Marker};
#[derive(TypedData)]
#[magnus(class = "MyClass", mark)]
struct MyObject {
// Hold references properly
data: Value
}
// SAFETY: MyObject is only accessed from Ruby threads with the GVL
unsafe impl Send for MyObject {}
impl DataTypeFunctions for MyObject {
fn mark(&self, marker: &Marker) {
// Mark all Ruby objects
marker.mark(self.data);
}
}
Memory Leaks
Symptoms:
- Growing memory usage
- Slow performance over time
Solution:
- Enable debugging:
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(debug_assertions)]
static ALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0);
fn track_allocation() {
#[cfg(debug_assertions)]
ALLOCATION_COUNT.fetch_add(1, Ordering::SeqCst);
}
- Use memory profiling:
GC.start
GC.stat
Thread Safety Issues
ThreadError: deadlock detected
Solution:
- Use proper thread safety patterns:
use std::sync::Mutex;
use magnus::{Error, Value};
struct ThreadSafeObject {
data: Mutex<Vec<Value>>
}
// SAFETY: ThreadSafeObject uses proper synchronization via Mutex
unsafe impl Send for ThreadSafeObject {}
unsafe impl Sync for ThreadSafeObject {}
impl ThreadSafeObject {
fn modify_data(&self) -> Result<(), Error> {
let mut data = self.data.lock().map_err(|_| Error::new(magnus::exception::runtime_error(), "Failed to acquire lock"))?;
// Modify data safely
Ok(())
}
}
- Release GVL when appropriate:
use rb_sys::rb_thread_call_without_gvl;
use std::ptr;
use std::os::raw::c_void;
extern "C" fn heavy_computation(data: *mut c_void) -> *mut c_void {
// Your CPU-intensive work here
// This runs without holding the GVL
ptr::null_mut()
}
fn release_gvl_example() {
unsafe {
rb_thread_call_without_gvl(
Some(heavy_computation),
ptr::null_mut(),
None,
ptr::null_mut()
);
}
}
Debug Techniques
Enable Debug Logging
- Set environment variables:
export RUST_BACKTRACE=1
export RUST_LOG=debug
- Add logging to your code:
use log::{debug, error};
use magnus::Error;
fn process_data(input: &str) -> Result<String, Error> {
debug!("Processing: {}", input);
// Processing logic
let error_condition = false; // Example condition
let result = String::from("processed"); // Example result
if error_condition {
let error = "example error";
error!("Failed to process: {}", error);
return Err(Error::new(
magnus::exception::runtime_error(),
"Processing failed"
));
}
Ok(result)
}
GDB/LLDB Debugging
- Compile with debug symbols:
[profile.dev]
debug = true
- Run with debugger:
lldb -- ruby -I lib test/my_test.rb
- Set breakpoints:
(lldb) b my_extension.c:42
(lldb) continue
Memory Analysis
- Use Ruby's memory profiler:
require "memory_profiler"
MemoryProfiler.report do
# Your code here
end.pretty_print
- Track object allocations:
GC.stat
object_count = ObjectSpace.count_objects
puts "Live objects: #{object_count[:TOTAL]}"
Platform-Specific Issues
Windows
Error linking with `link.exe`
Solution:
- Install Visual Studio build tools
- Set environment:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
macOS
Dyld Error: Library not loaded
Solution:
- Check library paths:
otool -L your_extension.bundle
- Fix rpath issues:
# extconf.rb
append_ldflags("-Wl,-rpath,@loader_path/")
Linux
Error loading shared library
Solution:
- Update ldconfig:
sudo ldconfig
- Check library dependencies:
ldd your_extension.so
Best Practices
-
Systematic Debugging:
- Start with logging
- Use debugger for complex issues
- Profile memory for leaks
-
Error Handling:
use magnus::Error;
fn process_data(input: &str) -> Result<String, Error> {
if input.is_empty() {
return Err(Error::new(
magnus::exception::arg_error(),
"Input cannot be empty"
));
}
// Processing logic
let result = format!("Processed: {}", input);
Ok(result)
}
- Memory Management:
use magnus::{TypedData, DataTypeFunctions, Value, gc::Marker};
#[derive(TypedData)]
#[magnus(class = "MyClass", mark)]
struct MyObject {
data: Value
}
// SAFETY: MyObject is only accessed from Ruby threads with the GVL
unsafe impl Send for MyObject {}
impl DataTypeFunctions for MyObject {
fn mark(&self, marker: &Marker) {
marker.mark(self.data);
}
}
- Cross-Platform Compatibility:
#[cfg(target_os = "windows")]
fn platform_specific() {
// Windows-specific code
}
#[cfg(not(target_os = "windows"))]
fn platform_specific() {
// Unix-like systems code
}