Replace Result returns in API with callback based error handling#70
Replace Result returns in API with callback based error handling#70
Conversation
With this change, all functions and operations return appropriate values/objects instead of wrapping those values inside a Result<> object. All errors reported by FFI calls are handled by a global error handling function that is registered by a call to the function `register_error_handler` that accepts either a closure or a function. By default, the error handler is set to a internal function that prints the error message to standard output and then panics.
|
@arrayfire/rust-devel Feedback please. |
src/error.rs
Outdated
|
|
||
| pub fn handle_error_general(error_code: AfError) { | ||
| match error_code { | ||
| AfError::SUCCESS => {}, /* No-op */ |
There was a problem hiding this comment.
Suggestion: use the function (maybe move it outside of the trait impl) to get the &str 's here to avoid having code dupes:
impl Error for AfError {
fn description(&self) -> &str {There was a problem hiding this comment.
Good idea, will look into it.
@jramapuram Was wondering if global static HANDLE_ERROR should be mutex protected apart from being inside unsafe block ? Any idea about that ?
There was a problem hiding this comment.
First question: why does it need to be unsafe? Its just a standard rust panic function.
Don't see a need for mutexing. There is no shared data in these handlers. They are independent.
There was a problem hiding this comment.
Enclosing the code that mutates mutable static global inside an unsafe block is mandatory. Check here.
There was a problem hiding this comment.
Ah ok, that makes sense. In this case it would only need a mutex if you think people would be modifying the handler (at runtime) from multiple threads. If that is the case then yes it should be mutexed.
There was a problem hiding this comment.
Yes, we would need a static mutex if we need one at all.
There was a problem hiding this comment.
True, but it is conceivable that the user wants a different method of error handling at a different point in the program execution and we should be robust in upholding that
There was a problem hiding this comment.
Regarding Error trait, i didn't notice. I have already implemented in defines module. Just need to reuse it.
|
@9prady9 : Looks good besides the comment above! |
|
@jramapuram StaticMutex is unstable and can't be used unless in Non-stable channels. I am not sure how else we can do it as of now. Other than that, i think i finished other changes. |
|
How about using lazy static: #[macro_use]
extern crate lazy_static;
use std::sync::Mutex;
lazy_static! {
static ref ARRAY: Mutex<Vec<u8>> = Mutex::new(vec![]);
}
fn do_a_call() {
ARRAY.lock().unwrap().push(1);
}
fn main() {
do_a_call();
do_a_call();
do_a_call();
println!("called {}", ARRAY.lock().unwrap().len());
}(http://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton) |
Add tests directory with a function to check the function `register_error_handler`
|
@jramapuram Check out the latest commit and let me know what you think. |
src/error.rs
Outdated
|
|
||
| pub type ErrorCallback = Fn(AfError); | ||
|
|
||
| pub static mut HANDLE_ERROR: &'static ErrorCallback = &handle_error_general; |
There was a problem hiding this comment.
@9prady9 : I don't think this does what you are expecting.
I would assume this would look like [havent tested] :
lazy_static! {
static ref ARRAY: Mutex<ErrorCallback> = Mutex::new(&handle_error_general);
}Then every call would be HANDLE_ERROR.lock().unwrap()(af_err)
This would ensure that when you swap out the HANDLE_ERROR function from a thread it would then lock the currently stored function (and thus ensure that the currently registered one would be called).
There was a problem hiding this comment.
Yes, i see it now, this is wrong. Won't help protect the data.
I have tried passing ErrorCallback to Mutex, but that didn't compile for me. Therefore, i created the mutex around an integer.
Compiling arrayfire v3.3.0 (file:///E:/gitroot/arrayfire-rust)
<lazy_static macros>:30:7: 30:23 error: casting `usize` as `*const std::sync::mutex::Mutex<core::ops::Fn(defines::AfError) + 'static>` is invalid
<lazy_static macros>:30 $ T = 0 as * const $ T ; static mut ONCE : Once = ONCE_INIT ; ONCE . call_once
^~~~~~~~~~~~~~~~
<lazy_static macros>:4:1: 5:73 note: in this expansion of lazy_static! (defined in <lazy_static macros>)
src\error.rs:10:1: 12:2 note: in this expansion of lazy_static! (defined in <lazy_static macros>)
error: aborting due to previous error
Could not compile `arrayfire`.It seems like, internally lazy_static is casting the pointer to object to some integral type or something like that.
There was a problem hiding this comment.
Can you try a reference to a function (instead of a function) or even a Box maybe?
There was a problem hiding this comment.
I think i tried reference, but that doesn't work because it will require life time specifier. Adding 'static life time brings us back to the old problem of not being able to create a Mutex around 'static life time object.
I don't remember trying Box but i suspect it will have similar problem. But will give it a shot.
There was a problem hiding this comment.
@jramapuram
Hm, i think i found the root cause of this. Fn(AfError) needs to have Send and Sync traits implemented for it to be used either in the context of Box or static mut.
I am getting the error explained in the output of rustc --explain E0117 if try to impl implement Send/Sync.
Removed an incorrect implementation to provide thread safety for the function register_error_handler.
Also, modifies binary operators to use Array objects instead of borrowed references.
| idxrs.set_index(&Seq::<f32>::default(), 0, Some(false)); | ||
| idxrs.set_index(&Seq::<f32>::default(), 1, Some(false)); | ||
| let tmp = assign_gen(self as &Array, &idxrs, & $func(self as &Array, &rhs)); | ||
| mem::replace(self, tmp); |
There was a problem hiding this comment.
@jramapuram Any idea if i am creating any leaks here ?
There was a problem hiding this comment.
@9prady9 : sorry for the delay. According to the docs mem::replace does not deinitialize the old value. It does however return the previous value. I think de-initializing manually here is the apt choice to prevent a dangling pointer. I'm not too worried about the actual array since that is a smart pointer in C++. Thoughts @pavanky ?
|
@9prady9 : So you can't implement |
|
@jramapuram Nope, doesn't seem like it. I tried doing something similar to the following struct MyBox(*mut u8);
unsafe impl Send for MyBox {}
unsafe impl Sync for MyBox {}for src\error.rs:24:1: 24:38 error: the impl does not reference any types defined in this crate; only traits defined in the current crate can be implemented for arbitrary types [E0117]
src\error.rs:24 unsafe impl Send for ErrorCallback {}
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src\error.rs:24:1: 24:38 help: run `rustc --explain E0117` to see a detailed explanation |
|
Putting the function pointer inside a struct compiled the code, but have to test this if it is doing what we need to do. I am in new areas (to me) of Rust now :) UPDATE: ha, seems like if the variable type has fixed size during compile time, then we can do it. Hence the pointer stuff works. |
|
@jramapuram I came up with something like this and modified the test to look like this and trying to debug any issues with this. |
|
@arrayfire/rust-devel Please take a look at the latest changes for error handler thread safety and let me know if there are any issues. Thanks. |
tests/lib.rs
Outdated
| // 1 => thread::sleep(Duration::from_millis(20)), | ||
| // 2 => thread::sleep(Duration::from_millis(10)), | ||
| // _ => (), | ||
| //} |
There was a problem hiding this comment.
My bad, will remove them, i must have forgotten them after some debugging.
|
Looks good @9prady9 ! |
Fixes #45
Fixes #38
With this change, all functions and operations return appropriate
values/objects instead of wrapping those values inside a Result<>
object. All errors reported by FFI calls are handled by a global
error handling function that is registered by a call to the function
register_error_handlerthat accepts either a closure or a function.By default, the error handler is set to a internal function that
prints the error message to standard output and then panics.