use crate::error::{DaosError, Result};
use crate::unsafe_inner::ffi::{daos_fini as ffi_daos_fini, daos_init as ffi_daos_init};
use std::sync::{Mutex, OnceLock};
static RUNTIME_MUTEX: Mutex<()> = Mutex::new(());
static RUNTIME_REFCOUNT: OnceLock<Mutex<usize>> = OnceLock::new();
#[inline]
fn runtime_refcount() -> &'static Mutex<usize> {
RUNTIME_REFCOUNT.get_or_init(|| Mutex::new(0))
}
pub fn is_runtime_initialized() -> bool {
let _guard = RUNTIME_MUTEX.lock().unwrap();
*runtime_refcount().lock().unwrap() > 0
}
#[derive(Debug)]
pub struct DaosRuntime {
_private: (),
}
impl DaosRuntime {
pub fn new() -> Result<Self> {
let _guard = RUNTIME_MUTEX.lock().unwrap();
let mut refcount = runtime_refcount().lock().unwrap();
if *refcount == 0 {
ffi_daos_init()?;
}
*refcount += 1;
Ok(Self { _private: () })
}
pub fn try_new() -> Result<Self> {
Self::new()
}
pub fn is_initialized(&self) -> bool {
is_runtime_initialized()
}
}
impl Default for DaosRuntime {
fn default() -> Self {
Self::new().expect("DaosRuntime::default(): failed to initialize DAOS")
}
}
impl Drop for DaosRuntime {
fn drop(&mut self) {
let _guard = RUNTIME_MUTEX.lock().unwrap();
let mut refcount = runtime_refcount().lock().unwrap();
if *refcount == 0 {
return;
}
*refcount -= 1;
if *refcount == 0 {
if let Err(e) = ffi_daos_fini() {
eprintln!(
"DaosRuntime::drop: daos_fini() failed with {:?}, \
continuing with drop anyway",
e
);
}
}
}
}
pub const RUNTIME_NOT_INIT_ERROR: &str =
"DAOS runtime not initialized. Create a DaosRuntime instance first.";
#[inline]
pub fn require_runtime() -> Result<()> {
let _guard = RUNTIME_MUTEX.lock().unwrap();
if *runtime_refcount().lock().unwrap() > 0 {
Ok(())
} else {
Err(DaosError::InvalidArg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_runtime_init_and_fini() {
let runtime = DaosRuntime::new().expect("failed to create DaosRuntime");
assert!(runtime.is_initialized());
assert!(is_runtime_initialized());
drop(runtime);
assert!(!is_runtime_initialized());
}
#[test]
fn test_runtime_default() {
let runtime = DaosRuntime::default();
assert!(runtime.is_initialized());
}
#[test]
fn test_repeated_init() {
while is_runtime_initialized() {
drop(DaosRuntime::new());
}
let runtime1 = DaosRuntime::new().expect("failed to create first DaosRuntime");
assert!(runtime1.is_initialized());
let runtime2 = DaosRuntime::new().expect("failed to create second DaosRuntime");
assert!(runtime2.is_initialized());
drop(runtime2);
assert!(runtime1.is_initialized());
drop(runtime1);
assert!(!is_runtime_initialized());
}
#[test]
fn test_require_runtime_when_not_init() {
while is_runtime_initialized() {
drop(DaosRuntime::new());
}
assert!(!is_runtime_initialized());
let result = require_runtime();
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), DaosError::InvalidArg));
}
#[test]
fn test_require_runtime_when_init() {
let runtime = DaosRuntime::new().expect("failed to create DaosRuntime");
assert!(runtime.is_initialized());
let result = require_runtime();
assert!(result.is_ok());
}
#[test]
fn test_threaded_runtime() {
let runtime = DaosRuntime::new().expect("failed to create DaosRuntime");
assert!(runtime.is_initialized());
let handle = thread::spawn(move || {
assert!(is_runtime_initialized());
});
handle.join().expect("thread panicked");
assert!(runtime.is_initialized());
}
#[test]
fn test_drop_never_panics() {
let runtime = DaosRuntime::new().expect("failed to create DaosRuntime");
assert!(is_runtime_initialized());
drop(runtime);
assert!(!is_runtime_initialized());
}
#[test]
fn test_runtime_state_persists_across_operations() {
while is_runtime_initialized() {
drop(DaosRuntime::new());
}
let runtime = DaosRuntime::new().expect("failed to create DaosRuntime");
assert!(require_runtime().is_ok());
assert!(runtime.is_initialized());
assert!(require_runtime().is_ok());
drop(runtime);
assert!(require_runtime().is_err());
}
}