Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating error callbacks #215

Merged
merged 10 commits into from
Sep 17, 2021
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ gdal-sys = { path = "gdal-sys", version = "^0.5"}
ndarray = {version = "0.15", optional = true }
chrono = { version = "0.4", optional = true }
bitflags = "1.2"
once_cell = "1.8"

[build-dependencies]
gdal-sys = { path = "gdal-sys", version= "^0.5"}
Expand Down
109 changes: 108 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@
//! Refer to [GDAL `ConfigOptions`](https://trac.osgeo.org/gdal/wiki/ConfigOptions) for
//! a full list of options.

use crate::errors::Result;
use gdal_sys::{CPLErr, CPLErrorNum, CPLGetErrorHandlerUserData};
use libc::{c_char, c_void};

use crate::errors::{CplErrType, Result};
use crate::utils::_string;
use once_cell::sync::Lazy;
use std::ffi::CString;
use std::pin::Pin;
use std::sync::Mutex;

/// Set a GDAL library configuration option
///
Expand Down Expand Up @@ -108,10 +114,111 @@ pub fn clear_thread_local_config_option(key: &str) -> Result<()> {
Ok(())
}

type CallbackType = dyn FnMut(CplErrType, i32, &str) + 'static + Send;
type PinnedCallback = Pin<Box<Box<CallbackType>>>;
rmanoka marked this conversation as resolved.
Show resolved Hide resolved

/// Static variable that holds the current error callback function
static ERROR_CALLBACK: Lazy<Mutex<Option<PinnedCallback>>> = Lazy::new(Default::default);

/// Set a custom error handler for GDAL.
/// Could be overwritten by setting a thread-local error handler.
///
/// Note:
/// Stores the callback in the static variable [`ERROR_CALLBACK_THREAD_SAFE`].
rmanoka marked this conversation as resolved.
Show resolved Hide resolved
/// Internally, it passes a pointer to the callback to GDAL as `pUserData`.
///
pub fn set_error_handler<F>(callback: F)
where
F: FnMut(CplErrType, i32, &str) + 'static + Send,
{
unsafe extern "C" fn error_handler(
error_type: CPLErr::Type,
error_num: CPLErrorNum,
error_msg_ptr: *const c_char,
) {
let error_msg = _string(error_msg_ptr);
let error_type: CplErrType = error_type.into();

// reconstruct callback from user data pointer
let callback_raw = CPLGetErrorHandlerUserData();
let callback: &mut Box<CallbackType> = &mut *(callback_raw as *mut Box<_>);

callback(error_type, error_num as i32, &error_msg);
}

// pin memory location of callback for sending its pointer to GDAL
let mut callback: PinnedCallback = Box::pin(Box::new(callback));

let callback_ref: &mut Box<CallbackType> = callback.as_mut().get_mut();
unsafe {
gdal_sys::CPLSetErrorHandlerEx(Some(error_handler), callback_ref as *mut _ as *mut c_void);
};

let mut callback_lock = match ERROR_CALLBACK.lock() {
rmanoka marked this conversation as resolved.
Show resolved Hide resolved
Ok(guard) => guard,
// poor man's lock poisoning handling, i.e., ignoring it
Err(poison_error) => poison_error.into_inner(),
};
// store callback in static variable so we avoid a dangling pointer
callback_lock.replace(callback);
}

/// Remove a custom error handler for GDAL.
pub fn remove_error_handler() {
unsafe {
gdal_sys::CPLSetErrorHandler(None);
};

// drop callback

let mut callback_lock = match ERROR_CALLBACK.lock() {
Ok(guard) => guard,
// poor man's lock poisoning handling, i.e., ignoring it
Err(poison_error) => poison_error.into_inner(),
};

callback_lock.take();
}

#[cfg(test)]
mod tests {

use std::sync::{Arc, Mutex};

use super::*;

#[test]
fn error_handler() {
let errors: Arc<Mutex<Vec<(CplErrType, i32, String)>>> = Arc::new(Mutex::new(vec![]));

let errors_clone = errors.clone();

set_error_handler(move |a, b, c| {
errors_clone.lock().unwrap().push((a, b, c.to_string()));
});

unsafe {
let msg = CString::new("foo".as_bytes()).unwrap();
gdal_sys::CPLError(CPLErr::CE_Failure, 42, msg.as_ptr());
};

unsafe {
let msg = CString::new("bar".as_bytes()).unwrap();
gdal_sys::CPLError(std::mem::transmute(CplErrType::Warning), 1, msg.as_ptr());
};

remove_error_handler();

let result: Vec<(CplErrType, i32, String)> = errors.lock().unwrap().clone();
assert_eq!(
result,
vec![
(CplErrType::Failure, 42, "foo".to_string()),
(CplErrType::Warning, 1, "bar".to_string())
]
);
}

#[test]
fn test_set_get_option() {
assert!(set_config_option("GDAL_CACHEMAX", "128").is_ok());
Expand Down
21 changes: 21 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,24 @@ pub enum GdalError {
#[error("Unable to unlink mem file: {file_name}")]
UnlinkMemFile { file_name: String },
}

/// A wrapper for [`CPLErr::Type`] that reflects it as an enum
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(C)]
pub enum CplErrType {
None = 0,
Debug = 1,
Warning = 2,
Failure = 3,
Fatal = 4,
}

impl From<CPLErr::Type> for CplErrType {
fn from(error_type: CPLErr::Type) -> Self {
if error_type > 4 {
return Self::None; // fallback type, should not happen
}

unsafe { std::mem::transmute(error_type) }
}
}