mod config;
pub use config::NixlBackendConfig;
use anyhow::Result;
use nixl_sys::Agent as RawNixlAgent;
use std::collections::HashSet;
#[derive(Clone, Debug)]
pub struct NixlAgent {
agent: RawNixlAgent,
available_backends: HashSet<String>,
}
impl NixlAgent {
pub fn new_with_backends(name: &str, backends: &[&str]) -> Result<Self> {
let agent = RawNixlAgent::new(name)?;
let mut available_backends = HashSet::new();
for backend in backends {
let backend_upper = backend.to_uppercase();
match agent.get_plugin_params(&backend_upper) {
Ok((_, params)) => match agent.create_backend(&backend_upper, ¶ms) {
Ok(_) => {
available_backends.insert(backend_upper);
}
Err(e) => {
eprintln!(
"✗ Failed to create {} backend: {}. Operations requiring this backend will fail.",
backend_upper, e
);
}
},
Err(_) => {
eprintln!(
"✗ No {} plugin found. Operations requiring this backend will fail.",
backend_upper
);
}
}
}
if available_backends.is_empty() {
anyhow::bail!("Failed to initialize any NIXL backends from {:?}", backends);
}
Ok(Self {
agent,
available_backends,
})
}
pub fn require_backends(name: &str, backends: &[&str]) -> Result<Self> {
let agent = RawNixlAgent::new(name)?;
let mut available_backends = HashSet::new();
let mut failed_backends = Vec::new();
for backend in backends {
let backend_upper = backend.to_uppercase();
match agent.get_plugin_params(&backend_upper) {
Ok((_, params)) => match agent.create_backend(&backend_upper, ¶ms) {
Ok(_) => {
available_backends.insert(backend_upper);
}
Err(e) => {
eprintln!("✗ Failed to create {} backend: {}", backend_upper, e);
failed_backends
.push((backend_upper.clone(), format!("create failed: {}", e)));
}
},
Err(e) => {
eprintln!("✗ No {} plugin found", backend_upper);
failed_backends
.push((backend_upper.clone(), format!("plugin not found: {}", e)));
}
}
}
if !failed_backends.is_empty() {
let error_details: Vec<String> = failed_backends
.iter()
.map(|(name, reason)| format!("{}: {}", name, reason))
.collect();
anyhow::bail!(
"Failed to initialize required backends: [{}]",
error_details.join(", ")
);
}
Ok(Self {
agent,
available_backends,
})
}
pub fn new_default(name: &str) -> Result<Self> {
Self::new_with_backends(name, &["UCX", "GDS_MT", "POSIX"])
}
pub fn raw_agent(&self) -> &RawNixlAgent {
&self.agent
}
pub fn into_raw_agent(self) -> RawNixlAgent {
self.agent
}
pub fn has_backend(&self, backend: &str) -> bool {
self.available_backends.contains(&backend.to_uppercase())
}
pub fn backends(&self) -> &HashSet<String> {
&self.available_backends
}
pub fn require_backend(&self, backend: &str) -> Result<()> {
let backend_upper = backend.to_uppercase();
if self.has_backend(&backend_upper) {
Ok(())
} else {
anyhow::bail!(
"Operation requires {} backend, but it was not initialized. Available backends: {:?}",
backend_upper,
self.available_backends
)
}
}
}
impl std::ops::Deref for NixlAgent {
type Target = RawNixlAgent;
fn deref(&self) -> &Self::Target {
&self.agent
}
}
#[cfg(all(test, feature = "testing-nixl"))]
mod tests {
use super::*;
#[test]
fn test_agent_backend_tracking() {
let agent = NixlAgent::new_with_backends("test", &["UCX"]);
if let Ok(agent) = agent {
assert!(agent.has_backend("UCX"));
assert!(agent.has_backend("ucx")); }
}
#[test]
fn test_require_backend() {
let agent = match NixlAgent::new_with_backends("test", &["UCX"]) {
Ok(agent) => agent,
Err(_) => {
eprintln!("Skipping test_require_backend: UCX not available");
return;
}
};
assert!(agent.require_backend("UCX").is_ok());
assert!(agent.require_backend("GDS_MT").is_err());
}
#[test]
fn test_require_backends_strict() {
let agent = match NixlAgent::require_backends("test_strict", &["UCX"]) {
Ok(agent) => agent,
Err(_) => {
eprintln!("Skipping test_require_backends_strict: UCX not available");
return;
}
};
assert!(agent.has_backend("UCX"));
let result = NixlAgent::require_backends("test_strict_fail", &["UCX", "DUDE"]);
assert!(result.is_err());
}
}