I'm a Rust newb and need some help!
The current project I am working on is to replace an existing application written in Python with Rust. I am struggling with one of the components that integrates with an existing C library. The interface is quite simple and I have setup bindgen for automatically generating the rust -> C types.
The program seems to work; however, it crashes with a memory error upon shutdown. Specifically, I get this: tcache_thread_shutdown(): unaligned tcache chunk detected
.
My main two objectives for this question are:
- Determine how to fix the crash.
- How can I add a better abstraction layer on top of the rust bindgen generated functions. Specifically, I would like to have a rust wrapper function that accepts a closure for
start_sensor
wrapper function.
All of the code can be found here: GitHub - SeeRich/mre-rust-cpp
Should be pretty simple to build/reproduce (Makefile included with instructions in the README). Please let me know if you have trouble running/reproducing this. I reduced the code as far as I could so others could easily run themselves (if wanted).
The main rust code (rs/src/sensor.rs):
- The unsafe start/stop functions come directly from the C header file as well as
DataPayload_t
. - The example is a litte verbose because I was trying to keep it as close to the original as possible while still exhibiting the issue.
use crate::DataPayload_t;
use crate::{start, stop};
use std::ffi::c_void;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
// Callback from C library
extern "C" fn sensor_data_callback(data: *mut DataPayload_t, user_data: *mut c_void) {
unsafe {
if data.is_null() || user_data.is_null() {
eprintln!("Invalid arguments in callback!");
return;
}
println!("cb - {:?}", *data);
let logger = Arc::from_raw(user_data as *mut DataLogger);
logger.on_sensor_msg(&(*data));
}
}
// Wrapper around starting sensor
fn start_sensor(user_data: *mut c_void) {
unsafe {
start(Some(sensor_data_callback), user_data);
}
}
// Wrapper around stopping sensor
fn stop_sensor() {
unsafe {
stop();
}
}
pub struct SensorInstance {
inner: Arc<Mutex<SensorInstanceInner>>,
}
impl SensorInstance {
pub fn new() -> Self {
SensorInstance {
inner: Arc::new(Mutex::new(SensorInstanceInner::new())),
}
}
pub fn start(&self) {
let mut inner = self.inner.lock().unwrap();
inner.start();
}
pub fn stop(&self) {
let mut inner = self.inner.lock().unwrap();
inner.stop();
}
}
impl Default for SensorInstance {
fn default() -> Self {
Self::new()
}
}
struct DataLogger {}
impl DataLogger {
pub fn new() -> Self {
DataLogger {}
}
pub fn on_sensor_msg(&self, result: &DataPayload_t) {
// Really does something more complex...
println!("Logging data: {:?}", result);
}
}
struct SensorInstanceInner {
running: bool,
data_logger: Option<Arc<DataLogger>>,
sensor_thread: Option<JoinHandle<()>>,
}
impl SensorInstanceInner {
fn new() -> Self {
SensorInstanceInner {
running: false,
data_logger: None,
sensor_thread: None,
}
}
fn start_sensor_thread(&mut self) {
println!("Starting sensor processing");
// Spawn a thread that will call start sensor, this will block the thread until complete
let logger = self.data_logger.clone().unwrap().clone();
let handle = thread::spawn(|| {
let user_data_ptr = Arc::into_raw(logger);
start_sensor(user_data_ptr as *mut c_void);
});
self.sensor_thread = Some(handle);
}
fn start(&mut self) {
if self.running {
return;
}
// Create data logger
let logger = Arc::new(DataLogger::new());
self.data_logger = Some(logger.clone());
self.start_sensor_thread();
self.running = true;
}
fn stop(&mut self) {
if !self.running {
println!("ERROR - sensor is not running");
return;
}
// Stop sensor processing
stop_sensor();
// Wait for thread
if self.sensor_thread.is_some() {
let _ = self.sensor_thread.take().unwrap().join();
}
self.running = false;
}
}
The C header file (cpp/sensor.h):
#ifndef LIB_SENSOR_H
#define LIB_SENSOR_H
#include <stdbool.h>
#include <stdint.h>
/// Sensor data payload
typedef struct DataPayload_t
{
uint64_t data_1;
uint32_t data_2;
bool data_3;
} DataPayload_t;
/// Callback for sensor data (void* is user data)
typedef void sensor_data_cb_t(DataPayload_t*, void*);
/// Start function to begin processing
void start(sensor_data_cb_t* sensor_data_cb, void* user_data);
/// Stop function to end processing
void stop();
#endif
Rust test code (rs/src/bin/test.rs):
use sensorrs::sensor::SensorInstance;
fn main() {
let sensor = SensorInstance::new();
sensor.start();
std::thread::sleep(std::time::Duration::from_millis(300));
sensor.stop();
}