Skip to main content

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:

  1. 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
  2. Verify Ruby installation:

ruby -v
which ruby

Linker Errors

Undefined reference to `rb_define_module`

Solution:

  1. Check your Cargo.toml:
[dependencies]
rb-sys = { version = "0.9", features = ["stable-api"] }
  1. 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:

  1. Accessing deallocated Ruby objects
  2. Missing GC mark implementation
  3. 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:

  1. 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);
}
  1. Use memory profiling:
GC.start
GC.stat

Thread Safety Issues

ThreadError: deadlock detected

Solution:

  1. 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(())
}
}
  1. 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

  1. Set environment variables:
export RUST_BACKTRACE=1
export RUST_LOG=debug
  1. 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

  1. Compile with debug symbols:
[profile.dev]
debug = true
  1. Run with debugger:
lldb -- ruby -I lib test/my_test.rb
  1. Set breakpoints:
(lldb) b my_extension.c:42
(lldb) continue

Memory Analysis

  1. Use Ruby's memory profiler:
require "memory_profiler"

MemoryProfiler.report do
# Your code here
end.pretty_print
  1. 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:

  1. Install Visual Studio build tools
  2. Set environment:
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"

macOS

Dyld Error: Library not loaded

Solution:

  1. Check library paths:
otool -L your_extension.bundle
  1. Fix rpath issues:
# extconf.rb
append_ldflags("-Wl,-rpath,@loader_path/")

Linux

Error loading shared library

Solution:

  1. Update ldconfig:
sudo ldconfig
  1. Check library dependencies:
ldd your_extension.so

Best Practices

  1. Systematic Debugging:

    • Start with logging
    • Use debugger for complex issues
    • Profile memory for leaks
  2. 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)
}
  1. 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);
}
}
  1. 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
}