Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C API 2.0 error handling #5797

Merged
merged 3 commits into from
Feb 19, 2025
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
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)
76 changes: 76 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,76 @@
// 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>
#include <system_error>
#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
198 changes: 198 additions & 0 deletions dali/c_api_2/error_handling.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// 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 <system_error>
#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;
};

namespace {
thread_local ErrorInfo g_daliLastError;

// A workaround for category comparison acros shared object boundary
inline bool CategoryEqual(const std::error_category &c1, const std::error_category &c2) {
if (c1 == c2) // we might get false negatives here...
return true;
// ...so we compare names - it's not foolproof, but should work for builtin categories,
// which is all we're intersted in.
const char *n1 = c1.name();
const char *n2 = c2.name();
return n1 == n2 || !strcmp(n1, n2);
}

} // namespace

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) {
// compare by name to work around DLL issues
if (CategoryEqual(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 (CategoryEqual(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
Loading