use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Duration;
use parking_lot::{Mutex, RwLock};
use ringkernel_cuda::persistent::{
H2KMessage, K2HMessage, PersistentSimulation, ResponseType, SimCommand,
};
use crate::error::{EcosystemError, Result as EcoResult};
use crate::persistent::{
CommandId, CommandTracker, PersistentCommand, PersistentConfig, PersistentHandle,
PersistentResponse, PersistentStats,
};
pub struct CudaPersistentHandle {
kernel_id: String,
simulation: Arc<Mutex<PersistentSimulation>>,
running: AtomicBool,
cmd_counter: AtomicU64,
tracker: CommandTracker,
config: RwLock<PersistentConfig>,
}
impl CudaPersistentHandle {
#[allow(clippy::arc_with_non_send_sync)] pub fn new(simulation: PersistentSimulation, kernel_id: impl Into<String>) -> Self {
let is_running = simulation.is_running();
Self {
kernel_id: kernel_id.into(),
simulation: Arc::new(Mutex::new(simulation)),
running: AtomicBool::new(is_running),
cmd_counter: AtomicU64::new(1),
tracker: CommandTracker::new(),
config: RwLock::new(PersistentConfig::default()),
}
}
pub fn with_config(
simulation: PersistentSimulation,
kernel_id: impl Into<String>,
config: PersistentConfig,
) -> Self {
let handle = Self::new(simulation, kernel_id);
*handle.config.write() = config;
handle
}
pub fn simulation(&self) -> parking_lot::MutexGuard<'_, PersistentSimulation> {
self.simulation.lock()
}
pub fn start(&self, ptx: &str, func_name: &str) -> ringkernel_core::error::Result<()> {
let mut sim = self.simulation.lock();
sim.start(ptx, func_name)?;
self.running.store(true, Ordering::Release);
Ok(())
}
#[allow(dead_code)] fn command_to_h2k(cmd: &PersistentCommand, cmd_id: u64) -> H2KMessage {
match cmd {
PersistentCommand::RunSteps { count } => H2KMessage::run_steps(cmd_id, *count),
PersistentCommand::Pause => H2KMessage {
cmd: SimCommand::Pause as u32,
cmd_id,
..Default::default()
},
PersistentCommand::Resume => H2KMessage {
cmd: SimCommand::Resume as u32,
cmd_id,
..Default::default()
},
PersistentCommand::Inject { position, value } => {
H2KMessage::inject_impulse(cmd_id, position.0, position.1, position.2, *value)
}
PersistentCommand::GetProgress => H2KMessage {
cmd: SimCommand::GetProgress as u32,
cmd_id,
..Default::default()
},
PersistentCommand::GetStats => H2KMessage {
cmd: SimCommand::GetProgress as u32, cmd_id,
flags: 1, ..Default::default()
},
PersistentCommand::Terminate => H2KMessage::terminate(cmd_id),
PersistentCommand::Custom { type_id, payload } => H2KMessage {
cmd: *type_id,
cmd_id,
param1: payload.len() as u64,
..Default::default()
},
}
}
fn k2h_to_response(msg: &K2HMessage, stats: &PersistentStats) -> PersistentResponse {
let cmd_id = CommandId::new(msg.cmd_id);
match msg.resp_type {
t if t == ResponseType::Ack as u32 => PersistentResponse::Ack { cmd_id },
t if t == ResponseType::Progress as u32 => PersistentResponse::Progress {
cmd_id,
current_step: msg.step,
remaining: msg.steps_remaining,
},
t if t == ResponseType::Error as u32 => PersistentResponse::Error {
cmd_id,
code: msg.error_code,
message: format!("GPU error code: {}", msg.error_code),
},
t if t == ResponseType::Terminated as u32 => PersistentResponse::Terminated {
final_step: msg.step,
},
t if t == ResponseType::Energy as u32 => PersistentResponse::Stats {
cmd_id,
stats: stats.clone(),
},
_ => PersistentResponse::Custom {
cmd_id,
type_id: msg.resp_type,
payload: vec![],
},
}
}
fn next_cmd_id(&self) -> CommandId {
CommandId::new(self.cmd_counter.fetch_add(1, Ordering::Relaxed))
}
}
impl Clone for CudaPersistentHandle {
fn clone(&self) -> Self {
Self {
kernel_id: self.kernel_id.clone(),
simulation: self.simulation.clone(),
running: AtomicBool::new(self.running.load(Ordering::Acquire)),
cmd_counter: AtomicU64::new(self.cmd_counter.load(Ordering::Relaxed)),
tracker: CommandTracker::new(),
config: RwLock::new(self.config.read().clone()),
}
}
}
unsafe impl Send for CudaPersistentHandle {}
unsafe impl Sync for CudaPersistentHandle {}
#[async_trait::async_trait]
impl PersistentHandle for CudaPersistentHandle {
fn kernel_id(&self) -> &str {
&self.kernel_id
}
fn is_running(&self) -> bool {
self.running.load(Ordering::Acquire)
}
fn send_command(&self, cmd: PersistentCommand) -> EcoResult<CommandId> {
if !self.is_running() {
return Err(EcosystemError::KernelNotRunning(self.kernel_id.clone()));
}
let cmd_id = self.next_cmd_id();
let sim = self.simulation.lock();
match &cmd {
PersistentCommand::RunSteps { count } => {
sim.run_steps(*count)
.map_err(|e| EcosystemError::Internal(format!("Failed to run steps: {}", e)))?;
}
PersistentCommand::Inject { position, value } => {
sim.inject_impulse(position.0, position.1, position.2, *value)
.map_err(|e| {
EcosystemError::Internal(format!("Failed to inject impulse: {}", e))
})?;
}
PersistentCommand::Terminate => {
}
_ => {
}
}
Ok(cmd_id)
}
fn poll_responses(&self) -> Vec<PersistentResponse> {
let sim = self.simulation.lock();
let k2h_messages = sim.poll_responses();
let stats = self.stats();
let mut responses = Vec::with_capacity(k2h_messages.len());
for msg in &k2h_messages {
let response = Self::k2h_to_response(msg, &stats);
self.tracker.notify(&response);
if matches!(response, PersistentResponse::Terminated { .. }) {
self.running.store(false, Ordering::Release);
}
responses.push(response);
}
responses
}
fn stats(&self) -> PersistentStats {
let sim = self.simulation.lock();
let cuda_stats = sim.stats();
PersistentStats {
current_step: cuda_stats.current_step,
steps_remaining: cuda_stats.steps_remaining,
messages_processed: cuda_stats.messages_processed,
total_energy: cuda_stats.total_energy,
k2k_sent: cuda_stats.k2k_sent,
k2k_received: cuda_stats.k2k_received,
is_running: sim.is_running(),
has_terminated: cuda_stats.has_terminated,
pending_commands: 0, pending_responses: 0,
}
}
async fn wait_for_command(&self, cmd_id: CommandId, timeout: Duration) -> EcoResult<()> {
let start = std::time::Instant::now();
let poll_interval = self.config.read().poll_interval;
let mut rx = self.tracker.register(cmd_id);
loop {
if start.elapsed() > timeout {
return Err(EcosystemError::Timeout(timeout));
}
tokio::select! {
biased;
result = &mut rx => {
match result {
Ok(response) => {
if response.is_error() {
if let PersistentResponse::Error { message, .. } = response {
return Err(EcosystemError::Internal(message));
}
}
return Ok(());
}
Err(_) => {
return Ok(());
}
}
}
_ = tokio::time::sleep(poll_interval) => {
let responses = self.poll_responses();
for response in responses {
if response.command_id() == Some(cmd_id) {
if response.is_error() {
if let PersistentResponse::Error { message, .. } = response {
return Err(EcosystemError::Internal(message));
}
}
return Ok(());
}
}
}
}
}
}
async fn shutdown(&self) -> EcoResult<()> {
if !self.is_running() {
return Ok(());
}
let mut sim = self.simulation.lock();
sim.shutdown()
.map_err(|e| EcosystemError::Internal(format!("Failed to shutdown: {}", e)))?;
self.running.store(false, Ordering::Release);
Ok(())
}
fn config(&self) -> &PersistentConfig {
Box::leak(Box::new(self.config.read().clone()))
}
}
pub struct CudaPersistentHandleBuilder {
kernel_id: String,
config: PersistentConfig,
}
impl CudaPersistentHandleBuilder {
pub fn new(kernel_id: impl Into<String>) -> Self {
Self {
kernel_id: kernel_id.into(),
config: PersistentConfig::default(),
}
}
pub fn h2k_capacity(mut self, capacity: usize) -> Self {
self.config = self.config.with_h2k_capacity(capacity);
self
}
pub fn k2h_capacity(mut self, capacity: usize) -> Self {
self.config = self.config.with_k2h_capacity(capacity);
self
}
pub fn progress_interval(mut self, interval: u64) -> Self {
self.config = self.config.with_progress_interval(interval);
self
}
pub fn poll_interval(mut self, interval: Duration) -> Self {
self.config = self.config.with_poll_interval(interval);
self
}
pub fn command_timeout(mut self, timeout: Duration) -> Self {
self.config = self.config.with_command_timeout(timeout);
self
}
pub fn build(self, simulation: PersistentSimulation) -> CudaPersistentHandle {
CudaPersistentHandle::with_config(simulation, self.kernel_id, self.config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_command_to_h2k_run_steps() {
let h2k =
CudaPersistentHandle::command_to_h2k(&PersistentCommand::RunSteps { count: 1000 }, 42);
assert_eq!(h2k.cmd, SimCommand::RunSteps as u32);
assert_eq!(h2k.cmd_id, 42);
assert_eq!(h2k.param1, 1000);
}
#[test]
fn test_command_to_h2k_inject() {
let h2k = CudaPersistentHandle::command_to_h2k(
&PersistentCommand::Inject {
position: (10, 20, 30),
value: 1.5,
},
99,
);
assert_eq!(h2k.cmd, SimCommand::InjectImpulse as u32);
assert_eq!(h2k.cmd_id, 99);
assert_eq!(h2k.param2, 30); assert_eq!(h2k.param4, 1.5); }
#[test]
fn test_command_to_h2k_terminate() {
let h2k = CudaPersistentHandle::command_to_h2k(&PersistentCommand::Terminate, 123);
assert_eq!(h2k.cmd, SimCommand::Terminate as u32);
assert_eq!(h2k.cmd_id, 123);
}
#[test]
fn test_k2h_to_response_ack() {
let k2h = K2HMessage {
resp_type: ResponseType::Ack as u32,
cmd_id: 42,
..Default::default()
};
let stats = PersistentStats::default();
let response = CudaPersistentHandle::k2h_to_response(&k2h, &stats);
assert!(matches!(
response,
PersistentResponse::Ack { cmd_id } if cmd_id.as_u64() == 42
));
}
#[test]
fn test_k2h_to_response_progress() {
let k2h = K2HMessage {
resp_type: ResponseType::Progress as u32,
cmd_id: 42,
step: 500,
steps_remaining: 500,
..Default::default()
};
let stats = PersistentStats::default();
let response = CudaPersistentHandle::k2h_to_response(&k2h, &stats);
assert!(matches!(
response,
PersistentResponse::Progress {
cmd_id,
current_step: 500,
remaining: 500,
} if cmd_id.as_u64() == 42
));
}
#[test]
fn test_k2h_to_response_terminated() {
let k2h = K2HMessage {
resp_type: ResponseType::Terminated as u32,
step: 1000,
..Default::default()
};
let stats = PersistentStats::default();
let response = CudaPersistentHandle::k2h_to_response(&k2h, &stats);
assert!(matches!(
response,
PersistentResponse::Terminated { final_step: 1000 }
));
}
#[test]
fn test_k2h_to_response_error() {
let k2h = K2HMessage {
resp_type: ResponseType::Error as u32,
cmd_id: 42,
error_code: 101,
..Default::default()
};
let stats = PersistentStats::default();
let response = CudaPersistentHandle::k2h_to_response(&k2h, &stats);
assert!(matches!(
response,
PersistentResponse::Error {
cmd_id,
code: 101,
..
} if cmd_id.as_u64() == 42
));
}
#[test]
fn test_builder() {
let builder = CudaPersistentHandleBuilder::new("test")
.h2k_capacity(512)
.k2h_capacity(512)
.progress_interval(50)
.poll_interval(Duration::from_micros(50))
.command_timeout(Duration::from_secs(10));
assert_eq!(builder.kernel_id, "test");
assert_eq!(builder.config.h2k_capacity, 512);
assert_eq!(builder.config.k2h_capacity, 512);
assert_eq!(builder.config.progress_interval, 50);
}
}