Skip to content

Commit

Permalink
C API error handling.
Browse files Browse the repository at this point in the history
Signed-off-by: Michał Zientkiewicz <[email protected]>
  • Loading branch information
mzient committed Feb 13, 2025
1 parent d36e0ba commit 14aebea
Show file tree
Hide file tree
Showing 6 changed files with 417 additions and 1 deletion.
3 changes: 2 additions & 1 deletion dali/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2017-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# Copyright (c) 2017-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -46,6 +46,7 @@ if (BUILD_DALI_PIPELINE)
add_subdirectory(util)
add_subdirectory(plugin)
add_subdirectory(c_api)
add_subdirectory(c_api_2)
endif()

if(BUILD_DALI_OPERATORS)
Expand Down
21 changes: 21 additions & 0 deletions dali/c_api_2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Get all the source files

# The headers here are private and should not be installed.
# collect_headers(DALI_INST_HDRS PARENT_SCOPE)

collect_sources(DALI_SRCS PARENT_SCOPE)
collect_test_sources(DALI_TEST_SRCS PARENT_SCOPE)
75 changes: 75 additions & 0 deletions dali/c_api_2/c_api_internal_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <gtest/gtest.h>
#include <stdexcept>
#define DALI_ALLOW_NEW_C_API
#include "dali/dali.h"
#include "dali/c_api_2/error_handling.h"
#include "dali/core/cuda_error.h"

namespace dali {

template <typename ExceptionType>
daliResult_t ThrowAndTranslate(ExceptionType &&ex) {
DALI_PROLOG();
throw std::forward<ExceptionType>(ex);
DALI_EPILOG();
}

template <typename ExceptionType>
void CheckException(ExceptionType &&ex, daliResult_t expected_result) {
std::string message(ex.what());
daliResult_t ret = ThrowAndTranslate(std::forward<ExceptionType>(ex));
EXPECT_EQ(ret, expected_result);
EXPECT_EQ(daliGetLastError(), expected_result);
EXPECT_EQ(daliGetLastErrorMessage(), message);
std::cout << daliGetErrorName(ret) << " "
<< daliGetLastErrorMessage() << std::endl;
daliClearLastError();
EXPECT_EQ(daliGetLastError(), DALI_SUCCESS);
EXPECT_STREQ(daliGetLastErrorMessage(), "");
}

TEST(CAPI2InternalTest, ErrorTranslation) {
CheckException(std::runtime_error("Runtime Error"), DALI_ERROR_INVALID_OPERATION);
CheckException(std::bad_alloc(), DALI_ERROR_OUT_OF_MEMORY);
CheckException(CUDABadAlloc(), DALI_ERROR_OUT_OF_MEMORY);
CheckException(std::logic_error("Logic dictates that it's an error"), DALI_ERROR_INTERNAL);
CheckException(std::out_of_range("Bullet left the shooting range"), DALI_ERROR_OUT_OF_RANGE);
CheckException(invalid_key("This key doesn't fit into the keyhole."), DALI_ERROR_INVALID_KEY);

CheckException(std::system_error(std::make_error_code(std::errc::no_such_file_or_directory)),
DALI_ERROR_PATH_NOT_FOUND);
CheckException(std::system_error(std::make_error_code(std::errc::no_such_device_or_address)),
DALI_ERROR_PATH_NOT_FOUND);

CheckException(std::system_error(std::make_error_code(std::errc::no_space_on_device)),
DALI_ERROR_IO_ERROR);
CheckException(std::system_error(
std::make_error_code(std::errc::inappropriate_io_control_operation)),
DALI_ERROR_IO_ERROR);
CheckException(std::system_error(std::make_error_code(std::io_errc::stream)),
DALI_ERROR_IO_ERROR);

CheckException(std::system_error(std::make_error_code(std::errc::not_enough_memory)),
DALI_ERROR_OUT_OF_MEMORY);

CheckException(std::system_error(std::make_error_code(std::errc::bad_file_descriptor)),
DALI_ERROR_SYSTEM);
CheckException(std::system_error(std::make_error_code(std::errc::too_many_files_open)),
DALI_ERROR_SYSTEM);
}

} // namespace dali
182 changes: 182 additions & 0 deletions dali/c_api_2/error_handling.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdexcept>
#include <string>
#include <utility>
#include "dali/c_api_2/error_handling.h"
#include "dali/core/error_handling.h"
#include "dali/core/cuda_error.h"

namespace dali::c_api {

struct ErrorInfo {
inline ErrorInfo() {
message.reserve(1024); // should help with properly reporting OOMs
}
daliResult_t result = DALI_SUCCESS;
std::string message;
};

thread_local ErrorInfo g_daliLastError;

struct ErrorDesc {
const char *name, *description;
};

ErrorDesc GetErrorDesc(daliResult_t result) {
#define RESULT_DESC(name, desc) case DALI_##name: return { "DALI_" #name, desc }
#define ERROR_DESC(name, desc) RESULT_DESC(ERROR_##name, desc)

switch (result) {
RESULT_DESC(SUCCESS, "The operation was successful.");
RESULT_DESC(NO_DATA, "The operation was successful, but didn't return any data.");
RESULT_DESC(NOT_READY, "The query succeeded, but the operation queried is still pending.");
ERROR_DESC(INVALID_HANDLE, "The operation received an invalid DALI handle.");
ERROR_DESC(INVALID_ARGUMENT, "An invalid argument was specified.");
ERROR_DESC(INVALID_TYPE, "An argument of invalid type encountered.");
ERROR_DESC(INVALID_OPERATION, "An invalid operation was requested.");
ERROR_DESC(OUT_OF_RANGE, "An argument is out of valid range.");
ERROR_DESC(INVALID_KEY, "The operation received an invalid dictionary key.");

ERROR_DESC(SYSTEM, "An operating system routine failed.");
ERROR_DESC(PATH_NOT_FOUND, "A non-existent or non-accessible file path was encountered.");
ERROR_DESC(IO_ERROR, "An I/O operation failed");
ERROR_DESC(OUT_OF_MEMORY, "Cannot allocate memory");
ERROR_DESC(INTERNAL, "An internal error occurred");
ERROR_DESC(UNLOADING, "DALI is unloading - either daliShutdown was called or "
"the process is shutting down.");
ERROR_DESC(CUDA_ERROR, "A CUDA call has failed.");
default:
return { "<invalid>", "<invalid>" };
}
}

daliResult_t SetLastError(daliResult_t result, const char *message) {
g_daliLastError.result = result;
g_daliLastError.message = message;
return result;
}

daliResult_t HandleError(std::exception_ptr ex) {
try {
std::rethrow_exception(std::move(ex));
} catch (dali::c_api::InvalidHandle &e) {
return SetLastError(DALI_ERROR_INVALID_HANDLE, e.what());
} catch (std::invalid_argument &e) {
return SetLastError(DALI_ERROR_INVALID_ARGUMENT, e.what());
} catch (dali::CUDAError &e) {
if (e.is_rt_api()) {
if (e.rt_error() == cudaErrorNotReady)
return SetLastError(DALI_NOT_READY, e.what());
} else if (e.is_drv_api()) {
if (e.drv_error() == CUDA_ERROR_NOT_READY)
return SetLastError(DALI_NOT_READY, e.what());
}
return SetLastError(DALI_ERROR_CUDA_ERROR, e.what());
} catch (dali::CUDABadAlloc &e) {
return SetLastError(DALI_ERROR_OUT_OF_MEMORY, e.what());
} catch (std::bad_alloc &e) {
return SetLastError(DALI_ERROR_OUT_OF_MEMORY, e.what());
} catch (dali::invalid_key &e) {
return SetLastError(DALI_ERROR_INVALID_KEY, e.what());
} catch (std::out_of_range &e) {
return SetLastError(DALI_ERROR_OUT_OF_RANGE, e.what());
} catch (std::system_error &e) {
if (e.code().category() == std::generic_category()) {
daliResult_t result = [&]() {
switch (static_cast<std::errc>(e.code().value())) {
case std::errc::no_such_file_or_directory:
case std::errc::no_such_device:
case std::errc::no_such_device_or_address:
return DALI_ERROR_PATH_NOT_FOUND;
case std::errc::not_enough_memory:
return DALI_ERROR_OUT_OF_MEMORY;
case std::errc::timed_out:
return DALI_ERROR_TIMEOUT;
case std::errc::address_family_not_supported:
case std::errc::address_in_use:
case std::errc::address_not_available:
case std::errc::already_connected:
case std::errc::broken_pipe:
case std::errc::connection_aborted:
case std::errc::connection_already_in_progress:
case std::errc::connection_refused:
case std::errc::connection_reset:
case std::errc::device_or_resource_busy:
case std::errc::directory_not_empty:
case std::errc::file_exists:
case std::errc::file_too_large:
case std::errc::filename_too_long:
case std::errc::host_unreachable:
case std::errc::inappropriate_io_control_operation:
case std::errc::io_error:
case std::errc::is_a_directory:
case std::errc::message_size:
case std::errc::network_down:
case std::errc::network_reset:
case std::errc::network_unreachable:
case std::errc::no_buffer_space:
case std::errc::no_message:
case std::errc::no_space_on_device:
case std::errc::not_a_directory:
case std::errc::not_a_socket:
case std::errc::read_only_file_system:
return DALI_ERROR_IO_ERROR;
default:
return DALI_ERROR_SYSTEM;
}
}();
return SetLastError(result, e.what());
} else if (e.code().category() == std::iostream_category()) {
return SetLastError(DALI_ERROR_IO_ERROR, e.what());
} else {
return SetLastError(DALI_ERROR_SYSTEM, e.what());
}
} catch (std::runtime_error &e) {
return SetLastError(DALI_ERROR_INVALID_OPERATION, e.what());
} catch (std::exception &e) {
return SetLastError(DALI_ERROR_INTERNAL, e.what());
} catch (const char *e) { // handle strings thrown as exceptions
return SetLastError(DALI_ERROR_INTERNAL, e);
} catch (const std::string &e) { // handle strings thrown as exceptions
return SetLastError(DALI_ERROR_INTERNAL, e.c_str());
} catch (...) {
return SetLastError(DALI_ERROR_INTERNAL, "<unknown error>");
}
}

} // namespace dali::c_api

using namespace dali; // NOLINT

daliResult_t daliGetLastError() {
return c_api::g_daliLastError.result;
}

const char *daliGetLastErrorMessage() {
return c_api::g_daliLastError.message.c_str();
}

void daliClearLastError() {
c_api::g_daliLastError = {};
}

const char *daliGetErrorName(daliResult_t result) {
return c_api::GetErrorDesc(result).name;
}

const char *daliGetErrorDescription(daliResult_t result) {
return c_api::GetErrorDesc(result).description;
}
68 changes: 68 additions & 0 deletions dali/c_api_2/error_handling.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef DALI_C_API_2_ERROR_HANDLING_H_
#define DALI_C_API_2_ERROR_HANDLING_H_

#include <stdexcept>
#include <iostream>
#include <string>
#include <sstream>
#define DALI_ALLOW_NEW_C_API
#include "dali/dali.h"
#include "dali/core/error_handling.h"

inline std::ostream &operator<<(std::ostream &os, daliResult_t result) {
const char *e = daliGetErrorName(result);
if (e[0] == '<')
os << "<unknown: " << static_cast<int>(result) << ">";
else
os << e;
return os;
}

inline std::string to_string(daliResult_t result) {
std::stringstream ss;
ss << result;
return ss.str();
}

namespace dali {
namespace c_api {

DLL_PUBLIC daliResult_t HandleError(std::exception_ptr ex);
DLL_PUBLIC daliResult_t CheckInit();

class InvalidHandle : public std::invalid_argument {
public:
InvalidHandle() : std::invalid_argument("The handle is invalid") {}
explicit InvalidHandle(const std::string &what) : std::invalid_argument(what) {}
explicit InvalidHandle(const char *what) : std::invalid_argument(what) {}
};

inline InvalidHandle NullHandle() { return InvalidHandle("The handle must not be NULL."); }

inline InvalidHandle NullHandle(const char *what_handle) {
return InvalidHandle(make_string("The ", what_handle, " handle must not be NULL."));
}

} // namespace c_api
} // namespace dali

#define DALI_PROLOG() try { if (auto err = dali::c_api::CheckInit()) return err; else;
#define DALI_EPILOG() return DALI_SUCCESS; } catch (...) { \
return ::dali::c_api::HandleError(std::current_exception()); \
}

#endif // DALI_C_API_2_ERROR_HANDLING_H_
Loading

0 comments on commit 14aebea

Please sign in to comment.