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

Refactor FFI error handling for better reliability #210

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ jobs:
build-android:
name: Build library (Android)
needs: [checks]
# NB: RUST_VERSION must be <1.68 here to support NDK 17
env:
RUST_VERSION: "1.67"

Expand Down
68 changes: 50 additions & 18 deletions src/ffi/error.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
use crate::error::{Error, ErrorKind};

use std::collections::BTreeMap;
use std::os::raw::c_char;
use std::sync::RwLock;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::Mutex;
use std::time::{Duration, Instant};

use ffi_support::rust_string_to_c;

use once_cell::sync::Lazy;

static LAST_ERROR: Lazy<RwLock<Option<Error>>> = Lazy::new(|| RwLock::new(None));
struct StoredError {
error: Error,
time: Instant,
}

static ERRORS: Lazy<Mutex<BTreeMap<ErrorHandle, StoredError>>> =
Lazy::new(|| Mutex::new(BTreeMap::new()));

static ERROR_INDEX: AtomicI64 = AtomicI64::new(1);

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ErrorHandle(i64);

impl ErrorHandle {
pub const OK: Self = Self(0);

pub fn next() -> Self {
Self(ERROR_INDEX.fetch_add(1, Ordering::Relaxed))
}
}

pub const ERROR_EXPIRY: Duration = Duration::from_secs(30);

#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize)]
#[repr(i64)]
Expand Down Expand Up @@ -50,37 +74,45 @@ impl<T> From<Result<T, Error>> for ErrorCode {
}

#[no_mangle]
pub extern "C" fn askar_get_current_error(error_json_p: *mut *const c_char) -> ErrorCode {
trace!("askar_get_current_error");
pub extern "C" fn askar_fetch_error(
handle: ErrorHandle,
error_json_p: *mut *const c_char,
) -> ErrorCode {
trace!("askar_fetch_error");

let error = rust_string_to_c(get_current_error_json());
let error = rust_string_to_c(fetch_error_json(handle));
unsafe { *error_json_p = error };

ErrorCode::Success
}

pub fn get_current_error_json() -> String {
pub fn fetch_error_json(handle: ErrorHandle) -> String {
#[derive(Serialize)]
struct ErrorJson {
code: usize,
message: String,
}

if let Some(err) = Option::take(&mut *LAST_ERROR.write().unwrap()) {
let message = err.to_string();
let code = ErrorCode::from(err.kind()) as usize;
let mut errors = ERRORS.lock().unwrap();
if let Some(err) = errors.remove(&handle) {
let message = err.error.to_string();
let code = ErrorCode::from(err.error.kind()) as usize;
serde_json::json!(&ErrorJson { code, message }).to_string()
} else {
r#"{"code":0,"message":null}"#.to_owned()
}
}

pub fn set_last_error(error: Option<Error>) -> ErrorCode {
trace!("askar_set_last_error");
let code = match error.as_ref() {
Some(err) => err.kind.into(),
None => ErrorCode::Success,
};
*LAST_ERROR.write().unwrap() = error;
code
pub fn store_error(error: Error) -> ErrorHandle {
trace!("askar_store_error");
let mut errors = ERRORS.lock().unwrap();
let time = Instant::now();
while let Some(entry) = errors.first_entry() {
if entry.get().time + ERROR_EXPIRY < time {
entry.remove();
}
}
let handle = ErrorHandle::next();
errors.insert(handle, StoredError { error, time });
handle
}
Loading
Loading