Skip to content
Merged
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
72 changes: 72 additions & 0 deletions rust/pygpukit-python/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Unified error handling for PyGPUkit Python bindings
//!
//! This module provides helper functions for consistent error conversion
//! from Rust errors to Python exceptions.
//!
//! Error convention:
//! - MemoryError: Resource exhaustion (quota exceeded, allocation failures)
//! - ValueError: Invalid arguments (bad IDs, not found)
//! - RuntimeError: Operation failures (eviction, state errors)

use pyo3::exceptions::{PyRuntimeError, PyValueError, PyMemoryError};
use pyo3::PyErr;
use pygpukit_core::memory::MemoryError;
use pygpukit_core::scheduler::PartitionError;
use pygpukit_core::transfer::PinnedError;

/// Convert MemoryError to PyErr
pub fn memory_error_to_py(err: MemoryError) -> PyErr {
match err {
MemoryError::QuotaExceeded { requested, used, quota } => {
PyMemoryError::new_err(format!(
"Memory quota exceeded: requested {} bytes, {} used, {} quota",
requested, used, quota
))
}
MemoryError::InvalidBlock(id) => {
PyValueError::new_err(format!("Invalid memory block ID: {}", id))
}
MemoryError::BlockEvicted(id) => {
PyRuntimeError::new_err(format!("Memory block {} was evicted", id))
}
}
}

/// Convert PartitionError to PyErr
pub fn partition_error_to_py(err: PartitionError) -> PyErr {
match err {
PartitionError::NotFound { id } => {
PyValueError::new_err(format!("Partition not found: {}", id))
}
PartitionError::AlreadyExists { id } => {
PyValueError::new_err(format!("Partition already exists: {}", id))
}
PartitionError::InsufficientResources { resource, requested, available } => {
PyRuntimeError::new_err(format!(
"Insufficient {} for partition: requested {}, {} available",
resource, requested, available
))
}
PartitionError::NotAllowed { reason } => {
PyRuntimeError::new_err(format!("Operation not allowed: {}", reason))
}
}
}

/// Convert PinnedError to PyErr
pub fn pinned_error_to_py(err: PinnedError) -> PyErr {
match err {
PinnedError::QuotaExceeded { requested, available } => {
PyMemoryError::new_err(format!(
"Pinned memory quota exceeded: requested {} bytes, {} available",
requested, available
))
}
PinnedError::InvalidBlock { id } => {
PyValueError::new_err(format!("Pinned memory block not found: {}", id))
}
PinnedError::AllocationFailed { reason } => {
PyMemoryError::new_err(format!("Pinned memory allocation failed: {}", reason))
}
}
}
6 changes: 5 additions & 1 deletion rust/pygpukit-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use pyo3::prelude::*;

mod errors;
mod memory;
mod scheduler;
mod transfer;
Expand Down Expand Up @@ -52,9 +53,12 @@ fn _pygpukit_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<dispatch::PyPacingDecision>()?;
m.add_class::<dispatch::PyPacingStats>()?;
// Admission control
m.add_class::<scheduler::PyAdmissionConfig>()?;
m.add_class::<scheduler::PyAdmissionController>()?;
m.add_class::<scheduler::PyAdmissionDecision>()?;
m.add_class::<scheduler::PyAdmissionStats>()?;
m.add_class::<scheduler::PyRejectReason>()?;
m.add_class::<scheduler::PyRejectReasonEnum>()?;
m.add_class::<scheduler::PyRejectReasonDetails>()?;
// QoS policy
m.add_class::<scheduler::PyQosClass>()?;
m.add_class::<scheduler::PyQosPolicy>()?;
Expand Down
9 changes: 4 additions & 5 deletions rust/pygpukit-python/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Memory module Python bindings

use pyo3::prelude::*;
use pyo3::exceptions::PyRuntimeError;
use std::sync::Arc;
use pygpukit_core::memory::{MemoryPool, MemoryBlock, PoolStats};

use crate::errors::memory_error_to_py;

/// Python wrapper for MemoryBlock
#[pyclass(name = "MemoryBlock")]
#[derive(Clone)]
Expand Down Expand Up @@ -207,11 +208,9 @@ impl PyMemoryPool {
/// Block ID for the allocated block
///
/// Raises:
/// RuntimeError: If quota exceeded and cannot evict
/// MemoryError: If quota exceeded and cannot evict
fn allocate(&self, size: usize) -> PyResult<u64> {
self.inner.allocate(size).map_err(|e| {
PyRuntimeError::new_err(e.to_string())
})
self.inner.allocate(size).map_err(memory_error_to_py)
}

/// Free a memory block (return to free list).
Expand Down
Loading