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
18 changes: 8 additions & 10 deletions include/radex/client_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <utility>
#include <vector>

#include "radex/exceptions.hpp"
#include "radex/handles.hpp"

namespace radex {
Expand Down Expand Up @@ -302,13 +303,11 @@ class IClient {
typename std::enable_if<data::is_supported_type<T>::value, T>::type
assemble_scalar(const detail::ItemInfo &info) {
if (info.metadata().n_dims() != 0) {
// TODO: Better error type/message here
throw std::runtime_error(
throw RankMismatchError(
"Attempted to retrieve scalar at a key with a vector");
}
if (info.metadata().type() != data::encode_type<T>()) {
// TODO: Better error type/message here
throw std::runtime_error(
throw DTypeMismatchError(
"Attempted to retrieve scalar of mismatched type");
}
T converted;
Expand All @@ -321,13 +320,11 @@ class IClient {
TensorInfo<T>>::type
assemble_tensor(const detail::ItemInfo &info) {
if (info.metadata().n_dims() == 0) {
// TODO: Better error type/message here
throw std::runtime_error(
throw RankMismatchError(
"Attempted to retrieve vector at a key with a scalar");
}
if (info.metadata().type() != data::encode_type<T>()) {
// TODO: Better error type/message here
throw std::runtime_error(
throw DTypeMismatchError(
"Attempted to retrieve vector of mismatched type");
}

Expand All @@ -340,15 +337,16 @@ class IClient {
namespace unsupported_backend {

/// Placeholder `IClient` used when a backend was disabled at build time; every
/// method throws `std::runtime_error` explaining how to rebuild with it enabled.
/// method throws `radex::BackendUnavailableError` explaining how to rebuild
/// with it enabled.
class Client : public IClient {
private:
std::string backend_name;
std::string enable_option;

protected:
[[noreturn]] void throw_backend_unavailable() const {
throw std::runtime_error(
throw BackendUnavailableError(
"RaDex was built without " + backend_name + " backend support. "
"Rebuild with -D" + enable_option + "=ON to enable this client."
);
Expand Down
60 changes: 60 additions & 0 deletions include/radex/exceptions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef __RADEX_EXCEPTIONS_HPP__
#define __RADEX_EXCEPTIONS_HPP__

#include <stdexcept>

namespace radex {

/// Base class for every error raised by radex.
class Error : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};

/// A key was requested that is not present in the store.
class KeyNotFoundError : public Error {
public:
using Error::Error;
};

/// A key did not appear in the store before timing out.
class TimeoutError : public Error {
public:
using Error::Error;
};

/// The stored item does not match the item type requested.
class TypeMismatchError : public Error {
public:
using Error::Error;
};

/// A scalar was requested at a tensor key, or a tensor at a scalar key.
class RankMismatchError : public TypeMismatchError {
public:
using TypeMismatchError::TypeMismatchError;
};

/// The stored element type differs from the requested one. Distinct from
/// `RankMismatchError` so that callers can retry with another element type
/// without also retrying a request that asked for the wrong shape entirely.
class DTypeMismatchError : public TypeMismatchError {
public:
using TypeMismatchError::TypeMismatchError;
};

/// An item's metadata record could not be decoded.
class MetadataError : public Error {
public:
using Error::Error;
};

/// A client was requested for a backend that was disabled at build time.
class BackendUnavailableError : public Error {
public:
using Error::Error;
};

} // namespace radex

#endif // __RADEX_EXCEPTIONS_HPP__
16 changes: 14 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,21 @@ if(ENABLE_CXX)
endif()

set(RADEX_GENERATED_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated/include)
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../include/
DESTINATION ${RADEX_GENERATED_INCLUDE_DIR}
set(RADEX_PUBLIC_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include)

file(GLOB_RECURSE RADEX_PUBLIC_HEADERS
RELATIVE ${RADEX_PUBLIC_INCLUDE_DIR}
${RADEX_PUBLIC_INCLUDE_DIR}/*.hpp
)
foreach(header IN LISTS RADEX_PUBLIC_HEADERS)
configure_file(
${RADEX_PUBLIC_INCLUDE_DIR}/${header}
${RADEX_GENERATED_INCLUDE_DIR}/${header}
COPYONLY
)
endforeach()

# Overwrite the build_config.hpp
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/radex-build-config.hpp.in
${RADEX_GENERATED_INCLUDE_DIR}/radex/build_config.hpp
Expand Down
5 changes: 2 additions & 3 deletions src/cpp/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ MetaData MetaData::from_buffer(BytesBuffer buffer) {
MetaInt expected_size =
(meta.n_dims() + Index::END_OF_HEADER) * sizeof(MetaInt);
if (meta.size() != expected_size) {
throw std::runtime_error("Malformed item metadata buffer received");
throw radex::MetadataError("Malformed item metadata buffer received");
}
return meta;
}
Expand Down Expand Up @@ -74,8 +74,7 @@ detail::BytesBuffer IClient::wait_for_bytes(std::string_view key,
std::ostringstream msg;
msg << "Failed to find key `" << key << "` before timeout";

// TODO: Better error type here
throw std::runtime_error(msg.str());
throw radex::TimeoutError(msg.str());
}
std::this_thread::sleep_for(poll_interval);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cpp/dragon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ radex::detail::BytesBuffer Client::get_bytes(std::string_view key) {
const std::chrono::milliseconds fast_timeout{1};
// TODO: Check if/when Dragon can support bypassing wait for keys
if (!contains(key)) {
std::runtime_error("Key does not exist in the DDict: " + std::string(key));
throw radex::KeyNotFoundError("Key does not exist in the DDict: " +
std::string(key));
}
return wait_for_bytes(key, fast_timeout);
}
Expand Down
6 changes: 5 additions & 1 deletion src/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ def _required_env(name, backend):


def make_extensions():
include_dirs = [os.fspath(RADEX_INCLUDE_DIR), numpy.get_include()]
include_dirs = [
os.fspath(RADEX_INCLUDE_DIR),
os.fspath(PY_SRC),
numpy.get_include(),
]
library_dirs = [os.fspath(RADEX_LIB_DIR)]
libraries = ["radex"]
runtime_library_dirs = [os.fspath(RADEX_LIB_DIR)]
Expand Down
17 changes: 9 additions & 8 deletions src/python/src/radex/clients/core.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,25 @@ from radex.handles.handles cimport (
CXXIncomingHandle as IncomingHandle,
CXXOutgoingHandle as OutgoingHandle,
)
from radex.utils.exceptions cimport raise_py_error

cimport numpy as np
np.import_array()

cdef extern from "radex/client.hpp" namespace "radex":
cdef cppclass IClient:
# >>> Start Virtual Methods >>>
bint contains(string_view) except +
void put_bytes(string_view, const void*, size_t) except +
BytesBuffer get_bytes(string_view) except +
BytesBuffer wait_for_bytes(string_view, milliseconds) except +
bint contains(string_view) except +raise_py_error
void put_bytes(string_view, const void*, size_t) except +raise_py_error
BytesBuffer get_bytes(string_view) except +raise_py_error
BytesBuffer wait_for_bytes(string_view, milliseconds) except +raise_py_error
# <<< End Virtual Methods <<<

void put_scalar[T](const OutgoingHandle&, T) except +
void put_scalar[T](const OutgoingHandle&, T) except +raise_py_error
void put_tensor[T](const OutgoingHandle&,
const size_t*, size_t,
const T*, size_t) except +
const T*, size_t) except +raise_py_error

unique_ptr[ItemInfo] get_item_info_ptr(const IncomingHandle&) except +
unique_ptr[ItemInfo] get_item_info_ptr(const IncomingHandle&) except +raise_py_error
unique_ptr[ItemInfo] wait_for_item_info_ptr(
const IncomingHandle&, milliseconds) except +
const IncomingHandle&, milliseconds) except +raise_py_error
5 changes: 3 additions & 2 deletions src/python/src/radex/clients/dragon.pxd
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from libc.time cimport timespec

from radex.clients.core cimport IClient
from radex.utils.exceptions cimport raise_py_error


cdef extern from "radex/dragon.hpp" namespace "radex::drg::ddict":
cdef cppclass Client(IClient):
Client() except +
Client(const char*, const timespec*) except +
Client() except +raise_py_error
Client(const char*, const timespec*) except +raise_py_error
5 changes: 3 additions & 2 deletions src/python/src/radex/clients/redis.pxd
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from libcpp.string_view cimport string_view

from radex.clients.core cimport IClient
from radex.utils.exceptions cimport raise_py_error


cdef extern from "radex/smartredis.hpp" namespace "radex::redis::smartredis":
cdef cppclass Client(IClient):
Client() except +
Client(string_view) except +
Client() except +raise_py_error
Client(string_view) except +raise_py_error
45 changes: 45 additions & 0 deletions src/python/src/radex/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Python mirror of the C++ exception hierarchy in ``radex/exceptions.hpp``.
"""

__all__ = [
"RadexError",
"KeyNotFoundError",
"TimeoutError",
"TypeMismatchError",
"RankMismatchError",
"DTypeMismatchError",
"MetadataError",
"BackendUnavailableError",
]


class RadexError(RuntimeError):
"""Base class for every error raised by radex."""


class KeyNotFoundError(RadexError):
"""A key was requested that is not present in the store."""


class TimeoutError(RadexError):
"""A key did not appear in the store before the timeout elapsed."""


class TypeMismatchError(RadexError):
"""The stored item does not match the description it was requested with."""


class RankMismatchError(TypeMismatchError):
"""A scalar was requested at a tensor key, or a tensor at a scalar key."""


class DTypeMismatchError(TypeMismatchError):
"""The stored element type differs from the requested one."""


class MetadataError(RadexError):
"""An item's metadata record could not be decoded."""


class BackendUnavailableError(RadexError):
"""A client was requested for a backend that was disabled at build time."""
10 changes: 6 additions & 4 deletions src/python/src/radex/utils/data.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ cdef inline np.number coerce_py_objects_to_np_numbers(object value):

cdef inline construct_scalar(const ItemInfo &info):
if info.metadata().n_dims() != 0:
# TODO: Better error type/msg here
raise ValueError("Attempted to retrieve scalar at a key with a vector")
from radex.exceptions import RankMismatchError
raise RankMismatchError(
"Attempted to retrieve scalar at a key with a vector")

cdef DType type_ = info.metadata().type()
return make_ndarray(type_, info.data(), 1)[0]
Expand All @@ -80,8 +81,9 @@ cdef inline construct_scalar(const ItemInfo &info):
cdef inline construct_tensor(const ItemInfo &info):
cdef MetaInt n_dims = info.metadata().n_dims()
if n_dims == 0:
# TODO: Better error type/msg here
raise ValueError("Attempted to retrieve vector at a key with a scalar")
from radex.exceptions import RankMismatchError

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might be able to move this and the import at line 73 into a top level

from radex.exceptions import RankMismatchError

as I don't think that radex.exceptions uses anything from this module and therefor we do not need to guard against the circular import.

That said, I'm not entirely sure how will this will play nice with the Cython compile step. It might be worth a shot, but if it doesn't work immediately feel free to ignore this comment.

raise RankMismatchError(
"Attempted to retrieve vector at a key with a scalar")

cdef const MetaInt[:] dims = <const MetaInt[:n_dims]> info.metadata().dims_ptr()
cdef n_elements = info.metadata().n_elements()
Expand Down
71 changes: 71 additions & 0 deletions src/python/src/radex/utils/exception_translation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#ifndef __RADEX_PY_EXCEPTION_TRANSLATION_HPP__
#define __RADEX_PY_EXCEPTION_TRANSLATION_HPP__

#include <Python.h>

#include <new>
#include <stdexcept>

#include "radex/exceptions.hpp"

namespace radex_py {

namespace detail {

inline void set_error(const char *name, const char *what) {
PyObject *module = PyImport_ImportModule("radex.exceptions");
if (module == nullptr) {
return; // ImportError is already set
}

PyObject *exc_type = PyObject_GetAttrString(module, name);
if (exc_type != nullptr) {
PyErr_SetString(exc_type, what);
Py_DECREF(exc_type);
}

Py_DECREF(module);
}

} // namespace detail

/// Translate the in-flight C++ exception into the matching Python one.
///
/// Only valid inside a catch block, which is where Cython's
/// `except +raise_py_error` invokes it. Derived types must be caught before
/// their bases or the subclass information is lost.
inline void raise_py_error() {
try {
throw;
} catch (const radex::KeyNotFoundError &e) {
detail::set_error("KeyNotFoundError", e.what());
} catch (const radex::TimeoutError &e) {
detail::set_error("TimeoutError", e.what());
} catch (const radex::RankMismatchError &e) {
detail::set_error("RankMismatchError", e.what());
} catch (const radex::DTypeMismatchError &e) {
detail::set_error("DTypeMismatchError", e.what());
} catch (const radex::TypeMismatchError &e) {
detail::set_error("TypeMismatchError", e.what());
} catch (const radex::MetadataError &e) {
detail::set_error("MetadataError", e.what());
} catch (const radex::BackendUnavailableError &e) {
detail::set_error("BackendUnavailableError", e.what());
} catch (const radex::Error &e) {
detail::set_error("RadexError", e.what());
} catch (const std::invalid_argument &e) {
PyErr_SetString(PyExc_ValueError, e.what());
} catch (const std::out_of_range &e) {
PyErr_SetString(PyExc_IndexError, e.what());
} catch (const std::bad_alloc &e) {
PyErr_SetString(PyExc_MemoryError, e.what());
} catch (const std::exception &e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
} catch (...) {
PyErr_SetString(PyExc_RuntimeError, "Unknown C++ exception");
}
}

} // namespace radex_py

#endif // __RADEX_PY_EXCEPTION_TRANSLATION_HPP__
2 changes: 2 additions & 0 deletions src/python/src/radex/utils/exceptions.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cdef extern from "radex/utils/exception_translation.hpp" namespace "radex_py":
void raise_py_error()
Loading
Loading