diff --git a/CHANGELOG.md b/CHANGELOG.md index 2707832a..16c7943c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * The CMake namespace was changed from `roc::` to `hip::` * `AIS_BUILD_EXAMPLES` has been renamed to `AIS_INSTALL_EXAMPLES` * `AIS_USE_SANITIZERS` now also enables the following sanitizers: integer, float-divide-by-zero, local-bounds, vptr, nullability (in addition to address, leak, and undefined). Sanitizers should also now emit usable stack trace info. +* The AIS optimized IO path will automatically fallback to the POSIX IO path if a failure occurs and the compatibility mode has not been disabled. * Added check in the Fastpath/AIS backend to ensure the HIP Runtime is initialized. This avoids causing a segfault in the HIP Runtime. * The default CMake build type was changed from `Debug` to `RelWithDebInfo` diff --git a/src/amd_detail/CMakeLists.txt b/src/amd_detail/CMakeLists.txt index 143d078e..aca54c7b 100644 --- a/src/amd_detail/CMakeLists.txt +++ b/src/amd_detail/CMakeLists.txt @@ -7,6 +7,7 @@ set(HIPFILE_SOURCES "${HIPFILE_SRC_COMMON_PATH}/hipfile-common.cpp" async.cpp + backend.cpp backend/asyncop-fallback.cpp backend/memcpy-kernel.hip backend/fallback.cpp diff --git a/src/amd_detail/backend.cpp b/src/amd_detail/backend.cpp new file mode 100644 index 00000000..e4d3c3c8 --- /dev/null +++ b/src/amd_detail/backend.cpp @@ -0,0 +1,65 @@ +/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved. + * + * SPDX-License-Identifier: MIT + */ + +#include "backend.h" +#include "buffer.h" +#include "file.h" +#include "io.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace hipFile; + +ssize_t +Backend::io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) +{ + return _io_impl(type, file, buffer, size, file_offset, buffer_offset); +} + +ssize_t +BackendWithFallback::io(IoType type, std::shared_ptr file, std::shared_ptr buffer, + size_t size, hoff_t file_offset, hoff_t buffer_offset) +{ + ssize_t nbytes{0}; + try { + nbytes = _io_impl(type, file, buffer, size, file_offset, buffer_offset); + if (nbytes < 0) { + // Typically we should not reach this point. But in case we do, throw + // an exception to use the fallback backend. + throw std::system_error(-static_cast(nbytes), std::generic_category()); + } + } + catch (...) { + std::exception_ptr e_ptr = std::current_exception(); + if (fallback_backend && is_fallback_eligible(e_ptr, nbytes) && + fallback_backend->score(file, buffer, size, file_offset, buffer_offset) >= 0) { + nbytes = fallback_backend->io(type, file, buffer, size, file_offset, buffer_offset); + } + else { + throw; + } + return nbytes; + } + return nbytes; +} + +void +BackendWithFallback::register_fallback_backend(std::shared_ptr backend) +{ + if (!backend) { + throw std::invalid_argument("Cannot register a nullptr as a fallback backend."); + } + else if (backend.get() == this) { + throw std::invalid_argument("Cannot register a backend as it's own fallback."); + } + fallback_backend = backend; +} diff --git a/src/amd_detail/backend.h b/src/amd_detail/backend.h index aa7d970e..e35e8d6b 100644 --- a/src/amd_detail/backend.h +++ b/src/amd_detail/backend.h @@ -12,6 +12,7 @@ #include "sys.h" #include +#include #include #include #include @@ -58,11 +59,55 @@ struct Backend { /// @param file_offset Offset from the start of the file /// @param buffer_offset Offset from the start of the buffer /// - /// @return Number of bytes transferred, negative on error + /// @return Number of bytes transferred /// /// @throws Hip::RuntimeError Sys::RuntimeError virtual ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, - hoff_t file_offset, hoff_t buffer_offset) = 0; + hoff_t file_offset, hoff_t buffer_offset); + +protected: + /// @brief Perform a read or write operation + /// + /// @note Provides a common target across all Backends that provides the + /// implementation for running IO. + /// @param type IO type (read/write) + /// @param file File to read from or write to + /// @param buffer Buffer to write to or read from + /// @param size Number of bytes to transfer + /// @param file_offset Offset from the start of the file + /// @param buffer_offset Offset from the start of the buffer + /// + /// @return Number of bytes transferred + /// + /// @throws Hip::RuntimeError Sys::RuntimeError + virtual ssize_t _io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, + size_t size, hoff_t file_offset, hoff_t buffer_offset) = 0; +}; + +// BackendWithFallback allows for an IO to be retried automatically with a +// different Backend in the event of an error. +struct BackendWithFallback : public Backend { + ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) override final; + + /// @brief Register a Backend to retry a failed IO operation. + /// + /// @param backend Backend to retry a failed IO operation. + /// + /// @throws std::invalid_argument If a nullptr or self reference is passed. + void register_fallback_backend(std::shared_ptr backend); + +protected: + /// @brief Check if a failed IO operation can be re-issued to the fallback Backend. + /// + /// @param e_ptr exception_ptr to the thrown exception from the failed IO + /// @param nbytes Return value from `_io_impl`, or 0 if an exception was thrown. + /// + /// @return True if this BackendWithFallback can retry the IO, else False. + virtual bool is_fallback_eligible(std::exception_ptr e_ptr, ssize_t nbytes) const = 0; + +private: + std::shared_ptr fallback_backend; }; } diff --git a/src/amd_detail/backend/fallback.cpp b/src/amd_detail/backend/fallback.cpp index 5358eb56..13c4b036 100644 --- a/src/amd_detail/backend/fallback.cpp +++ b/src/amd_detail/backend/fallback.cpp @@ -54,14 +54,21 @@ Fallback::score(std::shared_ptr file, std::shared_ptr buffer, si ssize_t Fallback::io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, - hoff_t file_offset, hoff_t buffer_offset) + hoff_t file_offset, hoff_t buffer_offset, size_t chunk_size) { - return io(type, file, buffer, size, file_offset, buffer_offset, DefaultChunkSize); + return _io_impl(type, file, buffer, size, file_offset, buffer_offset, chunk_size); } ssize_t -Fallback::io(IoType io_type, shared_ptr file, shared_ptr buffer, size_t size, - hoff_t file_offset, hoff_t buffer_offset, size_t chunk_size) +Fallback::_io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) +{ + return _io_impl(type, file, buffer, size, file_offset, buffer_offset, DefaultChunkSize); +} + +ssize_t +Fallback::_io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset, size_t chunk_size) { if (!Context::get()->fallback()) { throw BackendDisabled(); @@ -87,7 +94,7 @@ Fallback::io(IoType io_type, shared_ptr file, shared_ptr buffer, reinterpret_cast(buffer->getBuffer()) + static_cast(buffer_offset) + static_cast(total_io_bytes)); try { - switch (io_type) { + switch (type) { case IoType::Read: io_bytes = Context::get()->pread(file->getBufferedFd(), bounce_buffer.get(), count, offset); @@ -121,16 +128,17 @@ Fallback::io(IoType io_type, shared_ptr file, shared_ptr buffer, } } while (static_cast(total_io_bytes) < size); - switch (io_type) { - case IoType::Read: + switch (type) { + case (IoType::Read): statsAddFallbackPathRead(static_cast(total_io_bytes)); break; - case IoType::Write: + case (IoType::Write): statsAddFallbackPathWrite(static_cast(total_io_bytes)); break; default: break; } + return total_io_bytes; } diff --git a/src/amd_detail/backend/fallback.h b/src/amd_detail/backend/fallback.h index 14e3b483..8b7eb359 100644 --- a/src/amd_detail/backend/fallback.h +++ b/src/amd_detail/backend/fallback.h @@ -26,24 +26,26 @@ enum class IoType; namespace hipFile { struct Fallback : public Backend { + using Backend::io; virtual ~Fallback() override = default; int score(std::shared_ptr file, std::shared_ptr buffer, size_t size, hoff_t file_offset, hoff_t buffer_offset) const override; - ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, - hoff_t file_offset, hoff_t buffer_offset) override; - void async_io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t *size_p, hoff_t *file_offset_p, hoff_t *buffer_offset_p, ssize_t *bytes_transferred_p, std::shared_ptr stream); // Once we can import gtest.h and make test suites or test friends everything // below here should be made protected. - // protected: - ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, hoff_t file_offset, hoff_t buffer_offset, size_t chunk_size); + +protected: + ssize_t _io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) override; + ssize_t _io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset, size_t chunk_size); }; } diff --git a/src/amd_detail/backend/fastpath.cpp b/src/amd_detail/backend/fastpath.cpp index 35652363..60671b21 100644 --- a/src/amd_detail/backend/fastpath.cpp +++ b/src/amd_detail/backend/fastpath.cpp @@ -13,11 +13,16 @@ #include "io.h" #include "stats.h" +#include +#include #include +#include #include #include #include +#include #include +#include using namespace hipFile; using namespace std; @@ -158,8 +163,8 @@ Fastpath::score(shared_ptr file, shared_ptr buffer, size_t size, } ssize_t -Fastpath::io(IoType type, shared_ptr file, shared_ptr buffer, size_t size, hoff_t file_offset, - hoff_t buffer_offset) +Fastpath::_io_impl(IoType type, shared_ptr file, shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) { if (!Context::get()->fastpath()) { throw BackendDisabled(); @@ -193,22 +198,38 @@ Fastpath::io(IoType type, shared_ptr file, shared_ptr buffer, si switch (type) { case IoType::Read: nbytes = Context::get()->hipAmdFileRead(handle, devptr, size, file_offset); - break; - case IoType::Write: - nbytes = Context::get()->hipAmdFileWrite(handle, devptr, size, file_offset); - break; - default: - throw std::runtime_error("Invalid IoType"); - } - switch (type) { - case IoType::Read: statsAddFastPathRead(nbytes); break; case IoType::Write: + nbytes = Context::get()->hipAmdFileWrite(handle, devptr, size, file_offset); statsAddFastPathWrite(nbytes); break; default: - break; + throw std::runtime_error("Invalid IoType"); } return static_cast(nbytes); } + +bool +Fastpath::is_fallback_eligible(std::exception_ptr e_ptr, ssize_t nbytes) const +{ + (void)nbytes; + try { + std::rethrow_exception(e_ptr); + } + catch (const std::system_error &sys_err) { + switch (sys_err.code().value()) { + case ENODEV: + return true; + case EREMOTEIO: + return true; + default: + // System error not eligible for fallback. + return false; + } + } + catch (...) { + // Thrown exception not eligible for fallback. + return false; + } +} diff --git a/src/amd_detail/backend/fastpath.h b/src/amd_detail/backend/fastpath.h index 61f0915e..b13a6b29 100644 --- a/src/amd_detail/backend/fastpath.h +++ b/src/amd_detail/backend/fastpath.h @@ -6,9 +6,13 @@ #pragma once #include "backend.h" +#include "buffer.h" +#include "file.h" #include "hipfile.h" +#include #include +#include #include namespace hipFile { @@ -23,14 +27,16 @@ enum class IoType; namespace hipFile { -struct Fastpath : public Backend { +struct Fastpath : public BackendWithFallback { virtual ~Fastpath() override = default; int score(std::shared_ptr file, std::shared_ptr buffer, size_t size, hoff_t file_offset, hoff_t buffer_offset) const override; - ssize_t io(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, - hoff_t file_offset, hoff_t buffer_offset) override; +protected: + ssize_t _io_impl(IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset) override; + bool is_fallback_eligible(std::exception_ptr e_ptr, ssize_t nbytes) const override; }; } diff --git a/src/amd_detail/state.cpp b/src/amd_detail/state.cpp index f65c09db..44a08cca 100644 --- a/src/amd_detail/state.cpp +++ b/src/amd_detail/state.cpp @@ -275,8 +275,11 @@ std::vector> DriverState::getBackends() const { static bool once = [&]() { - backends.emplace_back(new Fastpath{}); - backends.emplace_back(new Fallback{}); + std::shared_ptr fallback_backend = std::make_shared(); + std::shared_ptr fastpath_backend = std::make_shared(); + fastpath_backend->register_fallback_backend(fallback_backend); + backends.push_back(fallback_backend); + backends.push_back(fastpath_backend); return true; }(); (void)once; diff --git a/test/amd_detail/CMakeLists.txt b/test/amd_detail/CMakeLists.txt index adf04724..8b20c4cd 100644 --- a/test/amd_detail/CMakeLists.txt +++ b/test/amd_detail/CMakeLists.txt @@ -12,6 +12,7 @@ set(SHARED_SOURCE_FILES set(TEST_SOURCE_FILES async.cpp + backend.cpp batch/batch.cpp configuration.cpp context.cpp diff --git a/test/amd_detail/backend.cpp b/test/amd_detail/backend.cpp new file mode 100644 index 00000000..67db17d7 --- /dev/null +++ b/test/amd_detail/backend.cpp @@ -0,0 +1,127 @@ +/* Copyright (c) Advanced Micro Devices, Inc. All rights reserved. + * + * SPDX-License-Identifier: MIT + */ + +#include "io.h" +#include "hipfile-test.h" +#include "hipfile-warnings.h" +#include "mbackend.h" +#include "mbuffer.h" +#include "mfile.h" + +#include +#include +#include + +using namespace hipFile; + +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::StrictMock; +using ::testing::Throw; + +// Put tests inside the macros to suppress the global constructor +// warnings +HIPFILE_WARN_NO_GLOBAL_CTOR_OFF + +struct DummyBackendWithFallback : public MBackendWithFallback {}; + +struct DummyFallbackBackend : MBackend {}; + +struct HipFileBackendWithFallback : public HipFileUnopened, ::testing::WithParamInterface { + std::shared_ptr> mock_buffer; + std::shared_ptr> mock_file; + + std::shared_ptr> default_backend; + std::shared_ptr> fallback_backend; + + IoType io_type; + ssize_t successful_io_size = 0x1234; + + void SetUp() override + { + mock_buffer = std::make_shared>(); + mock_file = std::make_shared>(); + + default_backend = std::make_shared>(); + EXPECT_CALL(*default_backend, _io_impl).Times(AnyNumber()); + EXPECT_CALL(*default_backend, is_fallback_eligible).WillRepeatedly(Return(true)); + + fallback_backend = std::make_shared>(); + EXPECT_CALL(*fallback_backend, io).Times(AnyNumber()); + EXPECT_CALL(*fallback_backend, score).WillRepeatedly(Return(0)); + + io_type = GetParam(); + } + + HipFileBackendWithFallback() + { + } +}; + +TEST_P(HipFileBackendWithFallback, IOSuccess) +{ + EXPECT_CALL(*default_backend, _io_impl).WillOnce(Return(successful_io_size)); + + ssize_t nbytes = default_backend->io(io_type, mock_file, mock_buffer, 0, 0, 0); + + ASSERT_EQ(nbytes, successful_io_size); +} + +TEST_P(HipFileBackendWithFallback, IOFailureWithNoRegisteredFallback) +{ + EXPECT_CALL(*default_backend, _io_impl).WillOnce(Throw(std::runtime_error("IO failure"))); + + EXPECT_THROW(default_backend->io(io_type, mock_file, mock_buffer, 0, 0, 0), std::runtime_error); +} + +TEST_P(HipFileBackendWithFallback, IOFailureWithRegisteredFallbackButNotFallbackEligible) +{ + // The Backend has registered a fallback, but the BackendWithFallback has + // determined based on the error that this IO should not be retried. + default_backend->register_fallback_backend(fallback_backend); + EXPECT_CALL(*default_backend, _io_impl).WillOnce(Throw(std::runtime_error("IO failure"))); + EXPECT_CALL(*default_backend, is_fallback_eligible).WillOnce(Return(false)); + + EXPECT_THROW(default_backend->io(io_type, mock_file, mock_buffer, 0, 0, 0), std::runtime_error); +} + +TEST_P(HipFileBackendWithFallback, IOFailureWithRegisteredFallbackRefusingTheIO) +{ + // The Backend has registered a fallback, but the fallback backend has + // determined that it cannot process the IO. + default_backend->register_fallback_backend(fallback_backend); + EXPECT_CALL(*default_backend, _io_impl).WillOnce(Throw(std::runtime_error("IO failure"))); + EXPECT_CALL(*fallback_backend, score).WillOnce(Return(-1)); + + EXPECT_THROW(default_backend->io(io_type, mock_file, mock_buffer, 0, 0, 0), std::runtime_error); +} + +TEST_P(HipFileBackendWithFallback, IOFailureWithGoodFallback) +{ + default_backend->register_fallback_backend(fallback_backend); + EXPECT_CALL(*default_backend, _io_impl).WillOnce(Throw(std::runtime_error("IO failure"))); + EXPECT_CALL(*fallback_backend, io).WillOnce(Return(successful_io_size)); + + ssize_t nbytes = default_backend->io(io_type, mock_file, mock_buffer, 0, 0, 0); + ASSERT_EQ(nbytes, successful_io_size); +} + +INSTANTIATE_TEST_SUITE_P(, HipFileBackendWithFallback, ::testing::Values(IoType::Read, IoType::Write)); + +struct HipFileBackendWithFallbackRegisterFallback : public HipFileUnopened {}; + +TEST_F(HipFileBackendWithFallbackRegisterFallback, RegisterNullptrFallback) +{ + auto backend = std::make_shared(); + ASSERT_THROW(backend->register_fallback_backend(nullptr), std::invalid_argument); +} + +TEST_F(HipFileBackendWithFallbackRegisterFallback, RegisterSelfAsAFallback) +{ + auto backend = std::make_shared(); + ASSERT_THROW(backend->register_fallback_backend(backend), std::invalid_argument); +} + +HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/fastpath.cpp b/test/amd_detail/fastpath.cpp index 06098b70..d02aa31c 100644 --- a/test/amd_detail/fastpath.cpp +++ b/test/amd_detail/fastpath.cpp @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ +#include "backend/fallback.h" #include "backend/fastpath.h" #include "hip.h" #include "hipfile.h" @@ -11,12 +12,15 @@ #include "io.h" #include "mbuffer.h" #include "mconfiguration.h" +#include "mbackend.h" #include "mfile.h" #include "mhip.h" +#include "msys.h" #include #include #include +#include #include #include #include @@ -26,6 +30,7 @@ #include #include #include +#include using namespace hipFile; using namespace testing; @@ -597,6 +602,170 @@ TEST_P(FastpathIoParam, IoSizeIsTruncatedToMaxRWCount) ASSERT_EQ(Fastpath().io(GetParam(), mfile, mbuffer, io_size, 0, 0), MAX_RW_COUNT); } +// Note: Tests for fallback eligible exceptions are further down this file +// in a separate test suite. +TEST_P(FastpathIoParam, IoWithFallbackThrowsAFallbackIneligibleException) +{ + auto backend = std::make_shared(); + auto m_fallback = std::make_shared>(); + backend->register_fallback_backend(m_fallback); + + EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); + EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); + EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(std::system_error(EBADFD, generic_category()))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(std::system_error(EBADFD, generic_category()))); + break; + default: + FAIL() << "Invalid IoType"; + } + + ASSERT_THROW( + backend->io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + std::system_error); +} + +// To cover the branch of non-std::system_error exceptions +TEST_P(FastpathIoParam, IoWithFallbackThrowsHipRuntimeException) +{ + auto backend = std::make_shared(); + auto m_fallback = std::make_shared>(); + backend->register_fallback_backend(m_fallback); + + EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); + EXPECT_CALL(mhip, hipInit).WillOnce(Return()); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); + EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(Hip::RuntimeError(hipErrorUnknown))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(Hip::RuntimeError(hipErrorUnknown))); + break; + default: + FAIL() << "Invalid IoType"; + } + + ASSERT_THROW( + backend->io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET), + Hip::RuntimeError); +} + +TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleENODEV) +{ + auto backend = std::make_shared(); + auto m_fallback = std::make_shared>(); + backend->register_fallback_backend(m_fallback); + + EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); + EXPECT_CALL(mhip, hipInit).WillOnce(Return()); + EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(std::system_error(ENODEV, generic_category()))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(std::system_error(ENODEV, generic_category()))); + break; + default: + FAIL() << "Invalid IoType"; + } + + EXPECT_CALL(*m_fallback, io).WillOnce(Return(DEFAULT_IO_SIZE)); + EXPECT_CALL(*m_fallback, score).WillOnce(Return(SCORE_ACCEPT)); + + ssize_t nbytes = + backend->io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET); + ASSERT_EQ(nbytes, DEFAULT_IO_SIZE); +} + +TEST_P(FastpathIoParam, IoThrowsAFallbackEligibleEREMOTEIO) +{ + auto backend = std::make_shared(); + auto m_fallback = std::make_shared>(); + backend->register_fallback_backend(m_fallback); + + EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); + EXPECT_CALL(mhip, hipInit).WillOnce(Return()); + EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead) + .WillOnce(Throw(std::system_error(EREMOTEIO, generic_category()))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite) + .WillOnce(Throw(std::system_error(EREMOTEIO, generic_category()))); + break; + default: + FAIL() << "Invalid IoType"; + } + + EXPECT_CALL(*m_fallback, io).WillOnce(Return(DEFAULT_IO_SIZE)); + EXPECT_CALL(*m_fallback, score).WillOnce(Return(SCORE_ACCEPT)); + + ssize_t nbytes = + backend->io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET); + ASSERT_EQ(nbytes, DEFAULT_IO_SIZE); +} + +// If an IO is marked as fallback eligible, but the fallback backend +// still rejects the IO request, the original exception should still +// be raised. +// This test in particular also checks that the errno returned is the same. +TEST_P(FastpathIoParam, FallbackRejectsIoRequest) +{ + auto backend = std::make_shared(); + auto m_fallback = std::make_shared>(); + backend->register_fallback_backend(m_fallback); + + EXPECT_CALL(mcfg, fastpath()).WillOnce(Return(true)); + EXPECT_CALL(mhip, hipInit).WillRepeatedly(Return()); + EXPECT_CALL(*mbuffer, getBuffer).WillOnce(Return(DEFAULT_BUFFER_ADDR)); + EXPECT_CALL(*mbuffer, getLength).WillOnce(Return(DEFAULT_BUFFER_LENGTH)); + EXPECT_CALL(*mfile, getUnbufferedFd).WillOnce(Return(DEFAULT_UNBUFFERED_FD)); + EXPECT_CALL(*m_fallback, score).WillOnce(Return(SCORE_REJECT)); + + switch (GetParam()) { + case IoType::Read: + EXPECT_CALL(mhip, hipAmdFileRead).WillOnce(Throw(std::system_error(ENODEV, generic_category()))); + break; + case IoType::Write: + EXPECT_CALL(mhip, hipAmdFileWrite).WillOnce(Throw(std::system_error(ENODEV, generic_category()))); + break; + default: + FAIL() << "Invalid IoType"; + } + + try { + backend->io(GetParam(), mfile, mbuffer, DEFAULT_IO_SIZE, DEFAULT_FILE_OFFSET, DEFAULT_BUFFER_OFFSET); + FAIL() << "io() was expected to throw, but it returned normally"; + } + catch (const std::system_error &actual_exc) { + ASSERT_EQ(typeid(std::system_error), typeid(actual_exc)); + ASSERT_EQ(ENODEV, actual_exc.code().value()); + } + catch (...) { + FAIL() << "io() threw something other than a std::system_error"; + } +} + INSTANTIATE_TEST_SUITE_P(FastpathTest, FastpathIoParam, Values(IoType::Read, IoType::Write)); HIPFILE_WARN_NO_GLOBAL_CTOR_ON diff --git a/test/amd_detail/mbackend.h b/test/amd_detail/mbackend.h index f4c395df..677c8be1 100644 --- a/test/amd_detail/mbackend.h +++ b/test/amd_detail/mbackend.h @@ -6,7 +6,11 @@ #pragma once #include "backend.h" +#include "buffer.h" +#include "file.h" +#include +#include #include namespace hipFile { @@ -18,6 +22,19 @@ struct MBackend : Backend { (hipFile::IoType type, std::shared_ptr, std::shared_ptr, size_t, hoff_t, hoff_t), (override)); + MOCK_METHOD(ssize_t, _io_impl, + (hipFile::IoType type, std::shared_ptr, std::shared_ptr, size_t, hoff_t, + hoff_t), + (override)); }; +struct MBackendWithFallback : BackendWithFallback { + MOCK_METHOD(int, score, (std::shared_ptr, std::shared_ptr, size_t, hoff_t, hoff_t), + (const, override)); + MOCK_METHOD(ssize_t, _io_impl, + (IoType type, std::shared_ptr file, std::shared_ptr buffer, size_t size, + hoff_t file_offset, hoff_t buffer_offset), + (override)); + MOCK_METHOD(bool, is_fallback_eligible, (std::exception_ptr e_ptr, ssize_t nbytes), (const, override)); +}; }