diff --git a/cmake/VSAGOptions.cmake b/cmake/VSAGOptions.cmake index 74d210a112..c81d30feba 100644 --- a/cmake/VSAGOptions.cmake +++ b/cmake/VSAGOptions.cmake @@ -34,6 +34,7 @@ option (ENABLE_PYBINDS "Whether compile Python bindings" OFF) option (ENABLE_NODE_BINDS "Whether compile Node.js bindings" OFF) option (ENABLE_MOCKIMPL "Whether compile mock implementation" OFF) option (ENABLE_LIBAIO "Whether to enable libaio support" ON) +option (ENABLE_LIBURING "Whether to enable liburing support" ON) option (ENABLE_SOVERSION "Whether to set SO version on the shared library" ON) option (DISABLE_SSE_FORCE "Force disable sse and higher instructions" OFF) option (DISABLE_AVX_FORCE "Force disable avx and higher instructions" OFF) diff --git a/cmake/VSAGSystemDeps.cmake b/cmake/VSAGSystemDeps.cmake index b34bd35246..ae9c349b6f 100644 --- a/cmake/VSAGSystemDeps.cmake +++ b/cmake/VSAGSystemDeps.cmake @@ -15,7 +15,12 @@ include_guard (GLOBAL) set (HAVE_LIBAIO 0) +set (HAVE_LIBURING 0) unset (AIO_LIBRARY) +unset (URING_LIBRARY) +unset (URING_LIBRARY CACHE) +unset (URING_INCLUDE_DIR) +unset (URING_INCLUDE_DIR CACHE) if (ENABLE_LIBAIO) find_library (AIO_LIBRARY aio) @@ -34,6 +39,32 @@ if (NOT HAVE_LIBAIO) add_compile_definitions (NO_LIBAIO=1) endif () +if (ENABLE_LIBURING) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_library (URING_LIBRARY uring) + find_path (URING_INCLUDE_DIR liburing.h) + if (URING_LIBRARY AND URING_INCLUDE_DIR) + set (HAVE_LIBURING 1) + message (STATUS "Found liburing: ${URING_LIBRARY}, include: ${URING_INCLUDE_DIR}") + else () + message (WARNING "liburing not found, disabling io_uring support") + unset (URING_LIBRARY) + unset (URING_LIBRARY CACHE) + unset (URING_INCLUDE_DIR) + unset (URING_INCLUDE_DIR CACHE) + endif () + else () + message (STATUS "liburing support is only available on Linux") + endif () +else () + message (STATUS "liburing support disabled by user") +endif () + +add_compile_definitions (HAVE_LIBURING=${HAVE_LIBURING}) +if (NOT HAVE_LIBURING) + add_compile_definitions (NO_LIBURING=1) +endif () + find_program (CCACHE_PROGRAM ccache) if (ENABLE_CCACHE) if (CCACHE_PROGRAM) diff --git a/src/datacell/bucket_interface.cpp b/src/datacell/bucket_interface.cpp index 7092462e55..dda3994d4c 100644 --- a/src/datacell/bucket_interface.cpp +++ b/src/datacell/bucket_interface.cpp @@ -20,6 +20,7 @@ #include "bucket_datacell.h" #include "inner_string_params.h" #include "io/io_headers.h" +#include "io/uring_io/uring_io.h" #include "quantization/quantizer_headers.h" namespace vsag { @@ -109,6 +110,9 @@ BucketInterface::MakeInstance(const BucketDataCellParamPtr& param, if (io_type_name == IO_TYPE_VALUE_ASYNC_IO) { return make_instance>(param, common_param); } + if (io_type_name == IO_TYPE_VALUE_URING_IO) { + return make_instance>(param, common_param); + } if (io_type_name == IO_TYPE_VALUE_BUFFER_IO) { return make_instance>(param, common_param); } diff --git a/src/datacell/flatten_interface.cpp b/src/datacell/flatten_interface.cpp index d37e2fb3bd..0022307b15 100644 --- a/src/datacell/flatten_interface.cpp +++ b/src/datacell/flatten_interface.cpp @@ -18,6 +18,7 @@ #include "index_common_param.h" #include "inner_string_params.h" #include "io/io_headers.h" +#include "io/uring_io/uring_io.h" #include "multi_vector_datacell.h" #include "quantization/int8_quantizer.h" #include "quantization/quantizer_adapter.h" @@ -297,6 +298,9 @@ FlattenInterface::MakeInstance(const FlattenInterfaceParamPtr& param, if (io_type_name == IO_TYPE_VALUE_ASYNC_IO) { return make_instance(param, common_param); } + if (io_type_name == IO_TYPE_VALUE_URING_IO) { + return make_instance(param, common_param); + } if (io_type_name == IO_TYPE_VALUE_MMAP_IO) { return make_instance(param, common_param); } diff --git a/src/datacell/graph_interface.cpp b/src/datacell/graph_interface.cpp index a2a99db69e..b07cbd91a8 100644 --- a/src/datacell/graph_interface.cpp +++ b/src/datacell/graph_interface.cpp @@ -18,6 +18,7 @@ #include "compressed_graph_datacell.h" #include "graph_datacell.h" #include "io/io_headers.h" +#include "io/uring_io/uring_io.h" #include "sparse_graph_datacell.h" namespace vsag { @@ -74,6 +75,9 @@ GraphInterface::MakeInstance(const GraphInterfaceParamPtr& graph_param, if (io_string == IO_TYPE_VALUE_ASYNC_IO) { return std::make_shared>(graph_param, common_param); } + if (io_string == IO_TYPE_VALUE_URING_IO) { + return std::make_shared>(graph_param, common_param); + } if (io_string == IO_TYPE_VALUE_READER_IO) { return std::make_shared>(graph_param, common_param); } diff --git a/src/datacell/rabitq_split_datacell.h b/src/datacell/rabitq_split_datacell.h index 8c83187bcb..2056b60c92 100644 --- a/src/datacell/rabitq_split_datacell.h +++ b/src/datacell/rabitq_split_datacell.h @@ -726,7 +726,8 @@ class RaBitQSplitDataCell : public FlattenInterface { IsKnownIOType(const std::string& io_type) { return io_type == IO_TYPE_VALUE_MEMORY_IO or io_type == IO_TYPE_VALUE_BUFFER_IO or io_type == IO_TYPE_VALUE_MMAP_IO or io_type == IO_TYPE_VALUE_READER_IO or - io_type == IO_TYPE_VALUE_ASYNC_IO or io_type == IO_TYPE_VALUE_BLOCK_MEMORY_IO; + io_type == IO_TYPE_VALUE_ASYNC_IO or io_type == IO_TYPE_VALUE_URING_IO or + io_type == IO_TYPE_VALUE_BLOCK_MEMORY_IO; } void diff --git a/src/inner_string_params.h b/src/inner_string_params.h index e4aff1ea2d..4422ca81b4 100644 --- a/src/inner_string_params.h +++ b/src/inner_string_params.h @@ -62,6 +62,7 @@ const char* const IO_TYPE_VALUE_BUFFER_IO = "buffer_io"; const char* const IO_TYPE_VALUE_MMAP_IO = "mmap_io"; const char* const IO_TYPE_VALUE_READER_IO = "reader_io"; const char* const IO_TYPE_VALUE_ASYNC_IO = "async_io"; +const char* const IO_TYPE_VALUE_URING_IO = "uring_io"; const char* const IO_TYPE_VALUE_BLOCK_MEMORY_IO = "block_memory_io"; const char* const BLOCK_IO_BLOCK_SIZE_KEY = "block_size"; diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index fa6dd3203f..091dcec30c 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -23,6 +23,8 @@ set (IO_SRC buffer_io/buffer_io.cpp async_io/async_io_parameter.cpp async_io/async_io.cpp + uring_io/uring_io_parameter.cpp + uring_io/uring_io.cpp mmap_io/mmap_io_parameter.cpp mmap_io/mmap_io.cpp memory_block_io/memory_block_io.cpp @@ -31,14 +33,28 @@ set (IO_SRC ) add_library (io OBJECT ${IO_SRC}) + +set (IO_LINK_LIBRARIES coverage_config vsag_src_common) + if (HAVE_LIBAIO) - target_link_libraries (io PRIVATE aio coverage_config vsag_src_common) + list (APPEND IO_LINK_LIBRARIES aio) message (STATUS "io module: linking with libaio") else () - target_link_libraries (io PRIVATE coverage_config vsag_src_common) message (STATUS "io module: libaio not available, async I/O disabled") endif () +if (HAVE_LIBURING) + list (APPEND IO_LINK_LIBRARIES ${URING_LIBRARY}) + message (STATUS "io module: linking with liburing") +else () + message (STATUS "io module: liburing not available, io_uring disabled") +endif () + +target_link_libraries (io PRIVATE ${IO_LINK_LIBRARIES}) +if (HAVE_LIBURING) + target_include_directories (io PRIVATE ${URING_INCLUDE_DIR}) +endif () + if (ENABLE_TESTS) file (GLOB_RECURSE IO_TESTS "*_test.cpp") add_library (io_test STATIC ${IO_TESTS}) diff --git a/src/io/common/io_parameter.cpp b/src/io/common/io_parameter.cpp index b38060d517..3c97a5df09 100644 --- a/src/io/common/io_parameter.cpp +++ b/src/io/common/io_parameter.cpp @@ -25,11 +25,13 @@ #include "io/memory_io/memory_io_parameter.h" #include "io/mmap_io/mmap_io_parameter.h" #include "io/reader_io/reader_io_parameter.h" +#include "io/uring_io/uring_io_parameter.h" namespace vsag { namespace { std::once_flag async_io_fallback_warn_once; +std::once_flag uring_io_fallback_warn_once; } // namespace IOParamPtr @@ -54,6 +56,16 @@ IOParameter::GetIOParameterByJson(const JsonType& json) { logger::warn("libaio is unavailable, async_io is falling back to buffer_io"); }); io_ptr = std::make_shared(); +#endif + io_ptr->FromJson(json); + } else if (type_name == IO_TYPE_VALUE_URING_IO) { +#if HAVE_LIBURING + io_ptr = std::make_shared(); +#else + std::call_once(uring_io_fallback_warn_once, []() { + logger::warn("liburing is unavailable, uring_io is falling back to buffer_io"); + }); + io_ptr = std::make_shared(); #endif io_ptr->FromJson(json); } else if (type_name == IO_TYPE_VALUE_MMAP_IO) { diff --git a/src/io/uring_io/uring_io.cpp b/src/io/uring_io/uring_io.cpp new file mode 100644 index 0000000000..01c2c2a94c --- /dev/null +++ b/src/io/uring_io/uring_io.cpp @@ -0,0 +1,360 @@ + +// Copyright 2024-present the vsag project +// +// 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. + +#if HAVE_LIBURING + +#include "io/uring_io/uring_io.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "io/common/io_syscall.h" + +namespace vsag { + +std::unique_ptr UringIO::io_context_pool_ = + std::make_unique(0, nullptr); + +UringIOContext::UringIOContext() { + ring_ = new io_uring; + int ret = io_uring_queue_init(RING_SIZE, ring_, 0); + if (ret < 0) { + delete ring_; + ring_ = nullptr; + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("io_uring_queue_init failed: {}", strerror(-ret))); + } +} + +UringIOContext::~UringIOContext() { + if (ring_ != nullptr) { + io_uring_queue_exit(ring_); + delete ring_; + ring_ = nullptr; + } +} + +uint64_t +UringIOContext::GetMemoryUsage() const { + return sizeof(UringIOContext) + RING_SIZE * sizeof(struct io_uring_sqe) + + RING_SIZE * sizeof(struct io_uring_cqe); +} + +io_uring* +UringIOContext::ring() { + return ring_; +} + +UringIO::UringIO(std::string filename, Allocator* allocator) + : BasicIO(allocator), filepath_(std::move(filename)) { + this->exist_file_ = std::filesystem::exists(this->filepath_); + if (std::filesystem::is_directory(this->filepath_)) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("{} is a directory", this->filepath_)); + } + this->rfd_ = open(filepath_.c_str(), O_CREAT | O_RDWR, 0644); + if (this->rfd_ < 0) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("open file {} error {}", this->filepath_, strerror(errno))); + } + this->wfd_ = open(filepath_.c_str(), O_CREAT | O_RDWR, 0644); + if (this->wfd_ < 0) { + close(this->rfd_); + if (not this->exist_file_) { + std::filesystem::remove(this->filepath_); + } + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("open file {} error {}", this->filepath_, strerror(errno))); + } +} + +UringIO::UringIO(const UringIOParameterPtr& io_param, const IndexCommonParam& common_param) + : UringIO(io_param->path_, common_param.allocator_.get()) { +} + +UringIO::UringIO(const IOParamPtr& param, const IndexCommonParam& common_param) + : UringIO(std::dynamic_pointer_cast(param), common_param) { +} + +UringIO::~UringIO() { + close(this->wfd_); + close(this->rfd_); + // remove file + if (not this->exist_file_) { + std::filesystem::remove(this->filepath_); + } +} + +void +UringIO::WriteImpl(const uint8_t* data, uint64_t size, uint64_t offset) { + auto ret = IOSyscall::PWrite(this->wfd_, data, size, offset); + if (ret != static_cast(size)) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("write bytes {} less than {}", ret, size)); + } + if (size + offset > this->size_) { + this->size_ = size + offset; + } +} + +void +UringIO::ResizeImpl(uint64_t size) { + auto ret = IOSyscall::FTruncate(this->wfd_, size); + if (ret == -1) { + throw VsagException(ErrorType::INTERNAL_ERROR, "ftruncate failed"); + } + this->size_ = size; +} + +bool +UringIO::ReadImpl(uint64_t size, uint64_t offset, uint8_t* data) const { + if (not check_valid_offset(size + offset)) { + return false; + } + if (size == 0) { + return true; + } + auto ret = IOSyscall::PRead(this->rfd_, data, size, offset); + if (ret != static_cast(size)) { + if (ret < 0) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("pread error {}", strerror(errno))); + } + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("read bytes {} less than {}", ret, size)); + } + return true; +} + +const uint8_t* +UringIO::DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const { + need_release = false; + if (not check_valid_offset(size + offset)) { + return nullptr; + } + if (size == 0) { + return nullptr; + } + need_release = true; + auto* data = static_cast(malloc(size)); + if (data == nullptr) { + throw VsagException(ErrorType::NO_ENOUGH_MEMORY, "UringIO allocation failed"); + } + auto ret = IOSyscall::PRead(this->rfd_, data, size, offset); + if (ret != static_cast(size)) { + free(data); + if (ret < 0) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("pread error {}", strerror(errno))); + } + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("read bytes {} less than {}", ret, size)); + } + return data; +} + +void +UringIO::ReleaseImpl(const uint8_t* data) { + free(const_cast(data)); +} + +class UringReadObject { +public: + UringReadObject() = default; + + void + Set(uint64_t size1, uint64_t offset1, uint8_t* dist_data1) { + this->data = dist_data1; + this->size = size1; + this->offset = offset1; + this->dist_data = dist_data1; + } + + uint8_t* data{nullptr}; + uint64_t size{0}; + uint64_t offset{0}; + uint8_t* dist_data{nullptr}; + bool released{true}; +}; + +static void +ReleaseUringReadObject(UringReadObject& obj) { + if (not obj.released) { + obj.data = nullptr; + obj.released = true; + } +} + +class UringIOContextGuard { +public: + explicit UringIOContextGuard(std::shared_ptr ctx) : ctx_(std::move(ctx)) { + } + + ~UringIOContextGuard() { + if (ctx_ != nullptr) { + UringIO::io_context_pool_->ReturnOne(ctx_); + } + } + + UringIOContextGuard(const UringIOContextGuard&) = delete; + UringIOContextGuard& + operator=(const UringIOContextGuard&) = delete; + + void + Abandon() { + // Keep the context alive but do not return it to the pool when the ring + // may still have outstanding kernel references after a fatal wait error. + // The retained contexts intentionally live until process exit; this path + // only runs on exceptional io_uring failures, so the leak is bounded. + static std::mutex abandon_mutex; + static std::vector> abandoned; + std::lock_guard lock(abandon_mutex); + abandoned.push_back(std::move(ctx_)); + ctx_ = nullptr; + } + +private: + std::shared_ptr ctx_{nullptr}; +}; + +bool +UringIO::MultiReadImpl(uint8_t* datas, uint64_t* sizes, uint64_t* offsets, uint64_t count) const { + if (count == 0) { + return true; + } + + for (uint64_t i = 0; i < count; ++i) { + if (not check_valid_offset(sizes[i] + offsets[i])) { + return false; + } + } + + std::shared_ptr ctx; + try { + ctx = io_context_pool_->TakeOne(); + } catch (const VsagException&) { + for (uint64_t i = 0; i < count; ++i) { + if (not this->ReadImpl(sizes[i], offsets[i], datas)) { + return false; + } + datas += sizes[i]; + } + return true; + } + UringIOContextGuard ctx_guard(ctx); + auto* ring = ctx->ring(); + + auto all_count = count; + + while (all_count > 0) { + count = std::min(static_cast(UringIOContext::RING_SIZE), all_count); + std::vector objs(count); + + try { + for (uint64_t i = 0; i < count; ++i) { + objs[i].Set(sizes[i], offsets[i], datas); + objs[i].released = false; + datas += sizes[i]; + + struct io_uring_sqe* sqe = io_uring_get_sqe(ring); + if (!sqe) { + throw VsagException(ErrorType::INTERNAL_ERROR, + "io_uring_get_sqe failed in multi-read"); + } + io_uring_prep_read(sqe, this->rfd_, objs[i].data, objs[i].size, offsets[i]); + sqe->user_data = reinterpret_cast(&objs[i]); + } + } catch (...) { + for (auto& obj : objs) { + ReleaseUringReadObject(obj); + } + ctx_guard.Abandon(); + throw; + } + + int submitted = io_uring_submit(ring); + if (submitted < 0) { + for (auto& obj : objs) { + ReleaseUringReadObject(obj); + } + ctx_guard.Abandon(); + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("io_uring_submit failed: {}", strerror(-submitted))); + } + + int first_error = 0; + bool has_short_read = false; + uint64_t completed = 0; + while (completed < static_cast(submitted)) { + struct io_uring_cqe* cqe; + int ret = io_uring_wait_cqe(ring, &cqe); + if (ret < 0) { + if (ret == -EINTR) { + continue; + } + ctx_guard.Abandon(); + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("io_uring_wait_cqe failed: {}", strerror(-ret))); + } + + auto* obj = reinterpret_cast(cqe->user_data); + if (cqe->res < 0) { + first_error = first_error == 0 ? cqe->res : first_error; + } else if (cqe->res != static_cast(obj->size)) { + has_short_read = true; + } + + ReleaseUringReadObject(*obj); + completed++; + + io_uring_cqe_seen(ring, cqe); + } + + if (submitted != static_cast(count)) { + for (auto& obj : objs) { + ReleaseUringReadObject(obj); + } + ctx_guard.Abandon(); + throw VsagException( + ErrorType::INTERNAL_ERROR, + fmt::format( + "io_uring_submit partial: requested {} but submitted {}", count, submitted)); + } + + if (first_error < 0) { + throw VsagException(ErrorType::INTERNAL_ERROR, + fmt::format("multi-read failed: {}", strerror(-first_error))); + } + if (has_short_read) { + throw VsagException(ErrorType::INTERNAL_ERROR, "multi-read short read"); + } + sizes += count; + offsets += count; + all_count -= count; + } + + return true; +} + +} // namespace vsag + +#endif // HAVE_LIBURING diff --git a/src/io/uring_io/uring_io.h b/src/io/uring_io/uring_io.h new file mode 100644 index 0000000000..cef5eb7683 --- /dev/null +++ b/src/io/uring_io/uring_io.h @@ -0,0 +1,78 @@ + +// Copyright 2024-present the vsag project +// +// 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 + +#if HAVE_LIBURING +#include "index_common_param.h" +#include "io/common/basic_io.h" +#include "io/uring_io/uring_io_context.h" +#include "io/uring_io/uring_io_parameter.h" +namespace vsag { + +class UringIO : public BasicIO { +public: + static constexpr bool InMemory = false; + static constexpr bool SkipDeserialize = false; + +public: + explicit UringIO(std::string filename, Allocator* allocator); + + explicit UringIO(const UringIOParameterPtr& io_param, const IndexCommonParam& common_param); + + explicit UringIO(const IOParamPtr& param, const IndexCommonParam& common_param); + + ~UringIO() override; + +public: + void + WriteImpl(const uint8_t* data, uint64_t size, uint64_t offset); + + void + ResizeImpl(uint64_t size); + + bool + ReadImpl(uint64_t size, uint64_t offset, uint8_t* data) const; + + [[nodiscard]] const uint8_t* + DirectReadImpl(uint64_t size, uint64_t offset, bool& need_release) const; + + static void + ReleaseImpl(const uint8_t* data); + + bool + MultiReadImpl(uint8_t* datas, uint64_t* sizes, uint64_t* offsets, uint64_t count) const; + +public: + static std::unique_ptr io_context_pool_; + +private: + std::string filepath_{}; + + int rfd_{-1}; + + int wfd_{-1}; + + bool exist_file_{false}; +}; + +} // namespace vsag + +#else +#include "io/buffer_io/buffer_io.h" +namespace vsag { +using UringIO = BufferIO; +} // namespace vsag +#endif // HAVE_LIBURING diff --git a/src/io/uring_io/uring_io_context.h b/src/io/uring_io/uring_io_context.h new file mode 100644 index 0000000000..14a21d4d31 --- /dev/null +++ b/src/io/uring_io/uring_io_context.h @@ -0,0 +1,59 @@ +// Copyright 2024-present the vsag project +// +// 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 +#if HAVE_LIBURING + +#include + +#include +#include + +#include "utils/resource_object.h" +#include "utils/resource_object_pool.h" +#include "vsag_exception.h" + +struct io_uring; + +namespace vsag { + +class UringIOContext : public ResourceObject { +public: + static constexpr uint32_t RING_SIZE = 512; + + UringIOContext(); + ~UringIOContext() override; + + void + Reset() override { + } + + uint64_t + GetMemoryUsage() const override; + + io_uring* + ring(); + +private: + // Pointer storage + forward declaration keeps liburing.h (and its heavy + // Linux kernel uapi headers) out of every TU that only includes this + // header. The ring is allocated/freed in uring_io.cpp. + io_uring* ring_{nullptr}; +}; + +using UringIOContextPool = ResourceObjectPool; + +} // namespace vsag + +#endif // HAVE_LIBURING diff --git a/src/io/uring_io/uring_io_parameter.cpp b/src/io/uring_io/uring_io_parameter.cpp new file mode 100644 index 0000000000..954f2e8357 --- /dev/null +++ b/src/io/uring_io/uring_io_parameter.cpp @@ -0,0 +1,46 @@ + +// Copyright 2024-present the vsag project +// +// 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. + +#if HAVE_LIBURING + +#include "io/uring_io/uring_io_parameter.h" + +#include "inner_string_params.h" + +namespace vsag { + +UringIOParameter::UringIOParameter() : IOParameter(IO_TYPE_VALUE_URING_IO) { +} + +UringIOParameter::UringIOParameter(const vsag::JsonType& json) + : IOParameter(IO_TYPE_VALUE_URING_IO) { + this->FromJson(json); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) +} + +void +UringIOParameter::FromJson(const JsonType& json) { + CHECK_ARGUMENT(json.Contains(IO_FILE_PATH_KEY), "missing file_path parameter for uring_io"); + this->path_ = json[IO_FILE_PATH_KEY].GetString(); +} + +JsonType +UringIOParameter::ToJson() const { + JsonType json; + json[TYPE_KEY].SetString(IO_TYPE_VALUE_URING_IO); + json[IO_FILE_PATH_KEY].SetString(this->path_); + return json; +} +} // namespace vsag +#endif // HAVE_LIBURING diff --git a/src/io/uring_io/uring_io_parameter.h b/src/io/uring_io/uring_io_parameter.h new file mode 100644 index 0000000000..3b9db82102 --- /dev/null +++ b/src/io/uring_io/uring_io_parameter.h @@ -0,0 +1,47 @@ + +// Copyright 2024-present the vsag project +// +// 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 + +#if HAVE_LIBURING + +#include "io/common/io_parameter.h" +#include "utils/pointer_define.h" +namespace vsag { +DEFINE_POINTER(UringIOParameter); +class UringIOParameter : public IOParameter { +public: + UringIOParameter(); + + explicit UringIOParameter(const JsonType& json); + + void + FromJson(const JsonType& json) override; + + JsonType + ToJson() const override; + +public: + std::string path_{}; +}; +} // namespace vsag + +#else +#include "io/buffer_io/buffer_io_parameter.h" +namespace vsag { +using UringIOParameter = BufferIOParameter; +using UringIOParameterPtr = std::shared_ptr; +} // namespace vsag +#endif // HAVE_LIBURING diff --git a/src/io/uring_io/uring_io_parameter_test.cpp b/src/io/uring_io/uring_io_parameter_test.cpp new file mode 100644 index 0000000000..1a2dc86763 --- /dev/null +++ b/src/io/uring_io/uring_io_parameter_test.cpp @@ -0,0 +1,41 @@ + +// Copyright 2024-present the vsag project +// +// 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 "io/uring_io/uring_io_parameter.h" + +#include + +#include "parameter_test.h" +#include "unittest.h" + +using namespace vsag; + +TEST_CASE("UringIO Parameters Test", "[ut][UringIOParameters]") { +#if !HAVE_LIBURING + SKIP("liburing is unavailable; UringIOParameter falls back to BufferIOParameter"); +#endif + fixtures::TempDir dir("uring_io"); + auto path = dir.GenerateRandomFile(); + constexpr const char* param_str = R"( + {{ + "type": "uring_io", + "file_path": "{}" + }} + )"; + auto param_json = JsonType::Parse(fmt::format(param_str, path)); + auto param = std::make_shared(); + param->FromJson(param_json); + ParameterTest::TestToJson(param); +} diff --git a/src/io/uring_io/uring_io_test.cpp b/src/io/uring_io/uring_io_test.cpp new file mode 100644 index 0000000000..3f8af24be1 --- /dev/null +++ b/src/io/uring_io/uring_io_test.cpp @@ -0,0 +1,74 @@ + +// Copyright 2024-present the vsag project +// +// 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 "io/uring_io/uring_io.h" + +#include +#include + +#include "impl/allocator/safe_allocator.h" +#include "index_common_param.h" +#include "io/common/basic_io_test.h" + +using namespace vsag; + +TEST_CASE("UringIO Read And Write", "[ut][UringIO]") { +#if !HAVE_LIBURING + SKIP("liburing is unavailable; UringIO falls back to BufferIO"); +#endif + fixtures::TempDir dir("uring_io"); + auto path = dir.GenerateRandomFile(false); + auto allocator = SafeAllocator::FactoryDefaultAllocator(); + TestDistIOWrongInit(allocator.get()); + auto io = std::make_unique(path, allocator.get()); + TestBasicReadWrite(*io); + + // in memory + REQUIRE(UringIO::InMemory == false); +} + +TEST_CASE("UringIO Parameter", "[ut][UringIO]") { +#if !HAVE_LIBURING + SKIP("liburing is unavailable; UringIO falls back to BufferIO"); +#endif + fixtures::TempDir dir("uring_io"); + auto path = dir.GenerateRandomFile(); + auto allocator = SafeAllocator::FactoryDefaultAllocator(); + constexpr const char* param_str = R"( + {{ + "type": "uring_io", + "file_path" : "{}" + }} + )"; + auto json = JsonType::Parse(fmt::format(param_str, path)); + auto io_param = IOParameter::GetIOParameterByJson(json); + IndexCommonParam common_param; + common_param.allocator_ = allocator; + auto io = std::make_unique(io_param, common_param); + TestBasicReadWrite(*io); +} + +TEST_CASE("UringIO Serialize & Deserialize", "[ut][UringIO]") { +#if !HAVE_LIBURING + SKIP("liburing is unavailable; UringIO falls back to BufferIO"); +#endif + auto allocator = SafeAllocator::FactoryDefaultAllocator(); + fixtures::TempDir dir("uring_io"); + auto path1 = dir.GenerateRandomFile(); + auto path2 = dir.GenerateRandomFile(); + auto wio = std::make_unique(path1, allocator.get()); + auto rio = std::make_unique(path2, allocator.get()); + TestSerializeAndDeserialize(*wio, *rio); +}