From 45b43d2052e534411a898d799aeb7efdd028ef08 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 4 Feb 2025 10:29:09 -0500 Subject: [PATCH 1/5] Add raii file wrappers --- cpp/CMakeLists.txt | 1 + cpp/include/kvikio/file_handle.hpp | 20 ++-- cpp/include/kvikio/file_utils.hpp | 165 ++++++++++++++++++++++++++ cpp/src/file_handle.cpp | 180 ++++++++--------------------- cpp/src/file_utils.cpp | 163 ++++++++++++++++++++++++++ 5 files changed, 391 insertions(+), 138 deletions(-) create mode 100644 cpp/include/kvikio/file_utils.hpp create mode 100644 cpp/src/file_utils.cpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 1a5890071d..f17db71439 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -140,6 +140,7 @@ set(SOURCES "src/defaults.cpp" "src/error.cpp" "src/file_handle.cpp" + "src/file_utils.cpp" "src/posix_io.cpp" "src/shim/cuda.cpp" "src/shim/cufile.cpp" diff --git a/cpp/include/kvikio/file_handle.hpp b/cpp/include/kvikio/file_handle.hpp index e32136e468..371034e027 100644 --- a/cpp/include/kvikio/file_handle.hpp +++ b/cpp/include/kvikio/file_handle.hpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -44,12 +45,12 @@ namespace kvikio { class FileHandle { private: // We use two file descriptors, one opened with the O_DIRECT flag and one without. - int _fd_direct_on{-1}; - int _fd_direct_off{-1}; + FileWrapper _fd_direct_on{}; + FileWrapper _fd_direct_off{}; bool _initialized{false}; CompatMode _compat_mode{CompatMode::AUTO}; mutable std::size_t _nbytes{0}; // The size of the underlying file, zero means unknown. - CUfileHandle_t _handle{}; + CUFileHandleWrapper _handle{}; /** * @brief Given a requested compatibility mode, whether it is expected to reduce to `ON` for @@ -122,23 +123,24 @@ class FileHandle { * @brief Get one of the file descriptors * * Notice, FileHandle maintains two file descriptors - one opened with the - * `O_DIRECT` flag and one without. This function returns one of them but - * it is unspecified which one. + * `O_DIRECT` flag and one without. * + * @param o_direct Whether to get the file descriptor opened with the `O_DIRECT` flag. * @return File descriptor */ - [[nodiscard]] int fd() const noexcept; + [[nodiscard]] int fd(bool o_direct = false) const noexcept; /** * @brief Get the flags of one of the file descriptors (see open(2)) * * Notice, FileHandle maintains two file descriptors - one opened with the - * `O_DIRECT` flag and one without. This function returns the flags of one of - * them but it is unspecified which one. + * `O_DIRECT` flag and one without. * + * @param o_direct Whether to get the flags of the file descriptor opened with the `O_DIRECT` + * flag. * @return File descriptor */ - [[nodiscard]] int fd_open_flags() const; + [[nodiscard]] int fd_open_flags(bool o_direct = false) const; /** * @brief Get the file size diff --git a/cpp/include/kvikio/file_utils.hpp b/cpp/include/kvikio/file_utils.hpp new file mode 100644 index 0000000000..5e987060a9 --- /dev/null +++ b/cpp/include/kvikio/file_utils.hpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025, NVIDIA CORPORATION. + * + * 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. + */ +#pragma once + +#include +#include + +#include + +namespace kvikio { +/** + * @brief Class that provides RAII for file handling. + */ +class FileWrapper { + private: + int _fd{-1}; + + public: + /** + * @brief Open file. + * + * @param file_path File path. + * @param flags Open flags given as a string. + * @param o_direct Append O_DIRECT to `flags`. + * @param mode Access modes. + */ + FileWrapper(std::string const& file_path, std::string const& flags, bool o_direct, mode_t mode); + + /** + * @brief Construct an empty file wrapper object without opening a file. + */ + FileWrapper() = default; + + ~FileWrapper() noexcept; + FileWrapper(FileWrapper const&) = delete; + FileWrapper& operator=(FileWrapper const&) = delete; + FileWrapper(FileWrapper&& o) noexcept; + FileWrapper& operator=(FileWrapper&& o) noexcept; + + /** + * @brief Open file using `open(2)` + * + * @param file_path File path. + * @param flags Open flags given as a string. + * @param o_direct Append O_DIRECT to `flags`. + * @param mode Access modes. + */ + void open(std::string const& file_path, std::string const& flags, bool o_direct, mode_t mode); + + /** + * @brief Check if the file has been opened. + * + * @return A boolean answer indicating if the file has been opened. + */ + bool opened() noexcept; + + /** + * @brief Close the file if it is opened; do nothing otherwise. + */ + void close() noexcept; + + /** + * @brief Return the file descriptor. + * + * @return File descriptor. + */ + int fd() const noexcept; +}; + +/** + * @brief Class that provides RAII for the cuFile handle. + */ +class CUFileHandleWrapper { + private: + CUfileHandle_t _handle{}; + bool _registered{false}; + + public: + CUFileHandleWrapper() = default; + ~CUFileHandleWrapper(); + CUFileHandleWrapper(CUFileHandleWrapper const&) = delete; + CUFileHandleWrapper& operator=(CUFileHandleWrapper const&) = delete; + CUFileHandleWrapper(CUFileHandleWrapper&& o) noexcept; + CUFileHandleWrapper& operator=(CUFileHandleWrapper&& o) noexcept; + + /** + * @brief Register the file handle given the file descriptor. + * + * @param fd File descriptor. + * @return Return the cuFile error code from handle register. If the handle has already been + * registered by calling `register_handle()`, return `std::nullopt`. + */ + std::optional register_handle(int fd); + + /** + * @brief Check if the handle has been registered. + * + * @return A boolean answer indicating if the handle has been registered. + */ + bool registered() const noexcept; + + /** + * @brief Return the cuFile handle. + * + * @return The cuFile handle. + */ + CUfileHandle_t handle() const noexcept; + + /** + * @brief Unregister the handle if it has been registered; do nothing otherwise. + */ + void unregister_handle() noexcept; +}; + +/** + * @brief Parse open file flags given as a string and return oflags + * + * @param flags The flags + * @param o_direct Append O_DIRECT to the open flags + * @return oflags + * + * @throw std::invalid_argument if the specified flags are not supported. + * @throw std::invalid_argument if `o_direct` is true, but `O_DIRECT` is not supported. + */ +int open_fd_parse_flags(std::string const& flags, bool o_direct); + +/** + * @brief Open file using `open(2)` + * + * @param flags Open flags given as a string + * @param o_direct Append O_DIRECT to `flags` + * @param mode Access modes + * @return File descriptor + */ +int open_fd(std::string const& file_path, std::string const& flags, bool o_direct, mode_t mode); + +/** + * @brief Get the flags of the file descriptor (see `open(2)`) + * + * @return Open flags + */ +[[nodiscard]] int open_flags(int fd); + +/** + * @brief Get file size from file descriptor `fstat(3)` + * + * @param file_descriptor Open file descriptor + * @return The number of bytes + */ +[[nodiscard]] std::size_t get_file_size(int file_descriptor); + +} // namespace kvikio diff --git a/cpp/src/file_handle.cpp b/cpp/src/file_handle.cpp index d6b96f3d6d..34e8bdca53 100644 --- a/cpp/src/file_handle.cpp +++ b/cpp/src/file_handle.cpp @@ -25,104 +25,15 @@ #include #include +#include namespace kvikio { -namespace { - -/** - * @brief Parse open file flags given as a string and return oflags - * - * @param flags The flags - * @param o_direct Append O_DIRECT to the open flags - * @return oflags - * - * @throw std::invalid_argument if the specified flags are not supported. - * @throw std::invalid_argument if `o_direct` is true, but `O_DIRECT` is not supported. - */ -int open_fd_parse_flags(std::string const& flags, bool o_direct) -{ - int file_flags = -1; - if (flags.empty()) { throw std::invalid_argument("Unknown file open flag"); } - switch (flags[0]) { - case 'r': - file_flags = O_RDONLY; - if (flags[1] == '+') { file_flags = O_RDWR; } - break; - case 'w': - file_flags = O_WRONLY; - if (flags[1] == '+') { file_flags = O_RDWR; } - file_flags |= O_CREAT | O_TRUNC; - break; - case 'a': throw std::invalid_argument("Open flag 'a' isn't supported"); - default: throw std::invalid_argument("Unknown file open flag"); - } - file_flags |= O_CLOEXEC; - if (o_direct) { -#if defined(O_DIRECT) - file_flags |= O_DIRECT; -#else - throw std::invalid_argument("'o_direct' flag unsupported on this platform"); -#endif - } - return file_flags; -} - -/** - * @brief Open file using `open(2)` - * - * @param flags Open flags given as a string - * @param o_direct Append O_DIRECT to `flags` - * @param mode Access modes - * @return File descriptor - */ -int open_fd(std::string const& file_path, std::string const& flags, bool o_direct, mode_t mode) -{ - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - int fd = ::open(file_path.c_str(), open_fd_parse_flags(flags, o_direct), mode); - if (fd == -1) { throw std::system_error(errno, std::generic_category(), "Unable to open file"); } - return fd; -} - -/** - * @brief Get the flags of the file descriptor (see `open(2)`) - * - * @return Open flags - */ -[[nodiscard]] int open_flags(int fd) -{ - int ret = fcntl(fd, F_GETFL); // NOLINT(cppcoreguidelines-pro-type-vararg) - if (ret == -1) { - throw std::system_error(errno, std::generic_category(), "Unable to retrieve open flags"); - } - return ret; -} - -/** - * @brief Get file size from file descriptor `fstat(3)` - * - * @param file_descriptor Open file descriptor - * @return The number of bytes - */ -[[nodiscard]] std::size_t get_file_size(int file_descriptor) -{ - struct stat st {}; - int ret = fstat(file_descriptor, &st); - if (ret == -1) { - throw std::system_error(errno, std::generic_category(), "Unable to query file size"); - } - return static_cast(st.st_size); -} - -} // namespace - FileHandle::FileHandle(std::string const& file_path, std::string const& flags, mode_t mode, CompatMode compat_mode) - : _fd_direct_off{open_fd(file_path, flags, false, mode)}, - _initialized{true}, - _compat_mode{compat_mode} + : _fd_direct_off{file_path, flags, false, mode}, _initialized{true}, _compat_mode{compat_mode} { if (is_compat_mode_preferred()) { return; // Nothing to do in compatibility mode @@ -138,7 +49,7 @@ FileHandle::FileHandle(std::string const& file_path, }; try { - _fd_direct_on = open_fd(file_path, flags, true, mode); + _fd_direct_on.open(file_path, flags, true, mode); } catch (std::system_error const&) { handle_o_direct_except(); } catch (std::invalid_argument const&) { @@ -147,40 +58,37 @@ FileHandle::FileHandle(std::string const& file_path, if (_compat_mode == CompatMode::ON) { return; } - // Create a cuFile handle, if not in compatibility mode - CUfileDescr_t desc{}; // It is important to set to zero! - desc.type = CU_FILE_HANDLE_TYPE_OPAQUE_FD; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) - desc.handle.fd = _fd_direct_on; + CUFileHandleWrapper handle; + auto error_code = handle.register_handle(_fd_direct_on.fd()); + assert(error_code.has_value()); - auto error_code = cuFileAPI::instance().HandleRegister(&_handle, &desc); // For the AUTO mode, if the first cuFile API call fails, fall back to the compatibility // mode. - if (_compat_mode == CompatMode::AUTO && error_code.err != CU_FILE_SUCCESS) { + if (_compat_mode == CompatMode::AUTO && error_code.value().err != CU_FILE_SUCCESS) { _compat_mode = CompatMode::ON; } else { - CUFILE_TRY(error_code); + CUFILE_TRY(error_code.value()); } } FileHandle::FileHandle(FileHandle&& o) noexcept - : _fd_direct_on{std::exchange(o._fd_direct_on, -1)}, - _fd_direct_off{std::exchange(o._fd_direct_off, -1)}, + : _fd_direct_on{std::exchange(o._fd_direct_on, {})}, + _fd_direct_off{std::exchange(o._fd_direct_off, {})}, _initialized{std::exchange(o._initialized, false)}, _compat_mode{std::exchange(o._compat_mode, CompatMode::AUTO)}, _nbytes{std::exchange(o._nbytes, 0)}, - _handle{std::exchange(o._handle, CUfileHandle_t{})} + _handle{std::exchange(o._handle, {})} { } FileHandle& FileHandle::operator=(FileHandle&& o) noexcept { - _fd_direct_on = std::exchange(o._fd_direct_on, -1); - _fd_direct_off = std::exchange(o._fd_direct_off, -1); + _fd_direct_on = std::exchange(o._fd_direct_on, {}); + _fd_direct_off = std::exchange(o._fd_direct_off, {}); _initialized = std::exchange(o._initialized, false); _compat_mode = std::exchange(o._compat_mode, CompatMode::AUTO); _nbytes = std::exchange(o._nbytes, 0); - _handle = std::exchange(o._handle, CUfileHandle_t{}); + _handle = std::exchange(o._handle, {}); return *this; } @@ -193,13 +101,11 @@ void FileHandle::close() noexcept try { if (closed()) { return; } - if (!is_compat_mode_preferred()) { cuFileAPI::instance().HandleDeregister(_handle); } + _handle.unregister_handle(); _compat_mode = CompatMode::AUTO; - ::close(_fd_direct_off); - if (_fd_direct_on != -1) { ::close(_fd_direct_on); } - _fd_direct_on = -1; - _fd_direct_off = -1; - _initialized = false; + _fd_direct_off.close(); + _fd_direct_on.close(); + _initialized = false; } catch (...) { } } @@ -210,17 +116,20 @@ CUfileHandle_t FileHandle::handle() if (is_compat_mode_preferred()) { throw CUfileException("The underlying cuFile handle isn't available in compatibility mode"); } - return _handle; + return _handle.handle(); } -int FileHandle::fd() const noexcept { return _fd_direct_off; } +int FileHandle::fd(bool o_direct) const noexcept +{ + return o_direct ? _fd_direct_on.fd() : _fd_direct_off.fd(); +} -int FileHandle::fd_open_flags() const { return open_flags(_fd_direct_off); } +int FileHandle::fd_open_flags(bool o_direct) const { return open_flags(fd(o_direct)); } std::size_t FileHandle::nbytes() const { if (closed()) { return 0; } - if (_nbytes == 0) { _nbytes = get_file_size(_fd_direct_off); } + if (_nbytes == 0) { _nbytes = get_file_size(_fd_direct_off.fd()); } return _nbytes; } @@ -231,13 +140,17 @@ std::size_t FileHandle::read(void* devPtr_base, bool sync_default_stream) { if (is_compat_mode_preferred()) { - return detail::posix_device_read(_fd_direct_off, devPtr_base, size, file_offset, devPtr_offset); + return detail::posix_device_read( + _fd_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); } if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } KVIKIO_NVTX_SCOPED_RANGE("cufileRead()", size); - ssize_t ret = cuFileAPI::instance().Read( - _handle, devPtr_base, size, convert_size2off(file_offset), convert_size2off(devPtr_offset)); + ssize_t ret = cuFileAPI::instance().Read(_handle.handle(), + devPtr_base, + size, + convert_size2off(file_offset), + convert_size2off(devPtr_offset)); CUFILE_CHECK_BYTES_DONE(ret); return ret; } @@ -252,13 +165,16 @@ std::size_t FileHandle::write(void const* devPtr_base, if (is_compat_mode_preferred()) { return detail::posix_device_write( - _fd_direct_off, devPtr_base, size, file_offset, devPtr_offset); + _fd_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); } if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } KVIKIO_NVTX_SCOPED_RANGE("cufileWrite()", size); - ssize_t ret = cuFileAPI::instance().Write( - _handle, devPtr_base, size, convert_size2off(file_offset), convert_size2off(devPtr_offset)); + ssize_t ret = cuFileAPI::instance().Write(_handle.handle(), + devPtr_base, + size, + convert_size2off(file_offset), + convert_size2off(devPtr_offset)); if (ret == -1) { throw std::system_error(errno, std::generic_category(), "Unable to write file"); } @@ -283,7 +199,8 @@ std::future FileHandle::pread(void* buf, std::size_t file_offset, std::size_t hostPtr_offset) -> std::size_t { char* buf = static_cast(hostPtr_base) + hostPtr_offset; - return detail::posix_host_read(_fd_direct_off, buf, size, file_offset); + return detail::posix_host_read( + _fd_direct_off.fd(), buf, size, file_offset); }; return parallel_io(op, buf, size, file_offset, task_size, 0); @@ -295,7 +212,7 @@ std::future FileHandle::pread(void* buf, if (size < gds_threshold) { auto task = [this, ctx, buf, size, file_offset]() -> std::size_t { PushAndPopContext c(ctx); - return detail::posix_device_read(_fd_direct_off, buf, size, file_offset, 0); + return detail::posix_device_read(_fd_direct_off.fd(), buf, size, file_offset, 0); }; return std::async(std::launch::deferred, task); } @@ -333,7 +250,7 @@ std::future FileHandle::pwrite(void const* buf, std::size_t hostPtr_offset) -> std::size_t { char const* buf = static_cast(hostPtr_base) + hostPtr_offset; return detail::posix_host_write( - _fd_direct_off, buf, size, file_offset); + _fd_direct_off.fd(), buf, size, file_offset); }; return parallel_io(op, buf, size, file_offset, task_size, 0); @@ -345,7 +262,7 @@ std::future FileHandle::pwrite(void const* buf, if (size < gds_threshold) { auto task = [this, ctx, buf, size, file_offset]() -> std::size_t { PushAndPopContext c(ctx); - return detail::posix_device_write(_fd_direct_off, buf, size, file_offset, 0); + return detail::posix_device_write(_fd_direct_off.fd(), buf, size, file_offset, 0); }; return std::async(std::launch::deferred, task); } @@ -381,7 +298,7 @@ void FileHandle::read_async(void* devPtr_base, static_cast(read(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); } else { CUFILE_TRY(cuFileAPI::instance().ReadAsync( - _handle, devPtr_base, size_p, file_offset_p, devPtr_offset_p, bytes_read_p, stream)); + _handle.handle(), devPtr_base, size_p, file_offset_p, devPtr_offset_p, bytes_read_p, stream)); } } @@ -407,8 +324,13 @@ void FileHandle::write_async(void* devPtr_base, *bytes_written_p = static_cast(write(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); } else { - CUFILE_TRY(cuFileAPI::instance().WriteAsync( - _handle, devPtr_base, size_p, file_offset_p, devPtr_offset_p, bytes_written_p, stream)); + CUFILE_TRY(cuFileAPI::instance().WriteAsync(_handle.handle(), + devPtr_base, + size_p, + file_offset_p, + devPtr_offset_p, + bytes_written_p, + stream)); } } diff --git a/cpp/src/file_utils.cpp b/cpp/src/file_utils.cpp new file mode 100644 index 0000000000..ac0ac7ceb2 --- /dev/null +++ b/cpp/src/file_utils.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2025, NVIDIA CORPORATION. + * + * 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 +#include +#include +#include +#include +#include + +#include +#include + +namespace kvikio { + +FileWrapper::FileWrapper(std::string const& file_path, + std::string const& flags, + bool o_direct, + mode_t mode) +{ + open(file_path, flags, o_direct, mode); +} + +FileWrapper::~FileWrapper() noexcept { close(); } + +FileWrapper::FileWrapper(FileWrapper&& o) noexcept : _fd(std::exchange(o._fd, -1)) {} + +FileWrapper& FileWrapper::operator=(FileWrapper&& o) noexcept +{ + _fd = std::exchange(o._fd, -1); + return *this; +} + +void FileWrapper::open(std::string const& file_path, + std::string const& flags, + bool o_direct, + mode_t mode) +{ + if (!opened()) { _fd = open_fd(file_path, flags, o_direct, mode); } +} + +bool FileWrapper::opened() noexcept { return _fd != -1; } + +void FileWrapper::close() noexcept +{ + if (opened()) { + ::close(_fd); + _fd = -1; + } +} + +int FileWrapper::fd() const noexcept { return _fd; } + +CUFileHandleWrapper::~CUFileHandleWrapper() { unregister_handle(); } + +CUFileHandleWrapper::CUFileHandleWrapper(CUFileHandleWrapper&& o) noexcept + : _handle{std::exchange(o._handle, {})}, _registered{std::exchange(o._registered, false)} +{ +} + +CUFileHandleWrapper& CUFileHandleWrapper::operator=(CUFileHandleWrapper&& o) noexcept +{ + _handle = std::exchange(o._handle, {}); + _registered = std::exchange(o._registered, false); + return *this; +} + +std::optional CUFileHandleWrapper::register_handle(int fd) +{ + std::optional error_code; + if (registered()) { return error_code; } + + // Create a cuFile handle, if not in compatibility mode + CUfileDescr_t desc{}; // It is important to set to zero! + desc.type = CU_FILE_HANDLE_TYPE_OPAQUE_FD; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + desc.handle.fd = fd; + error_code = cuFileAPI::instance().HandleRegister(&_handle, &desc); + if (error_code.value().err == CU_FILE_SUCCESS) { _registered = true; } + return error_code; +} + +bool CUFileHandleWrapper::registered() const noexcept { return _registered; } + +CUfileHandle_t CUFileHandleWrapper::handle() const noexcept { return _handle; } + +void CUFileHandleWrapper::unregister_handle() noexcept +{ + if (registered()) { + cuFileAPI::instance().HandleDeregister(_handle); + _registered = false; + } +} + +int open_fd_parse_flags(std::string const& flags, bool o_direct) +{ + int file_flags = -1; + if (flags.empty()) { throw std::invalid_argument("Unknown file open flag"); } + switch (flags[0]) { + case 'r': + file_flags = O_RDONLY; + if (flags[1] == '+') { file_flags = O_RDWR; } + break; + case 'w': + file_flags = O_WRONLY; + if (flags[1] == '+') { file_flags = O_RDWR; } + file_flags |= O_CREAT | O_TRUNC; + break; + case 'a': throw std::invalid_argument("Open flag 'a' isn't supported"); + default: throw std::invalid_argument("Unknown file open flag"); + } + file_flags |= O_CLOEXEC; + if (o_direct) { +#if defined(O_DIRECT) + file_flags |= O_DIRECT; +#else + throw std::invalid_argument("'o_direct' flag unsupported on this platform"); +#endif + } + return file_flags; +} + +int open_fd(std::string const& file_path, std::string const& flags, bool o_direct, mode_t mode) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + int fd = ::open(file_path.c_str(), open_fd_parse_flags(flags, o_direct), mode); + if (fd == -1) { throw std::system_error(errno, std::generic_category(), "Unable to open file"); } + return fd; +} + +[[nodiscard]] int open_flags(int fd) +{ + int ret = fcntl(fd, F_GETFL); // NOLINT(cppcoreguidelines-pro-type-vararg) + if (ret == -1) { + throw std::system_error(errno, std::generic_category(), "Unable to retrieve open flags"); + } + return ret; +} + +[[nodiscard]] std::size_t get_file_size(int file_descriptor) +{ + struct stat st {}; + int ret = fstat(file_descriptor, &st); + if (ret == -1) { + throw std::system_error(errno, std::generic_category(), "Unable to query file size"); + } + return static_cast(st.st_size); +} + +} // namespace kvikio From 8105aedb1488f44bbd224f7a9db9e8d8c0606306 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 4 Feb 2025 10:45:36 -0500 Subject: [PATCH 2/5] Add const --- cpp/include/kvikio/file_utils.hpp | 2 +- cpp/src/file_utils.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/include/kvikio/file_utils.hpp b/cpp/include/kvikio/file_utils.hpp index 5e987060a9..c255f45095 100644 --- a/cpp/include/kvikio/file_utils.hpp +++ b/cpp/include/kvikio/file_utils.hpp @@ -65,7 +65,7 @@ class FileWrapper { * * @return A boolean answer indicating if the file has been opened. */ - bool opened() noexcept; + bool opened() const noexcept; /** * @brief Close the file if it is opened; do nothing otherwise. diff --git a/cpp/src/file_utils.cpp b/cpp/src/file_utils.cpp index ac0ac7ceb2..7c36653185 100644 --- a/cpp/src/file_utils.cpp +++ b/cpp/src/file_utils.cpp @@ -52,7 +52,7 @@ void FileWrapper::open(std::string const& file_path, if (!opened()) { _fd = open_fd(file_path, flags, o_direct, mode); } } -bool FileWrapper::opened() noexcept { return _fd != -1; } +bool FileWrapper::opened() const noexcept { return _fd != -1; } void FileWrapper::close() noexcept { From b02ab0019ae55d6a4049fa8e4cc2e7a277a9b20a Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 4 Feb 2025 11:48:11 -0500 Subject: [PATCH 3/5] Rename var --- cpp/include/kvikio/file_handle.hpp | 2 +- cpp/src/file_handle.cpp | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cpp/include/kvikio/file_handle.hpp b/cpp/include/kvikio/file_handle.hpp index 371034e027..ba44992b53 100644 --- a/cpp/include/kvikio/file_handle.hpp +++ b/cpp/include/kvikio/file_handle.hpp @@ -50,7 +50,7 @@ class FileHandle { bool _initialized{false}; CompatMode _compat_mode{CompatMode::AUTO}; mutable std::size_t _nbytes{0}; // The size of the underlying file, zero means unknown. - CUFileHandleWrapper _handle{}; + CUFileHandleWrapper _cufile_handle{}; /** * @brief Given a requested compatibility mode, whether it is expected to reduce to `ON` for diff --git a/cpp/src/file_handle.cpp b/cpp/src/file_handle.cpp index 34e8bdca53..0e65afb7fd 100644 --- a/cpp/src/file_handle.cpp +++ b/cpp/src/file_handle.cpp @@ -77,7 +77,7 @@ FileHandle::FileHandle(FileHandle&& o) noexcept _initialized{std::exchange(o._initialized, false)}, _compat_mode{std::exchange(o._compat_mode, CompatMode::AUTO)}, _nbytes{std::exchange(o._nbytes, 0)}, - _handle{std::exchange(o._handle, {})} + _cufile_handle{std::exchange(o._cufile_handle, {})} { } @@ -88,7 +88,7 @@ FileHandle& FileHandle::operator=(FileHandle&& o) noexcept _initialized = std::exchange(o._initialized, false); _compat_mode = std::exchange(o._compat_mode, CompatMode::AUTO); _nbytes = std::exchange(o._nbytes, 0); - _handle = std::exchange(o._handle, {}); + _cufile_handle = std::exchange(o._cufile_handle, {}); return *this; } @@ -101,7 +101,7 @@ void FileHandle::close() noexcept try { if (closed()) { return; } - _handle.unregister_handle(); + _cufile_handle.unregister_handle(); _compat_mode = CompatMode::AUTO; _fd_direct_off.close(); _fd_direct_on.close(); @@ -116,7 +116,7 @@ CUfileHandle_t FileHandle::handle() if (is_compat_mode_preferred()) { throw CUfileException("The underlying cuFile handle isn't available in compatibility mode"); } - return _handle.handle(); + return _cufile_handle.handle(); } int FileHandle::fd(bool o_direct) const noexcept @@ -146,7 +146,7 @@ std::size_t FileHandle::read(void* devPtr_base, if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } KVIKIO_NVTX_SCOPED_RANGE("cufileRead()", size); - ssize_t ret = cuFileAPI::instance().Read(_handle.handle(), + ssize_t ret = cuFileAPI::instance().Read(_cufile_handle.handle(), devPtr_base, size, convert_size2off(file_offset), @@ -170,7 +170,7 @@ std::size_t FileHandle::write(void const* devPtr_base, if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } KVIKIO_NVTX_SCOPED_RANGE("cufileWrite()", size); - ssize_t ret = cuFileAPI::instance().Write(_handle.handle(), + ssize_t ret = cuFileAPI::instance().Write(_cufile_handle.handle(), devPtr_base, size, convert_size2off(file_offset), @@ -297,8 +297,13 @@ void FileHandle::read_async(void* devPtr_base, *bytes_read_p = static_cast(read(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); } else { - CUFILE_TRY(cuFileAPI::instance().ReadAsync( - _handle.handle(), devPtr_base, size_p, file_offset_p, devPtr_offset_p, bytes_read_p, stream)); + CUFILE_TRY(cuFileAPI::instance().ReadAsync(_cufile_handle.handle(), + devPtr_base, + size_p, + file_offset_p, + devPtr_offset_p, + bytes_read_p, + stream)); } } @@ -324,7 +329,7 @@ void FileHandle::write_async(void* devPtr_base, *bytes_written_p = static_cast(write(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); } else { - CUFILE_TRY(cuFileAPI::instance().WriteAsync(_handle.handle(), + CUFILE_TRY(cuFileAPI::instance().WriteAsync(_cufile_handle.handle(), devPtr_base, size_p, file_offset_p, From 4a957177109bc1a4477d637845e0e6ecbb0bd72f Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 4 Feb 2025 11:59:04 -0500 Subject: [PATCH 4/5] Add noexcept --- cpp/include/kvikio/file_utils.hpp | 8 ++++---- cpp/src/file_utils.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp/include/kvikio/file_utils.hpp b/cpp/include/kvikio/file_utils.hpp index c255f45095..9cf3cae380 100644 --- a/cpp/include/kvikio/file_utils.hpp +++ b/cpp/include/kvikio/file_utils.hpp @@ -42,7 +42,7 @@ class FileWrapper { /** * @brief Construct an empty file wrapper object without opening a file. */ - FileWrapper() = default; + FileWrapper() noexcept = default; ~FileWrapper() noexcept; FileWrapper(FileWrapper const&) = delete; @@ -89,8 +89,8 @@ class CUFileHandleWrapper { bool _registered{false}; public: - CUFileHandleWrapper() = default; - ~CUFileHandleWrapper(); + CUFileHandleWrapper() noexcept = default; + ~CUFileHandleWrapper() noexcept; CUFileHandleWrapper(CUFileHandleWrapper const&) = delete; CUFileHandleWrapper& operator=(CUFileHandleWrapper const&) = delete; CUFileHandleWrapper(CUFileHandleWrapper&& o) noexcept; @@ -103,7 +103,7 @@ class CUFileHandleWrapper { * @return Return the cuFile error code from handle register. If the handle has already been * registered by calling `register_handle()`, return `std::nullopt`. */ - std::optional register_handle(int fd); + std::optional register_handle(int fd) noexcept; /** * @brief Check if the handle has been registered. diff --git a/cpp/src/file_utils.cpp b/cpp/src/file_utils.cpp index 7c36653185..da710bb43b 100644 --- a/cpp/src/file_utils.cpp +++ b/cpp/src/file_utils.cpp @@ -64,7 +64,7 @@ void FileWrapper::close() noexcept int FileWrapper::fd() const noexcept { return _fd; } -CUFileHandleWrapper::~CUFileHandleWrapper() { unregister_handle(); } +CUFileHandleWrapper::~CUFileHandleWrapper() noexcept { unregister_handle(); } CUFileHandleWrapper::CUFileHandleWrapper(CUFileHandleWrapper&& o) noexcept : _handle{std::exchange(o._handle, {})}, _registered{std::exchange(o._registered, false)} @@ -78,7 +78,7 @@ CUFileHandleWrapper& CUFileHandleWrapper::operator=(CUFileHandleWrapper&& o) noe return *this; } -std::optional CUFileHandleWrapper::register_handle(int fd) +std::optional CUFileHandleWrapper::register_handle(int fd) noexcept { std::optional error_code; if (registered()) { return error_code; } From 0c45bfa2a88d9a31c44bcea6324d336bb76f01c5 Mon Sep 17 00:00:00 2001 From: Tianyu Liu Date: Tue, 4 Feb 2025 13:59:23 -0500 Subject: [PATCH 5/5] Add error logging --- cpp/include/kvikio/error.hpp | 3 +++ cpp/src/error.cpp | 15 +++++++++++++++ cpp/src/file_utils.cpp | 3 ++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/cpp/include/kvikio/error.hpp b/cpp/include/kvikio/error.hpp index 25bf62a877..e38d05054f 100644 --- a/cpp/include/kvikio/error.hpp +++ b/cpp/include/kvikio/error.hpp @@ -115,6 +115,9 @@ void cufile_check_bytes_done_2(ssize_t nbytes_done, int line_number, char const* } } +#define KVIKIO_LOG_ERROR(err_msg) kvikio::detail::log_error(err_msg, __LINE__, __FILE__) +void log_error(std::string_view err_msg, int line_number, char const* filename); + } // namespace detail } // namespace kvikio diff --git a/cpp/src/error.cpp b/cpp/src/error.cpp index 21ce736a65..116a77cd28 100644 --- a/cpp/src/error.cpp +++ b/cpp/src/error.cpp @@ -14,4 +14,19 @@ * limitations under the License. */ +#include + #include + +namespace kvikio { + +namespace detail { + +void log_error(std::string_view err_msg, int line_number, char const* filename) +{ + std::cerr << "KvikIO error at: " << filename << ":" << line_number << ": " << err_msg << "\n"; +} + +} // namespace detail + +} // namespace kvikio diff --git a/cpp/src/file_utils.cpp b/cpp/src/file_utils.cpp index da710bb43b..ca00f254b2 100644 --- a/cpp/src/file_utils.cpp +++ b/cpp/src/file_utils.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -57,7 +58,7 @@ bool FileWrapper::opened() const noexcept { return _fd != -1; } void FileWrapper::close() noexcept { if (opened()) { - ::close(_fd); + if (::close(_fd) != 0) { KVIKIO_LOG_ERROR("File cannot be closed"); } _fd = -1; } }