diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 4c1daa58c6..0fd44e4e88 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -138,6 +138,7 @@ set(SOURCES "src/batch.cpp" "src/bounce_buffer.cpp" "src/buffer.cpp" + "src/compat_mode.cpp" "src/cufile/config.cpp" "src/cufile/driver.cpp" "src/defaults.cpp" diff --git a/cpp/doxygen/main_page.md b/cpp/doxygen/main_page.md index 5f21d6f224..a5e9e9162d 100644 --- a/cpp/doxygen/main_page.md +++ b/cpp/doxygen/main_page.md @@ -80,7 +80,7 @@ When KvikIO is running in compatibility mode, it doesn't load `libcufile.so`. In The environment variable `KVIKIO_COMPAT_MODE` has three options (case-insensitive): - `ON` (aliases: `TRUE`, `YES`, `1`): Enable the compatibility mode. - - `OFF` (aliases: `FALSE`, `NO`, `0`): Disable the compatibility mode, and enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met and cuFile is properly configured. However, if the system is not suited for cuFile, I/O operations under the `OFF` option may error out, crash or hang. + - `OFF` (aliases: `FALSE`, `NO`, `0`): Disable the compatibility mode, and enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met and cuFile is properly configured. However, if the system is not suited for cuFile, I/O operations under the `OFF` option may error out. - `AUTO`: Try cuFile I/O first, and fall back to POSIX I/O if the system requirements for cuFile are not met. Under `AUTO`, KvikIO falls back to the compatibility mode: diff --git a/cpp/include/kvikio/compat_mode.hpp b/cpp/include/kvikio/compat_mode.hpp new file mode 100644 index 0000000000..04998ba798 --- /dev/null +++ b/cpp/include/kvikio/compat_mode.hpp @@ -0,0 +1,150 @@ +/* + * 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 I/O compatibility mode. + */ +enum class CompatMode : uint8_t { + OFF, ///< Enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met + ///< and cuFile is properly configured. However, if the system is not suited for cuFile, I/O + ///< operations under the OFF option may error out. + ON, ///< Enforce POSIX I/O. + AUTO, ///< Try cuFile I/O first, and fall back to POSIX I/O if the system requirements for cuFile + ///< are not met. +}; + +namespace detail { +/** + * @brief Parse a string into a CompatMode enum. + * + * @param compat_mode_str Compatibility mode in string format (case-insensitive). Valid values + * include: + * - `ON` (alias: `TRUE`, `YES`, `1`) + * - `OFF` (alias: `FALSE`, `NO`, `0`) + * - `AUTO` + * @return A CompatMode enum. + */ +CompatMode parse_compat_mode_str(std::string_view compat_mode_str); + +} // namespace detail + +// Forward declaration. +class FileHandle; + +/** + * @brief Store and manage the compatibility mode data associated with a FileHandle. + */ +class CompatModeManager { + private: + CompatMode _compat_mode_requested{CompatMode::AUTO}; + bool _is_compat_mode_preferred{true}; + bool _is_compat_mode_preferred_for_async{true}; + + public: + /** + * @brief Construct an empty compatibility mode manager. + */ + CompatModeManager() noexcept = default; + + /** + * @brief Construct a compatibility mode manager associated with a FileHandle. + * + * According to the file path, requested compatibility mode, and the system configuration, the + * compatibility manager: + * - Infers the final compatibility modes for synchronous and asynchronous I/O paths, + * respectively. + * - Initializes the file wrappers and cuFile handle associated with a FileHandle. + * + * @param file_path Refer to + * FileHandle::FileHandle(std::string const&, std::string const&, mode_t, CompatMode). + * @param flags Same as above. + * @param mode Same as above. + * @param compat_mode_requested Same as above. + * @param file_handle Point to the FileHandle object that owns this compatibility mode manager. + */ + CompatModeManager(std::string const& file_path, + std::string const& flags, + mode_t mode, + CompatMode compat_mode_requested, + FileHandle* file_handle); + + ~CompatModeManager() noexcept = default; + CompatModeManager(const CompatModeManager&) = default; + CompatModeManager& operator=(const CompatModeManager&) = default; + CompatModeManager(CompatModeManager&&) noexcept = default; + CompatModeManager& operator=(CompatModeManager&&) noexcept = default; + + /** + * @brief Functionally identical to defaults::infer_compat_mode_if_auto(CompatMode). + * + * @param compat_mode Compatibility mode. + * @return If the given compatibility mode is CompatMode::AUTO, infer the final compatibility + * mode. + */ + CompatMode infer_compat_mode_if_auto(CompatMode compat_mode) noexcept; + + /** + * @brief Functionally identical to defaults::is_compat_mode_preferred(CompatMode). + * + * @param compat_mode Compatibility mode. + * @return Boolean answer. + */ + bool is_compat_mode_preferred(CompatMode compat_mode) noexcept; + + /** + * @brief Check if the compatibility mode for synchronous I/O of the associated FileHandle is + * expected to be CompatMode::ON. + * + * @return Boolean answer. + */ + bool is_compat_mode_preferred() const noexcept; + + /** + * @brief Check if the compatibility mode for asynchronous I/O of the associated FileHandle is + * expected to be CompatMode::ON. + * + * @return Boolean answer. + */ + bool is_compat_mode_preferred_for_async() const noexcept; + + /** + * @brief Retrieve the original compatibility mode requested. + * + * @return The original compatibility mode requested. + */ + CompatMode compat_mode_requested() const noexcept; + + /** + * @brief Determine if the asynchronous I/O can be performed or not (throw exceptions) + * according to the existing compatibility mode data in the manager. + * + * The asynchronous I/O cannot be performed, for instance, when compat_mode_requested() is + * CompatMode::OFF, is_compat_mode_preferred() is CompatMode::OFF, but + * is_compat_mode_preferred_for_async() is CompatMode::ON (due to missing cuFile stream API or + * cuFile configuration file). + */ + void validate_compat_mode_for_async() const; +}; + +} // namespace kvikio diff --git a/cpp/include/kvikio/defaults.hpp b/cpp/include/kvikio/defaults.hpp index 501c71981a..563cf09456 100644 --- a/cpp/include/kvikio/defaults.hpp +++ b/cpp/include/kvikio/defaults.hpp @@ -14,11 +14,6 @@ * limitations under the License. */ -// Enable documentation of the enum. -/** - * @file - */ - #pragma once #include @@ -29,35 +24,13 @@ #include +#include #include -namespace kvikio { /** - * @brief I/O compatibility mode. + * @brief KvikIO namespace. */ -enum class CompatMode : uint8_t { - OFF, ///< Enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met - ///< and cuFile is properly configured. However, if the system is not suited for cuFile, I/O - ///< operations under the OFF option may error out, crash or hang. - ON, ///< Enforce POSIX I/O. - AUTO, ///< Try cuFile I/O first, and fall back to POSIX I/O if the system requirements for cuFile - ///< are not met. -}; - -namespace detail { -/** - * @brief Parse a string into a CompatMode enum. - * - * @param compat_mode_str Compatibility mode in string format(case-insensitive). Valid values - * include: - * - `ON` (alias: `TRUE`, `YES`, `1`) - * - `OFF` (alias: `FALSE`, `NO`, `0`) - * - `AUTO` - * @return A CompatMode enum. - */ -CompatMode parse_compat_mode_str(std::string_view compat_mode_str); - -} // namespace detail +namespace kvikio { template T getenv_or(std::string_view env_var_name, T default_val) diff --git a/cpp/include/kvikio/error.hpp b/cpp/include/kvikio/error.hpp index e38d05054f..f0253fe70e 100644 --- a/cpp/include/kvikio/error.hpp +++ b/cpp/include/kvikio/error.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -71,7 +72,7 @@ void cuda_driver_try_2(CUresult error, int line_number, char const* filename) { if (error == CUDA_ERROR_STUB_LIBRARY) { throw Exception{std::string{"CUDA error at: "} + std::string(filename) + ":" + - KVIKIO_STRINGIFY(line_number) + + std::to_string(line_number) + ": CUDA_ERROR_STUB_LIBRARY(" "The CUDA driver loaded is a stub library)"}; } @@ -82,9 +83,8 @@ void cuda_driver_try_2(CUresult error, int line_number, char const* filename) CUresult err_str_status = cudaAPI::instance().GetErrorString(error, &err_str); if (err_name_status == CUDA_ERROR_INVALID_VALUE) { err_name = "unknown"; } if (err_str_status == CUDA_ERROR_INVALID_VALUE) { err_str = "unknown"; } - throw Exception{std::string{"CUDA error at: "} + filename + ":" + - KVIKIO_STRINGIFY(line_number) + ": " + std::string(err_name) + "(" + - std::string(err_str) + ")"}; + throw Exception{std::string{"CUDA error at: "} + filename + ":" + std::to_string(line_number) + + ": " + std::string(err_name) + "(" + std::string(err_str) + ")"}; } } @@ -97,7 +97,7 @@ void cufile_try_2(CUfileError_t error, int line_number, char const* filename) CUDA_DRIVER_TRY(cuda_error); } throw Exception{std::string{"cuFile error at: "} + filename + ":" + - KVIKIO_STRINGIFY(line_number) + ": " + + std::to_string(line_number) + ": " + cufileop_status_error((CUfileOpError)std::abs(error.err))}; } } @@ -111,7 +111,7 @@ void cufile_check_bytes_done_2(ssize_t nbytes_done, int line_number, char const* ? std::string(cufileop_status_error((CUfileOpError)err)) : std::string(std::strerror(err)); throw Exception{std::string{"cuFile error at: "} + filename + ":" + - KVIKIO_STRINGIFY(line_number) + ": " + msg}; + std::to_string(line_number) + ": " + msg}; } } diff --git a/cpp/include/kvikio/file_handle.hpp b/cpp/include/kvikio/file_handle.hpp index ba44992b53..3d31b96dc1 100644 --- a/cpp/include/kvikio/file_handle.hpp +++ b/cpp/include/kvikio/file_handle.hpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include @@ -45,23 +47,13 @@ namespace kvikio { class FileHandle { private: // We use two file descriptors, one opened with the O_DIRECT flag and one without. - FileWrapper _fd_direct_on{}; - FileWrapper _fd_direct_off{}; + FileWrapper _file_direct_on{}; + FileWrapper _file_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. CUFileHandleWrapper _cufile_handle{}; - - /** - * @brief Given a requested compatibility mode, whether it is expected to reduce to `ON` for - * asynchronous I/O. - * - * @param requested_compat_mode Requested compatibility mode. - * @return True if POSIX I/O fallback will be used; false for cuFile I/O. - * @exception std::runtime_error When the requested compatibility mode is `OFF`, but cuFile - * batch/stream library symbol is missing, or cuFile configuration file is missing. - */ - bool is_compat_mode_preferred_for_async(CompatMode requested_compat_mode); + CompatModeManager _compat_mode_manager; + friend class CompatModeManager; public: static constexpr mode_t m644 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH; @@ -444,27 +436,13 @@ class FileHandle { CUstream stream = nullptr); /** - * @brief Returns `true` if the compatibility mode is expected to be `ON` for this file. - * - * Compatibility mode can be explicitly enabled in object creation. The mode is also enabled - * automatically, if file cannot be opened with the `O_DIRECT` flag, or if the system does not - * meet the requirements for the cuFile library under the `AUTO` compatibility mode. - * - * @return Boolean answer. - */ - [[nodiscard]] bool is_compat_mode_preferred() const noexcept; - - /** - * @brief Returns `true` if the compatibility mode is expected to be `ON` for the asynchronous I/O - * on this file. - * - * For asynchronous I/O, the compatibility mode can be automatically enabled if the cuFile batch - * and stream symbols are missing, or if the cuFile configuration file is missing, or if - * `is_compat_mode_preferred()` returns true. + * @brief Get the associated compatibility mode manager, which can be used to query the original + * requested compatibility mode or the expected compatibility modes for synchronous and + * asynchronous I/O. * - * @return Boolean answer. + * @return The associated compatibility mode manager. */ - [[nodiscard]] bool is_compat_mode_preferred_for_async() const noexcept; + const CompatModeManager& get_compat_mode_manager() const noexcept; }; } // namespace kvikio diff --git a/cpp/src/batch.cpp b/cpp/src/batch.cpp index 128c2d5953..ee3148d7cc 100644 --- a/cpp/src/batch.cpp +++ b/cpp/src/batch.cpp @@ -60,7 +60,7 @@ void BatchHandle::submit(std::vector const& operations) std::vector io_batch_params; io_batch_params.reserve(operations.size()); for (auto const& op : operations) { - if (op.file_handle.is_compat_mode_preferred()) { + if (op.file_handle.get_compat_mode_manager().is_compat_mode_preferred()) { throw CUfileException("Cannot submit a FileHandle opened in compatibility mode"); } diff --git a/cpp/src/compat_mode.cpp b/cpp/src/compat_mode.cpp new file mode 100644 index 0000000000..52d741ee29 --- /dev/null +++ b/cpp/src/compat_mode.cpp @@ -0,0 +1,146 @@ +/* + * 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 { + +namespace detail { +CompatMode parse_compat_mode_str(std::string_view compat_mode_str) +{ + // Convert to lowercase + std::string tmp{compat_mode_str}; + std::transform( + tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c) { return std::tolower(c); }); + + CompatMode res{}; + if (tmp == "on" || tmp == "true" || tmp == "yes" || tmp == "1") { + res = CompatMode::ON; + } else if (tmp == "off" || tmp == "false" || tmp == "no" || tmp == "0") { + res = CompatMode::OFF; + } else if (tmp == "auto") { + res = CompatMode::AUTO; + } else { + throw std::invalid_argument("Unknown compatibility mode: " + std::string{tmp}); + } + return res; +} + +} // namespace detail + +CompatMode CompatModeManager::infer_compat_mode_if_auto(CompatMode compat_mode) noexcept +{ + if (compat_mode == CompatMode::AUTO) { + return is_cufile_available() ? CompatMode::OFF : CompatMode::ON; + } + return compat_mode; +} + +bool CompatModeManager::is_compat_mode_preferred(CompatMode compat_mode) noexcept +{ + return compat_mode == CompatMode::ON || + (compat_mode == CompatMode::AUTO && + infer_compat_mode_if_auto(compat_mode) == CompatMode::ON); +} + +bool CompatModeManager::is_compat_mode_preferred() const noexcept +{ + return _is_compat_mode_preferred; +} + +bool CompatModeManager::is_compat_mode_preferred_for_async() const noexcept +{ + return _is_compat_mode_preferred_for_async; +} + +CompatMode CompatModeManager::compat_mode_requested() const noexcept +{ + return _compat_mode_requested; +} + +CompatModeManager::CompatModeManager(std::string const& file_path, + std::string const& flags, + mode_t mode, + CompatMode compat_mode_requested_v, + FileHandle* file_handle) +{ + if (file_handle == nullptr) { + throw std::invalid_argument( + "The compatibility mode manager does not have a proper owning file handle."); + } + + file_handle->_file_direct_off.open(file_path, flags, false, mode); + _is_compat_mode_preferred = is_compat_mode_preferred(compat_mode_requested_v); + + // Nothing to do in compatibility mode + if (_is_compat_mode_preferred) { return; } + + try { + file_handle->_file_direct_on.open(file_path, flags, true, mode); + } catch (...) { + // Try to open the file with the O_DIRECT flag. Fall back to compatibility mode, if it fails. + if (compat_mode_requested_v == CompatMode::AUTO) { + _is_compat_mode_preferred = true; + } else { // CompatMode::OFF + throw; + } + } + + if (_is_compat_mode_preferred) { return; } + + auto error_code = file_handle->_cufile_handle.register_handle(file_handle->_file_direct_on.fd()); + assert(error_code.has_value()); + + // For the AUTO mode, if the first cuFile API call fails, fall back to the compatibility + // mode. + if (compat_mode_requested_v == CompatMode::AUTO && error_code.value().err != CU_FILE_SUCCESS) { + _is_compat_mode_preferred = true; + } else { + CUFILE_TRY(error_code.value()); + } + + // Check cuFile async API + static bool is_extra_symbol_available = is_stream_api_available(); + static bool is_config_path_empty = config_path().empty(); + _is_compat_mode_preferred_for_async = + _is_compat_mode_preferred || !is_extra_symbol_available || is_config_path_empty; + return; +} + +void CompatModeManager::validate_compat_mode_for_async() const +{ + if (!_is_compat_mode_preferred && _is_compat_mode_preferred_for_async && + _compat_mode_requested == CompatMode::OFF) { + std::string err_msg; + if (!is_stream_api_available()) { err_msg += "Missing the cuFile stream api."; } + + // When checking for availability, we also check if cuFile's config file exists. This is + // because even when the stream API is available, it doesn't work if no config file exists. + if (config_path().empty()) { err_msg += " Missing cuFile configuration file."; } + + throw std::runtime_error(err_msg); + } +} + +} // namespace kvikio diff --git a/cpp/src/defaults.cpp b/cpp/src/defaults.cpp index f005e86d0b..d71f51fa68 100644 --- a/cpp/src/defaults.cpp +++ b/cpp/src/defaults.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include #include #include #include @@ -23,34 +22,12 @@ #include +#include #include #include namespace kvikio { -namespace detail { -CompatMode parse_compat_mode_str(std::string_view compat_mode_str) -{ - // Convert to lowercase - std::string tmp{compat_mode_str}; - std::transform( - tmp.begin(), tmp.end(), tmp.begin(), [](unsigned char c) { return std::tolower(c); }); - - CompatMode res{}; - if (tmp == "on" || tmp == "true" || tmp == "yes" || tmp == "1") { - res = CompatMode::ON; - } else if (tmp == "off" || tmp == "false" || tmp == "no" || tmp == "0") { - res = CompatMode::OFF; - } else if (tmp == "auto") { - res = CompatMode::AUTO; - } else { - throw std::invalid_argument("Unknown compatibility mode: " + std::string{tmp}); - } - return res; -} - -} // namespace detail - template <> bool getenv_or(std::string_view env_var_name, bool default_val) { diff --git a/cpp/src/file_handle.cpp b/cpp/src/file_handle.cpp index 0e65afb7fd..4e8376a285 100644 --- a/cpp/src/file_handle.cpp +++ b/cpp/src/file_handle.cpp @@ -21,8 +21,9 @@ #include #include #include -#include +#include +#include #include #include #include @@ -33,62 +34,28 @@ FileHandle::FileHandle(std::string const& file_path, std::string const& flags, mode_t mode, CompatMode compat_mode) - : _fd_direct_off{file_path, flags, false, mode}, _initialized{true}, _compat_mode{compat_mode} + : _initialized{true}, _compat_mode_manager{file_path, flags, mode, compat_mode, this} { - if (is_compat_mode_preferred()) { - return; // Nothing to do in compatibility mode - } - - // Try to open the file with the O_DIRECT flag. Fall back to compatibility mode, if it fails. - auto handle_o_direct_except = [this] { - if (_compat_mode == CompatMode::AUTO) { - _compat_mode = CompatMode::ON; - } else { // CompatMode::OFF - throw; - } - }; - - try { - _fd_direct_on.open(file_path, flags, true, mode); - } catch (std::system_error const&) { - handle_o_direct_except(); - } catch (std::invalid_argument const&) { - handle_o_direct_except(); - } - - if (_compat_mode == CompatMode::ON) { return; } - - CUFileHandleWrapper handle; - auto error_code = handle.register_handle(_fd_direct_on.fd()); - assert(error_code.has_value()); - - // For the AUTO mode, if the first cuFile API call fails, fall back to the compatibility - // mode. - if (_compat_mode == CompatMode::AUTO && error_code.value().err != CU_FILE_SUCCESS) { - _compat_mode = CompatMode::ON; - } else { - CUFILE_TRY(error_code.value()); - } } FileHandle::FileHandle(FileHandle&& o) noexcept - : _fd_direct_on{std::exchange(o._fd_direct_on, {})}, - _fd_direct_off{std::exchange(o._fd_direct_off, {})}, + : _file_direct_on{std::exchange(o._file_direct_on, {})}, + _file_direct_off{std::exchange(o._file_direct_off, {})}, _initialized{std::exchange(o._initialized, false)}, - _compat_mode{std::exchange(o._compat_mode, CompatMode::AUTO)}, _nbytes{std::exchange(o._nbytes, 0)}, - _cufile_handle{std::exchange(o._cufile_handle, {})} + _cufile_handle{std::exchange(o._cufile_handle, {})}, + _compat_mode_manager{std::move(o._compat_mode_manager)} { } FileHandle& FileHandle::operator=(FileHandle&& o) noexcept { - _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); - _cufile_handle = std::exchange(o._cufile_handle, {}); + _file_direct_on = std::exchange(o._file_direct_on, {}); + _file_direct_off = std::exchange(o._file_direct_off, {}); + _initialized = std::exchange(o._initialized, false); + _nbytes = std::exchange(o._nbytes, 0); + _cufile_handle = std::exchange(o._cufile_handle, {}); + _compat_mode_manager = std::move(o._compat_mode_manager); return *this; } @@ -100,11 +67,10 @@ void FileHandle::close() noexcept { try { if (closed()) { return; } - _cufile_handle.unregister_handle(); - _compat_mode = CompatMode::AUTO; - _fd_direct_off.close(); - _fd_direct_on.close(); + _file_direct_off.close(); + _file_direct_on.close(); + _nbytes = 0; _initialized = false; } catch (...) { } @@ -113,7 +79,7 @@ void FileHandle::close() noexcept CUfileHandle_t FileHandle::handle() { if (closed()) { throw CUfileException("File handle is closed"); } - if (is_compat_mode_preferred()) { + if (get_compat_mode_manager().is_compat_mode_preferred()) { throw CUfileException("The underlying cuFile handle isn't available in compatibility mode"); } return _cufile_handle.handle(); @@ -121,7 +87,7 @@ CUfileHandle_t FileHandle::handle() int FileHandle::fd(bool o_direct) const noexcept { - return o_direct ? _fd_direct_on.fd() : _fd_direct_off.fd(); + return o_direct ? _file_direct_on.fd() : _file_direct_off.fd(); } int FileHandle::fd_open_flags(bool o_direct) const { return open_flags(fd(o_direct)); } @@ -129,7 +95,7 @@ int FileHandle::fd_open_flags(bool o_direct) const { return open_flags(fd(o_dire std::size_t FileHandle::nbytes() const { if (closed()) { return 0; } - if (_nbytes == 0) { _nbytes = get_file_size(_fd_direct_off.fd()); } + if (_nbytes == 0) { _nbytes = get_file_size(_file_direct_off.fd()); } return _nbytes; } @@ -139,9 +105,9 @@ std::size_t FileHandle::read(void* devPtr_base, std::size_t devPtr_offset, bool sync_default_stream) { - if (is_compat_mode_preferred()) { + if (get_compat_mode_manager().is_compat_mode_preferred()) { return detail::posix_device_read( - _fd_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); + _file_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); } if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } @@ -163,9 +129,9 @@ std::size_t FileHandle::write(void const* devPtr_base, { _nbytes = 0; // Invalidate the computed file size - if (is_compat_mode_preferred()) { + if (get_compat_mode_manager().is_compat_mode_preferred()) { return detail::posix_device_write( - _fd_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); + _file_direct_off.fd(), devPtr_base, size, file_offset, devPtr_offset); } if (sync_default_stream) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } @@ -200,7 +166,7 @@ std::future FileHandle::pread(void* buf, std::size_t hostPtr_offset) -> std::size_t { char* buf = static_cast(hostPtr_base) + hostPtr_offset; return detail::posix_host_read( - _fd_direct_off.fd(), buf, size, file_offset); + _file_direct_off.fd(), buf, size, file_offset); }; return parallel_io(op, buf, size, file_offset, task_size, 0); @@ -212,13 +178,13 @@ 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.fd(), buf, size, file_offset, 0); + return detail::posix_device_read(_file_direct_off.fd(), buf, size, file_offset, 0); }; return std::async(std::launch::deferred, task); } // Let's synchronize once instead of in each task. - if (sync_default_stream && !is_compat_mode_preferred()) { + if (sync_default_stream && !get_compat_mode_manager().is_compat_mode_preferred()) { PushAndPopContext c(ctx); CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } @@ -250,7 +216,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.fd(), buf, size, file_offset); + _file_direct_off.fd(), buf, size, file_offset); }; return parallel_io(op, buf, size, file_offset, task_size, 0); @@ -262,13 +228,13 @@ 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.fd(), buf, size, file_offset, 0); + return detail::posix_device_write(_file_direct_off.fd(), buf, size, file_offset, 0); }; return std::async(std::launch::deferred, task); } // Let's synchronize once instead of in each task. - if (sync_default_stream && !is_compat_mode_preferred()) { + if (sync_default_stream && !get_compat_mode_manager().is_compat_mode_preferred()) { PushAndPopContext c(ctx); CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(nullptr)); } @@ -292,7 +258,8 @@ void FileHandle::read_async(void* devPtr_base, ssize_t* bytes_read_p, CUstream stream) { - if (is_compat_mode_preferred_for_async(_compat_mode)) { + get_compat_mode_manager().validate_compat_mode_for_async(); + if (get_compat_mode_manager().is_compat_mode_preferred_for_async()) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(stream)); *bytes_read_p = static_cast(read(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); @@ -324,7 +291,8 @@ void FileHandle::write_async(void* devPtr_base, ssize_t* bytes_written_p, CUstream stream) { - if (is_compat_mode_preferred_for_async(_compat_mode)) { + get_compat_mode_manager().validate_compat_mode_for_async(); + if (get_compat_mode_manager().is_compat_mode_preferred_for_async()) { CUDA_DRIVER_TRY(cudaAPI::instance().StreamSynchronize(stream)); *bytes_written_p = static_cast(write(devPtr_base, *size_p, *file_offset_p, *devPtr_offset_p)); @@ -349,34 +317,9 @@ StreamFuture FileHandle::write_async( return ret; } -bool FileHandle::is_compat_mode_preferred() const noexcept -{ - return defaults::is_compat_mode_preferred(_compat_mode); -} - -bool FileHandle::is_compat_mode_preferred_for_async() const noexcept +const CompatModeManager& FileHandle::get_compat_mode_manager() const noexcept { - static bool is_extra_symbol_available = is_stream_api_available(); - static bool is_config_path_empty = config_path().empty(); - return is_compat_mode_preferred() || !is_extra_symbol_available || is_config_path_empty; -} - -bool FileHandle::is_compat_mode_preferred_for_async(CompatMode requested_compat_mode) -{ - if (defaults::is_compat_mode_preferred(requested_compat_mode)) { return true; } - - if (!is_stream_api_available()) { - if (requested_compat_mode == CompatMode::AUTO) { return true; } - throw std::runtime_error("Missing the cuFile stream api."); - } - - // When checking for availability, we also check if cuFile's config file exists. This is - // because even when the stream API is available, it doesn't work if no config file exists. - if (config_path().empty()) { - if (requested_compat_mode == CompatMode::AUTO) { return true; } - throw std::runtime_error("Missing cuFile configuration file."); - } - return false; + return _compat_mode_manager; } } // namespace kvikio diff --git a/docs/source/runtime_settings.rst b/docs/source/runtime_settings.rst index be5508741e..0ce1ab7972 100644 --- a/docs/source/runtime_settings.rst +++ b/docs/source/runtime_settings.rst @@ -8,7 +8,7 @@ When KvikIO is running in compatibility mode, it doesn't load ``libcufile.so``. The environment variable ``KVIKIO_COMPAT_MODE`` has three options (case-insensitive): * ``ON`` (aliases: ``TRUE``, ``YES``, ``1``): Enable the compatibility mode. - * ``OFF`` (aliases: ``FALSE``, ``NO``, ``0``): Disable the compatibility mode, and enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met and cuFile is properly configured. However, if the system is not suited for cuFile, I/O operations under the ``OFF`` option may error out, crash or hang. + * ``OFF`` (aliases: ``FALSE``, ``NO``, ``0``): Disable the compatibility mode, and enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met and cuFile is properly configured. However, if the system is not suited for cuFile, I/O operations under the ``OFF`` option may error out. * ``AUTO``: Try cuFile I/O first, and fall back to POSIX I/O if the system requirements for cuFile are not met. Under ``AUTO``, KvikIO falls back to the compatibility mode: