From 9e21c2f49d2691549fc20f472658ebc3e288584c Mon Sep 17 00:00:00 2001 From: "Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com)" Date: Fri, 3 Apr 2020 10:40:41 +0100 Subject: WiP merge of parts of the resumable_io branch into develop branch. --- include/llfio/revision.hpp | 6 +- include/llfio/v2.0/algorithm/remove_all.hpp | 4 +- include/llfio/v2.0/async_file_handle.hpp | 802 --------------------- include/llfio/v2.0/config.hpp | 98 +-- .../v2.0/detail/impl/fast_random_file_handle.ipp | 2 +- include/llfio/v2.0/detail/impl/io_multiplexer.ipp | 39 + .../v2.0/detail/impl/posix/async_file_handle.ipp | 381 ---------- include/llfio/v2.0/detail/impl/posix/io_handle.ipp | 71 +- .../llfio/v2.0/detail/impl/posix/io_service.ipp | 410 ----------- .../llfio/v2.0/detail/impl/posix/map_handle.ipp | 6 +- include/llfio/v2.0/detail/impl/storage_profile.ipp | 14 +- .../v2.0/detail/impl/windows/async_file_handle.ipp | 277 ------- .../v2.0/detail/impl/windows/directory_handle.ipp | 2 +- .../llfio/v2.0/detail/impl/windows/fs_handle.ipp | 7 +- include/llfio/v2.0/detail/impl/windows/handle.ipp | 2 +- include/llfio/v2.0/detail/impl/windows/import.hpp | 147 +++- .../llfio/v2.0/detail/impl/windows/io_handle.ipp | 157 ++-- .../llfio/v2.0/detail/impl/windows/io_service.ipp | 102 --- .../llfio/v2.0/detail/impl/windows/map_handle.ipp | 6 +- .../llfio/v2.0/detail/impl/windows/pipe_handle.ipp | 96 ++- .../v2.0/detail/impl/windows/symlink_handle.ipp | 4 +- include/llfio/v2.0/fast_random_file_handle.hpp | 56 +- include/llfio/v2.0/file_handle.hpp | 39 +- include/llfio/v2.0/fs_handle.hpp | 10 + include/llfio/v2.0/handle.hpp | 98 ++- include/llfio/v2.0/io_handle.hpp | 434 +++++------ include/llfio/v2.0/io_multiplexer.hpp | 412 +++++++++++ include/llfio/v2.0/io_service.hpp | 301 -------- include/llfio/v2.0/llfio.hpp | 4 - include/llfio/v2.0/map_handle.hpp | 93 ++- include/llfio/v2.0/mapped_file_handle.hpp | 79 +- include/llfio/v2.0/native_handle_type.hpp | 11 +- include/llfio/v2.0/path_handle.hpp | 14 +- include/llfio/v2.0/pipe_handle.hpp | 62 +- include/llfio/v2.0/status_code.hpp | 14 +- include/llfio/v2.0/storage_profile.hpp | 18 +- 36 files changed, 1329 insertions(+), 2949 deletions(-) delete mode 100644 include/llfio/v2.0/async_file_handle.hpp create mode 100644 include/llfio/v2.0/detail/impl/io_multiplexer.ipp delete mode 100644 include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp delete mode 100644 include/llfio/v2.0/detail/impl/posix/io_service.ipp delete mode 100644 include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp delete mode 100644 include/llfio/v2.0/detail/impl/windows/io_service.ipp create mode 100644 include/llfio/v2.0/io_multiplexer.hpp delete mode 100644 include/llfio/v2.0/io_service.hpp (limited to 'include') diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp index 15266d79..26511894 100644 --- a/include/llfio/revision.hpp +++ b/include/llfio/revision.hpp @@ -1,4 +1,4 @@ // Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time -#define LLFIO_PREVIOUS_COMMIT_REF 1c7940fb1fc3dc029a304bb0ec4f6cad3104da06 -#define LLFIO_PREVIOUS_COMMIT_DATE "2020-03-30 14:56:18 +00:00" -#define LLFIO_PREVIOUS_COMMIT_UNIQUE 1c7940fb +#define LLFIO_PREVIOUS_COMMIT_REF 434d73013a31d86cabf894328f13764b136579e2 +#define LLFIO_PREVIOUS_COMMIT_DATE "2020-03-31 06:49:29 +00:00" +#define LLFIO_PREVIOUS_COMMIT_UNIQUE 434d7301 diff --git a/include/llfio/v2.0/algorithm/remove_all.hpp b/include/llfio/v2.0/algorithm/remove_all.hpp index 64d50f06..20c0649d 100644 --- a/include/llfio/v2.0/algorithm/remove_all.hpp +++ b/include/llfio/v2.0/algorithm/remove_all.hpp @@ -74,7 +74,7 @@ namespace algorithm namespace detail { - LLFIO_HEADERS_ONLY_FUNC_SPEC result remove_all(directory_handle &&dirh, LLFIO_V2_NAMESPACE::detail::function_ptr(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)> callback, size_t threads) noexcept; + LLFIO_HEADERS_ONLY_FUNC_SPEC result remove_all(directory_handle &&dirh, LLFIO_V2_NAMESPACE::function_ptr(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)> callback, size_t threads) noexcept; } // namespace detail /*! \brief Reliably removes from the filesystem `dirh` and everything under `dirh`. @@ -151,7 +151,7 @@ namespace algorithm */ template inline result remove_all(directory_handle &&dirh, F &&callback, size_t threads = 0) noexcept { - return detail::remove_all(std::move(dirh), LLFIO_V2_NAMESPACE::detail::emplace_function_ptr(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)>(std::forward(callback)), threads); + return detail::remove_all(std::move(dirh), LLFIO_V2_NAMESPACE::emplace_function_ptr(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)>(std::forward(callback)), threads); } //! \overload With default callback with removal failure timeout of 10 seconds inline result remove_all(directory_handle &&dirh, size_t threads = 0) noexcept { return detail::remove_all(std::move(dirh), {}, threads); } diff --git a/include/llfio/v2.0/async_file_handle.hpp b/include/llfio/v2.0/async_file_handle.hpp deleted file mode 100644 index 0f366ffc..00000000 --- a/include/llfio/v2.0/async_file_handle.hpp +++ /dev/null @@ -1,802 +0,0 @@ -/* An async handle to a file -(C) 2015-2017 Niall Douglas (11 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#ifndef LLFIO_ASYNC_FILE_HANDLE_H -#define LLFIO_ASYNC_FILE_HANDLE_H - -#include "file_handle.hpp" -#include "io_service.hpp" - -//! \file async_file_handle.hpp Provides async_file_handle - -LLFIO_V2_NAMESPACE_EXPORT_BEGIN - -namespace detail -{ -#if __cplusplus > 201700 && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION > 7000 /* approx end of 2017 */) - template using is_invocable_r = std::is_invocable_r; -#else - template using is_invocable_r = std::true_type; -#endif -} - -/*! \class async_file_handle -\brief An asynchronous handle to an open something. - -\note Unlike the others, `async_file_handle` defaults to `only_metadata` caching as that is the -only use case where using async i/o makes sense given the other options below. - - - - - - -
Cost of openingCost of i/oConcurrency and AtomicityOther remarks
`file_handle`LeastSyscallPOSIX guarantees (usually)Least gotcha
`async_file_handle`MoreMost (syscall + malloc/free + reactor)POSIX guarantees (usually)Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`
`mapped_file_handle`MostLeastNoneCannot be used with uncached i/o
- -\warning i/o initiated by this class MUST be on the same kernel thread as which -created the owning `io_service` which MUST also be the same kernel thread as which -runs the i/o service's `run()` function. - -\snippet coroutines.cpp coroutines_example -*/ -class LLFIO_DECL async_file_handle : public file_handle -{ - friend class io_service; - -public: - using dev_t = file_handle::dev_t; - using ino_t = file_handle::ino_t; - using path_view_type = file_handle::path_view_type; - using path_type = io_handle::path_type; - using extent_type = io_handle::extent_type; - using size_type = io_handle::size_type; - using mode = io_handle::mode; - using creation = io_handle::creation; - using caching = io_handle::caching; - using flag = io_handle::flag; - using buffer_type = io_handle::buffer_type; - using const_buffer_type = io_handle::const_buffer_type; - using buffers_type = io_handle::buffers_type; - using const_buffers_type = io_handle::const_buffers_type; - template using io_request = io_handle::io_request; - template using io_result = io_handle::io_result; - -protected: - // Do NOT declare variables here, put them into file_handle to preserve up-conversion - -public: - //! Default constructor - constexpr async_file_handle() {} // NOLINT - ~async_file_handle() = default; - - //! Construct a handle from a supplied native handle - constexpr async_file_handle(io_service *service, native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none) - : file_handle(std::move(h), devid, inode, caching, flags) - { - this->_service = service; - } - //! Implicit move construction of async_file_handle permitted - async_file_handle(async_file_handle &&o) noexcept = default; - //! No copy construction (use `clone()`) - async_file_handle(const async_file_handle &) = delete; - //! Explicit conversion from file_handle permitted - explicit constexpr async_file_handle(file_handle &&o) noexcept : file_handle(std::move(o)) {} - //! Explicit conversion from handle and io_handle permitted - explicit constexpr async_file_handle(handle &&o, io_service *service, dev_t devid, ino_t inode) noexcept : file_handle(std::move(o), devid, inode) { this->_service = service; } - //! Move assignment of async_file_handle permitted - async_file_handle &operator=(async_file_handle &&o) noexcept - { - this->~async_file_handle(); - new(this) async_file_handle(std::move(o)); - return *this; - } - //! No copy assignment - async_file_handle &operator=(const async_file_handle &) = delete; - //! Swap with another instance - LLFIO_MAKE_FREE_FUNCTION - void swap(async_file_handle &o) noexcept - { - async_file_handle temp(std::move(*this)); - *this = std::move(o); - o = std::move(temp); - } - - /*! Create an async file handle opening access to a file on path - using the given io_service. - \param service The `io_service` to use. - \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute. - \param _path The path relative to base to open. - \param _mode How to open the file. - \param _creation How to create the file. - \param _caching How to ask the kernel to cache the file. - \param flags Any additional custom behaviours. - - \errors Any of the values POSIX open() or CreateFile() can return. - */ - LLFIO_MAKE_FREE_FUNCTION - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result async_file(io_service &service, const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept - { - // Open it overlapped, otherwise no difference. - OUTCOME_TRY(v, file_handle::file(std::move(base), _path, _mode, _creation, _caching, flags | flag::multiplexable)); - async_file_handle ret(std::move(v)); - ret._service = &service; - return {std::move(ret)}; - } - - /*! Create an async file handle creating a uniquely named file on a path. - The file is opened exclusively with `creation::only_if_not_exist` so it - will never collide with nor overwrite any existing file. - - \errors Any of the values POSIX open() or CreateFile() can return. - */ - LLFIO_MAKE_FREE_FUNCTION - static inline result async_uniquely_named_file(io_service &service, const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::only_metadata, flag flags = flag::none) noexcept - { - try - { - for(;;) - { - auto randomname = utils::random_string(32); - randomname.append(".random"); - result ret = async_file(service, dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags); - if(ret || (!ret && ret.error() != errc::file_exists)) - { - return ret; - } - } - } - catch(...) - { - return error_from_exception(); - } - } - /*! Create an async file handle creating the named file on some path which - the OS declares to be suitable for temporary files. Most OSs are - very lazy about flushing changes made to these temporary files. - Note the default flags are to have the newly created file deleted - on first handle close. - Note also that an empty name is equivalent to calling - `async_uniquely_named_file(path_discovery::storage_backed_temporary_files_directory())` and the creation - parameter is ignored. - - \note If the temporary file you are creating is not going to have its - path sent to another process for usage, this is the WRONG function - to use. Use `temp_inode()` instead, it is far more secure. - - \errors Any of the values POSIX open() or CreateFile() can return. - */ - LLFIO_MAKE_FREE_FUNCTION - static inline result async_temp_file(io_service &service, path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::only_metadata, flag flags = flag::unlink_on_first_close) noexcept - { - auto &tempdirh = path_discovery::storage_backed_temporary_files_directory(); - return name.empty() ? async_uniquely_named_file(service, tempdirh, _mode, _caching, flags) : async_file(service, tempdirh, name, _mode, _creation, _caching, flags); - } - /*! \em Securely create an async file handle creating a temporary anonymous inode in - the filesystem referred to by \em dirpath. The inode created has - no name nor accessible path on the filing system and ceases to - exist as soon as the last handle is closed, making it ideal for use as - a temporary file where other processes do not need to have access - to its contents via some path on the filing system (a classic use case - is for backing shared memory maps). - - \errors Any of the values POSIX open() or CreateFile() can return. - */ - LLFIO_MAKE_FREE_FUNCTION - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept - { - // Open it overlapped, otherwise no difference. - OUTCOME_TRY(v, file_handle::temp_inode(dir, _mode, flags | flag::multiplexable)); - async_file_handle ret(std::move(v)); - ret._service = &service; - return {std::move(ret)}; - } - - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override; - /*! Reopen this handle to a different io_service (copy constructor is disabled to avoid accidental copying) - - \errors Any of the values POSIX dup() or DuplicateHandle() can return. - */ - result reopen(io_service &service, mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept - { - OUTCOME_TRY(v, file_handle::reopen(mode_, caching_, d)); - async_file_handle ret(std::move(v)); - ret._service = &service; - return {std::move(ret)}; - } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result reopen(mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept - { - OUTCOME_TRY(v, file_handle::reopen(mode_, caching_, d)); - async_file_handle ret(std::move(v)); - ret._service = _service; - return {static_cast(ret)}; - } - -#if DOXYGEN_SHOULD_SKIP_THIS -private: -#else -protected: -#endif - using shared_size_type = size_type; - enum class operation_t - { - read, - write, - fsync_sync, - dsync_sync, - fsync_async, - dsync_async - }; - struct _erased_completion_handler; -#if defined(__clang__) && __clang_major__ >= 8 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdefaulted-function-deleted" -#endif - // Holds state for an i/o in progress. Will be subclassed with platform specific state and how to implement completion. - struct _erased_io_state_type - { - friend class io_service; - async_file_handle *parent; - operation_t operation; - bool must_deallocate_self; - size_t items; - shared_size_type items_to_go; - union result_storage { - io_result read; - io_result write; - constexpr result_storage() - : read(buffers_type()) - { - } - ~result_storage() { /* needed as io_result is move-only when configured with status code */} - } result; - constexpr _erased_io_state_type(async_file_handle *_parent, operation_t _operation, bool _must_deallocate_self, size_t _items) - : parent(_parent) - , operation(_operation) - , must_deallocate_self(_must_deallocate_self) - , items(_items) - , items_to_go(0) - { - } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~_erased_io_state_type() - { - // i/o still pending is very bad, this should never happen - assert(!items_to_go); - if(items_to_go != 0u) - { - LLFIO_LOG_FATAL(parent->native_handle().h, "FATAL: io_state destructed while i/o still in flight, the derived class should never allow this."); - abort(); - } - } - - //! Retrieves a pointer to the copy of the completion handler held inside the i/o state. - virtual _erased_completion_handler *erased_completion_handler() noexcept = 0; - /* Called when an i/o is completed by the system, figures out whether to call invoke_completion. - - For Windows: - - errcode: GetLastError() code - - bytes_transferred: obvious - - internal_state: LPOVERLAPPED for this op - - For POSIX AIO: - - errcode: errno code - - bytes_transferred: return from aio_return(), usually bytes transferred - - internal_state: address of pointer to struct aiocb in io_service's _aiocbsv - */ - virtual void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept = 0; - - protected: - _erased_io_state_type(_erased_io_state_type &&) = default; - _erased_io_state_type(const _erased_io_state_type &) = default; - _erased_io_state_type &operator=(_erased_io_state_type &&) = default; - _erased_io_state_type &operator=(const _erased_io_state_type &) = default; - }; -#if defined(__clang__) && __clang_major__ >= 8 -#pragma clang diagnostic pop -#endif - struct _io_state_deleter - { - template void operator()(U *_ptr) const - { - bool must_deallocate_self = _ptr->must_deallocate_self; - _ptr->~U(); - if(must_deallocate_self) - { - auto *ptr = reinterpret_cast(_ptr); - ::free(ptr); // NOLINT - } - } - }; - -public: - /*! Smart pointer to state of an i/o in progress. Destroying this before an i/o has completed - is blocking because the i/o must be cancelled before the destructor can safely exit. - */ - using io_state_ptr = std::unique_ptr<_erased_io_state_type, _io_state_deleter>; - -#if DOXYGEN_SHOULD_SKIP_THIS -private: -#else -protected: -#endif - // Used to indirect copy and call of unknown completion handler - struct _erased_completion_handler - { - virtual ~_erased_completion_handler() = default; - // Returns my size including completion handler - virtual size_t bytes() const noexcept = 0; - // Moves me and handler to some new location - virtual void move(_erased_completion_handler *dest) = 0; - // Invokes my completion handler - virtual void operator()(_erased_io_state_type *state) = 0; - // Returns a pointer to the completion handler - virtual void *address() noexcept = 0; - - protected: - _erased_completion_handler() = default; - _erased_completion_handler(_erased_completion_handler &&) = default; - _erased_completion_handler(const _erased_completion_handler &) = default; - _erased_completion_handler &operator=(_erased_completion_handler &&) = default; - _erased_completion_handler &operator=(const _erased_completion_handler &) = default; - }; - template result LLFIO_HEADERS_ONLY_MEMFUNC_SPEC _begin_io(span mem, operation_t operation, io_request reqs, _erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept; - LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result _begin_io(span mem, operation_t operation, io_request reqs, _erased_completion_handler &&completion) noexcept; - -public: - /*! \brief Schedule a barrier to occur asynchronously. - - \note All the caveats and exclusions which apply to `barrier()` also apply here. Note that Microsoft Windows - does not support asynchronously executed barriers, and this call will fail on that operating system. - - \return Either an io_state_ptr to the i/o in progress, or an error code. - \param reqs A scatter-gather and offset request for what range to barrier. May be ignored on some platforms - which always write barrier the entire file. Supplying a default initialised reqs write barriers the entire file. - \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result &&)`. - Note that buffers returned may not be buffers input, see documentation for `barrier()`. - \param kind Which kind of write reordering barrier to perform. - \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry. - \errors As for `barrier()`, plus `ENOMEM`. - \mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type - erased completion handler of unknown type and state per buffers input. - */ - LLFIO_MAKE_FREE_FUNCTION - template // - LLFIO_REQUIRES(true || detail::is_invocable_r &&>::value) // - result async_barrier(io_request reqs, CompletionRoutine &&completion, barrier_kind kind = barrier_kind::nowait_data_only, span mem = {}) noexcept - { - LLFIO_LOG_FUNCTION_CALL(this); - struct completion_handler : _erased_completion_handler - { - CompletionRoutine completion; - - explicit completion_handler(CompletionRoutine c) - : completion(std::move(c)) - { - } - size_t bytes() const noexcept final { return sizeof(*this); } - void move(_erased_completion_handler *_dest) final - { - auto *dest = reinterpret_cast(_dest); - using msvc_workaround = std::decay_t; - new(dest) msvc_workaround(std::move(*this)); - } - void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); } - void *address() noexcept final { return &completion; } - } ch{std::forward(completion)}; - operation_t operation = operation_t::fsync_sync; - if(kind == barrier_kind::nowait_all) - { - operation = operation_t::fsync_async; - } - else if(kind == barrier_kind::wait_data_only) - { - operation = operation_t::dsync_sync; - } - else if(kind == barrier_kind::nowait_data_only) - { - operation = operation_t::dsync_async; - } - return _begin_io(mem, operation, reinterpret_cast &>(reqs), std::move(ch)); - } - - /*! \brief Schedule a read to occur asynchronously. - - Note that some OS kernels can only process a limited number async i/o - operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again` - and gracefully reschedule the i/o for a later time. This temporary - failure may be returned immediately, or to the completion handler - and hence you ought to handle both situations. - - \return Either an io_state_ptr to the i/o in progress, or an error code. - \param reqs A scatter-gather and offset request. - \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result &&)`. - Note that buffers returned may not be buffers input, see documentation for `read()`. - \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry. - \errors As for `read()`, plus `ENOMEM`. - \mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type - erased completion handler of unknown type and state per buffers input. - */ - LLFIO_MAKE_FREE_FUNCTION - template // - LLFIO_REQUIRES(true || detail::is_invocable_r &&>::value) // - result async_read(io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept - { - LLFIO_LOG_FUNCTION_CALL(this); - struct completion_handler : _erased_completion_handler - { - CompletionRoutine completion; - explicit completion_handler(CompletionRoutine c) - : completion(std::move(c)) - { - } - size_t bytes() const noexcept final { return sizeof(*this); } - void move(_erased_completion_handler *_dest) final - { - auto *dest = reinterpret_cast(_dest); - using msvc_workaround = std::decay_t; - new(dest) msvc_workaround(std::move(*this)); - } - void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.read)); } - void *address() noexcept final { return &completion; } - } ch{std::forward(completion)}; - return _begin_io(mem, operation_t::read, io_request({reinterpret_cast(reqs.buffers.data()), reqs.buffers.size()}, reqs.offset), std::move(ch)); - } - - /*! \brief Schedule a write to occur asynchronously. - - Note that some OS kernels can only process a limited number async i/o - operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again` - and gracefully reschedule the i/o for a later time. This temporary - failure may be returned immediately, or to the completion handler - and hence you ought to handle both situations. - - - \return Either an io_state_ptr to the i/o in progress, or an error code. - \param reqs A scatter-gather and offset request. - \param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result &&)`. - Note that buffers returned may not be buffers input, see documentation for `write()`. - \param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry. - \errors As for `write()`, plus `ENOMEM`. - \mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type - erased completion handler of unknown type and state per buffers input. - */ - LLFIO_MAKE_FREE_FUNCTION - template // - LLFIO_REQUIRES(true || detail::is_invocable_r &&>::value) // - result async_write(io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept - { - LLFIO_LOG_FUNCTION_CALL(this); - struct completion_handler : _erased_completion_handler - { - CompletionRoutine completion; - explicit completion_handler(CompletionRoutine c) - : completion(std::move(c)) - { - } - size_t bytes() const noexcept final { return sizeof(*this); } - void move(_erased_completion_handler *_dest) final - { - auto *dest = reinterpret_cast(_dest); - using msvc_workaround = std::decay_t; - new(dest) msvc_workaround(std::move(*this)); - } - void operator()(_erased_io_state_type *state) final { completion(state->parent, std::move(state->result.write)); } - void *address() noexcept final { return &completion; } - } ch{std::forward(completion)}; - return _begin_io(mem, operation_t::write, reqs, std::move(ch)); - } - - using file_handle::read; - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override; - using file_handle::write; - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override; - -#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) -private: - template class awaitable_state - { - friend class async_file_handle; - optional> _suspended; - optional> _result; - - // Called on completion of the i/o - void operator()(async_file_handle * /*unused*/, io_result &&result) - { - // store the result and resume the coroutine - _result = std::move(result); - if(_suspended) - { - _suspended->resume(); - } - } - }; - -public: - //! Type sugar to tell `co_await` what to do - template class awaitable - { - friend class async_file_handle; - io_state_ptr _state; - awaitable_state *_astate; - - explicit awaitable(io_state_ptr state) - : _state(std::move(state)) - , _astate(reinterpret_cast *>(_state->erased_completion_handler()->address())) - { - } - - public: - //! Called by `co_await` to determine whether to suspend the coroutine. - bool await_ready() { return _astate->_result.has_value(); } - //! Called by `co_await` to suspend the coroutine. - void await_suspend(coroutine_handle<> co) { _astate->_suspended = co; } - //! Called by `co_await` after resuming the coroutine to return a value. - io_result await_resume() { return std::move(*_astate->_result); } - }; - -public: - /*! \brief Schedule a read to occur asynchronously. - - \return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine - until the operation has completed, resuming with the buffers read, which may - not be the buffers input. The size of each scatter-gather buffer is updated with the number - of bytes of that buffer transferred, and the pointer to the data may be \em completely - different to what was submitted (e.g. it may point into a memory map). - \param reqs A scatter-gather and offset request. - \errors As for read(), plus ENOMEM. - \mallocs One calloc, one free. - */ - LLFIO_MAKE_FREE_FUNCTION - result> co_read(io_request reqs) noexcept - { - OUTCOME_TRY(r, async_read(reqs, awaitable_state())); - return awaitable(std::move(r)); - } - //! \overload - LLFIO_MAKE_FREE_FUNCTION - result> co_read(extent_type offset, std::initializer_list lst) noexcept - { - buffer_type *_reqs = reinterpret_cast(alloca(sizeof(buffer_type) * lst.size())); - memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size()); - io_request reqs(buffers_type(_reqs, lst.size()), offset); - return co_read(reqs); - } - - /*! \brief Schedule a write to occur asynchronously - - \return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine - until the operation has completed, resuming with the buffers written, which - may not be the buffers input. The size of each scatter-gather buffer is updated with - the number of bytes of that buffer transferred. - \param reqs A scatter-gather and offset request. - \errors As for write(), plus ENOMEM. - \mallocs One calloc, one free. - */ - LLFIO_MAKE_FREE_FUNCTION - result> co_write(io_request reqs) noexcept - { - OUTCOME_TRY(r, async_write(reqs, awaitable_state())); - return awaitable(std::move(r)); - } - //! \overload - LLFIO_MAKE_FREE_FUNCTION - result> co_write(extent_type offset, std::initializer_list lst) noexcept - { - const_buffer_type *_reqs = reinterpret_cast(alloca(sizeof(const_buffer_type) * lst.size())); - memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size()); - io_request reqs(const_buffers_type(_reqs, lst.size()), offset); - return co_write(reqs); - } -#endif -}; - -//! \brief Constructor for `async_file_handle` -template <> struct construct -{ - io_service &service; - const path_handle &base; - async_file_handle::path_view_type _path; - async_file_handle::mode _mode = async_file_handle::mode::read; - async_file_handle::creation _creation = async_file_handle::creation::open_existing; - async_file_handle::caching _caching = async_file_handle::caching::only_metadata; - async_file_handle::flag flags = async_file_handle::flag::none; - result operator()() const noexcept { return async_file_handle::async_file(service, base, _path, _mode, _creation, _caching, flags); } -}; - - -// BEGIN make_free_functions.py -//! Swap with another instance -inline void swap(async_file_handle &self, async_file_handle &o) noexcept -{ - return self.swap(std::forward(o)); -} -/*! Create an async file handle opening access to a file on path -using the given io_service. -\param service The `io_service` to use. -\param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute. -\param _path The path relative to base to open. -\param _mode How to open the file. -\param _creation How to create the file. -\param _caching How to ask the kernel to cache the file. -\param flags Any additional custom behaviours. - -\errors Any of the values POSIX open() or CreateFile() can return. -*/ -inline result async_file(io_service &service, const path_handle &base, async_file_handle::path_view_type _path, async_file_handle::mode _mode = async_file_handle::mode::read, async_file_handle::creation _creation = async_file_handle::creation::open_existing, - async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept -{ - return async_file_handle::async_file(std::forward(service), std::forward(base), std::forward(_path), std::forward(_mode), std::forward(_creation), std::forward(_caching), - std::forward(flags)); -} -/*! Create an async file handle creating a randomly named file on a path. -The file is opened exclusively with `creation::only_if_not_exist` so it -will never collide with nor overwrite any existing file. - -\errors Any of the values POSIX open() or CreateFile() can return. -*/ -inline result async_uniquely_named_file(io_service &service, const path_handle &dirpath, async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::none) noexcept -{ - return async_file_handle::async_uniquely_named_file(std::forward(service), std::forward(dirpath), std::forward(_mode), std::forward(_caching), std::forward(flags)); -} -/*! Create an async file handle creating the named file on some path which -the OS declares to be suitable for temporary files. Most OSs are -very lazy about flushing changes made to these temporary files. -Note the default flags are to have the newly created file deleted -on first handle close. -Note also that an empty name is equivalent to calling -`async_uniquely_named_file(path_discovery::storage_backed_temporary_files_directory())` and the creation -parameter is ignored. - -\note If the temporary file you are creating is not going to have its -path sent to another process for usage, this is the WRONG function -to use. Use `temp_inode()` instead, it is far more secure. - -\errors Any of the values POSIX open() or CreateFile() can return. -*/ -inline result async_temp_file(io_service &service, async_file_handle::path_view_type name = async_file_handle::path_view_type(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::creation _creation = async_file_handle::creation::if_needed, - async_file_handle::caching _caching = async_file_handle::caching::only_metadata, async_file_handle::flag flags = async_file_handle::flag::unlink_on_first_close) noexcept -{ - return async_file_handle::async_temp_file(std::forward(service), std::forward(name), std::forward(_mode), std::forward(_creation), std::forward(_caching), std::forward(flags)); -} -/*! \em Securely create an async file handle creating a temporary anonymous inode in -the filesystem referred to by \em dirpath. The inode created has -no name nor accessible path on the filing system and ceases to -exist as soon as the last handle is closed, making it ideal for use as -a temporary file where other processes do not need to have access -to its contents via some path on the filing system (a classic use case -is for backing shared memory maps). - -\errors Any of the values POSIX open() or CreateFile() can return. -*/ -inline result async_temp_inode(io_service &service, const path_handle &dir = path_discovery::storage_backed_temporary_files_directory(), async_file_handle::mode _mode = async_file_handle::mode::write, async_file_handle::flag flags = async_file_handle::flag::none) noexcept -{ - return async_file_handle::async_temp_inode(std::forward(service), std::forward(dir), std::forward(_mode), std::forward(flags)); -} -/*! \brief Schedule a read to occur asynchronously. - -Note that some OS kernels can only process a limited number async i/o -operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again` -and gracefully reschedule the i/o for a later time. This temporary -failure may be returned immediately, or to the completion handler -and hence you ought to handle both situations. - -\return Either an io_state_ptr to the i/o in progress, or an error code. -\param self The object whose member function to call. -\param reqs A scatter-gather and offset request. -\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result &)`. -Note that buffers returned may not be buffers input, see documentation for `read()`. -\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry. -\errors As for `read()`, plus `ENOMEM`. -\mallocs If mem is not set, one calloc, one free. The allocation is unavoidable due to the need to store a type -erased completion handler of unknown type and state per buffers input. -*/ -template inline result async_read(async_file_handle &self, async_file_handle::io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept -{ - return self.async_read(std::forward(reqs), std::forward(completion), std::forward(mem)); -} -/*! \brief Schedule a write to occur asynchronously. - -Note that some OS kernels can only process a limited number async i/o -operations at a time. You should therefore check for the error `errc::resource_unavailable_try_again` -and gracefully reschedule the i/o for a later time. This temporary -failure may be returned immediately, or to the completion handler -and hence you ought to handle both situations. - - -\return Either an io_state_ptr to the i/o in progress, or an error code. -\param self The object whose member function to call. -\param reqs A scatter-gather and offset request. -\param completion A callable to call upon i/o completion. Spec is `void(async_file_handle *, io_result &)`. -Note that buffers returned may not be buffers input, see documentation for `write()`. -\param mem Optional span of memory to use to avoid using `calloc()`. Note span MUST be all bits zero on entry. -\errors As for `write()`, plus `ENOMEM`. -\mallocs If mem in not set, one calloc, one free. The allocation is unavoidable due to the need to store a type -erased completion handler of unknown type and state per buffers input. -*/ -template inline result async_write(async_file_handle &self, async_file_handle::io_request reqs, CompletionRoutine &&completion, span mem = {}) noexcept -{ - return self.async_write(std::forward(reqs), std::forward(completion), std::forward(mem)); -} -#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) -/*! \brief Schedule a read to occur asynchronously. - -\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine -until the operation has completed, resuming with the buffers read, which may -not be the buffers input. The size of each scatter-gather buffer is updated with the number -of bytes of that buffer transferred, and the pointer to the data may be \em completely -different to what was submitted (e.g. it may point into a memory map). -\param self The object whose member function to call. -\param reqs A scatter-gather and offset request. -\errors As for read(), plus ENOMEM. -\mallocs One calloc, one free. -*/ -inline result> co_read(async_file_handle &self, async_file_handle::io_request reqs) noexcept -{ - return self.co_read(std::forward(reqs)); -} -//! \overload -inline result> co_read(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list lst) noexcept -{ - return self.co_read(std::forward(offset), std::forward(lst)); -} -/*! \brief Schedule a write to occur asynchronously - -\return An awaitable, which when `co_await`ed upon, suspends execution of the coroutine -until the operation has completed, resuming with the buffers written, which -may not be the buffers input. The size of each scatter-gather buffer is updated with -the number of bytes of that buffer transferred. -\param self The object whose member function to call. -\param reqs A scatter-gather and offset request. -\errors As for write(), plus ENOMEM. -\mallocs One calloc, one free. -*/ -inline result> co_write(async_file_handle &self, async_file_handle::io_request reqs) noexcept -{ - return self.co_write(std::forward(reqs)); -} -//! \overload -inline result> co_write(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list lst) noexcept -{ - return self.co_write(std::forward(offset), std::forward(lst)); -} -#endif -// END make_free_functions.py - -LLFIO_V2_NAMESPACE_END - -#if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) -#define LLFIO_INCLUDED_BY_HEADER 1 -#ifdef _WIN32 -#include "detail/impl/windows/io_service.ipp" - -#include "detail/impl/windows/async_file_handle.ipp" -#else -#include "detail/impl/posix/io_service.ipp" - -#include "detail/impl/posix/async_file_handle.ipp" -#endif -#undef LLFIO_INCLUDED_BY_HEADER -#endif - -#endif diff --git a/include/llfio/v2.0/config.hpp b/include/llfio/v2.0/config.hpp index f548d8e0..9eb511ae 100644 --- a/include/llfio/v2.0/config.hpp +++ b/include/llfio/v2.0/config.hpp @@ -323,7 +323,8 @@ LLFIO_V2_NAMESPACE_END #include "quickcpplib/bitfield.hpp" // Bring in scoped undo #include "quickcpplib/scoped_undo.hpp" -LLFIO_V2_NAMESPACE_BEGIN using QUICKCPPLIB_NAMESPACE::scoped_undo::undoer; +LLFIO_V2_NAMESPACE_BEGIN +using QUICKCPPLIB_NAMESPACE::scoped_undo::undoer; LLFIO_V2_NAMESPACE_END // Bring in a span implementation #include "quickcpplib/span.hpp" @@ -346,6 +347,15 @@ LLFIO_V2_NAMESPACE_END LLFIO_V2_NAMESPACE_BEGIN using namespace QUICKCPPLIB_NAMESPACE::string_view; LLFIO_V2_NAMESPACE_END +// Bring in a function_ptr implementation +#include "quickcpplib/function_ptr.hpp" +LLFIO_V2_NAMESPACE_BEGIN +template using function_ptr = QUICKCPPLIB_NAMESPACE::function_ptr::function_ptr; +using QUICKCPPLIB_NAMESPACE::function_ptr::emplace_function_ptr; +using QUICKCPPLIB_NAMESPACE::function_ptr::emplace_function_ptr_nothrow; +using QUICKCPPLIB_NAMESPACE::function_ptr::make_function_ptr; +using QUICKCPPLIB_NAMESPACE::function_ptr::make_function_ptr_nothrow; +LLFIO_V2_NAMESPACE_END // Bring in an ensure_flushes implementation #include "quickcpplib/mem_flush_loads_stores.hpp" LLFIO_V2_NAMESPACE_BEGIN @@ -391,92 +401,6 @@ LLFIO_V2_NAMESPACE_END LLFIO_V2_NAMESPACE_BEGIN -namespace detail -{ - // A move only capable lightweight std::function, as std::function can't handle move only callables - template class function_ptr; - template class function_ptr - { - struct function_ptr_storage - { - function_ptr_storage() = default; - function_ptr_storage(const function_ptr_storage &) = delete; - function_ptr_storage(function_ptr_storage &&) = delete; - function_ptr_storage &operator=(const function_ptr_storage &) = delete; - function_ptr_storage &operator=(function_ptr_storage &&) = delete; - virtual ~function_ptr_storage() = default; - virtual R operator()(Args &&... args) = 0; - }; - template struct function_ptr_storage_impl : public function_ptr_storage - { - U c; - template - constexpr explicit function_ptr_storage_impl(Args2 &&... args) - : c(std::forward(args)...) - { - } - R operator()(Args &&... args) final { return c(std::move(args)...); } - }; - function_ptr_storage *ptr; - template struct emplace_t - { - }; - template friend inline function_ptr make_function_ptr(V &&f); // NOLINT - template - explicit function_ptr(std::nullptr_t, U &&f) - : ptr(new function_ptr_storage_impl::type>(std::forward(f))) - { - } - template friend inline function_ptr emplace_function_ptr(Args2 &&... args); // NOLINT - template - explicit function_ptr(emplace_t /*unused*/, Args2 &&... args) - : ptr(new function_ptr_storage_impl(std::forward(args)...)) - { - } - - public: - constexpr function_ptr() noexcept - : ptr(nullptr) - { - } - constexpr explicit function_ptr(function_ptr_storage *p) noexcept - : ptr(p) - { - } - constexpr function_ptr(function_ptr &&o) noexcept - : ptr(o.ptr) - { - o.ptr = nullptr; - } - function_ptr &operator=(function_ptr &&o) noexcept - { - delete ptr; - ptr = o.ptr; - o.ptr = nullptr; - return *this; - } - function_ptr(const function_ptr &) = delete; - function_ptr &operator=(const function_ptr &) = delete; - ~function_ptr() { delete ptr; } - explicit constexpr operator bool() const noexcept { return !!ptr; } - constexpr R operator()(Args... args) const { return (*ptr)(std::move(args)...); } - constexpr function_ptr_storage *get() noexcept { return ptr; } - constexpr void reset(function_ptr_storage *p = nullptr) noexcept - { - delete ptr; - ptr = p; - } - constexpr function_ptr_storage *release() noexcept - { - auto p = ptr; - ptr = nullptr; - return p; - } - }; - template inline function_ptr make_function_ptr(U &&f) { return function_ptr(nullptr, std::forward(f)); } - template inline function_ptr emplace_function_ptr(Args &&... args) { return function_ptr(typename function_ptr::template emplace_t(), std::forward(args)...); } -} // namespace detail - // Native handle support namespace win { diff --git a/include/llfio/v2.0/detail/impl/fast_random_file_handle.ipp b/include/llfio/v2.0/detail/impl/fast_random_file_handle.ipp index 282995fe..4429592e 100644 --- a/include/llfio/v2.0/detail/impl/fast_random_file_handle.ipp +++ b/include/llfio/v2.0/detail/impl/fast_random_file_handle.ipp @@ -26,7 +26,7 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_EXPORT_BEGIN -fast_random_file_handle::io_result fast_random_file_handle::read(io_request reqs, deadline /* unused */) noexcept +fast_random_file_handle::io_result fast_random_file_handle::_do_read(io_request reqs, deadline /* unused */) noexcept { if(reqs.offset >= _length) { diff --git a/include/llfio/v2.0/detail/impl/io_multiplexer.ipp b/include/llfio/v2.0/detail/impl/io_multiplexer.ipp new file mode 100644 index 00000000..88d193f7 --- /dev/null +++ b/include/llfio/v2.0/detail/impl/io_multiplexer.ipp @@ -0,0 +1,39 @@ +/* Multiplex file i/o +(C) 2019 Niall Douglas (9 commits) +File Created: Nov 2019 + + +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 in the accompanying file +Licence.txt or 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. + + +Distributed under the Boost Software License, Version 1.0. + (See accompanying file Licence.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +#include "../../io_multiplexer.hpp" + +LLFIO_V2_NAMESPACE_BEGIN + +namespace this_thread +{ + static LLFIO_THREAD_LOCAL io_multiplexer *_thread_multiplexer; + LLFIO_HEADERS_ONLY_FUNC_SPEC io_multiplexer *multiplexer() noexcept + { + return _thread_multiplexer; + } + LLFIO_HEADERS_ONLY_FUNC_SPEC void set_multiplexer(io_multiplexer *ctx) noexcept { _thread_multiplexer = ctx; } +} // namespace this_thread + +LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp deleted file mode 100644 index c05cacd2..00000000 --- a/include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp +++ /dev/null @@ -1,381 +0,0 @@ -/* A handle to something -(C) 2015-2017 Niall Douglas (5 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#include "../../../handle.hpp" - -#include -#include -#if LLFIO_USE_POSIX_AIO -#include -#endif - -LLFIO_V2_NAMESPACE_BEGIN - -async_file_handle::io_result async_file_handle::barrier(async_file_handle::io_request reqs, barrier_kind kind, deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - optional> ret; - OUTCOME_TRY(io_state, async_barrier(reqs, [&ret](async_file_handle *, io_result &&result) { ret = std::move(result); }, kind)); - (void) io_state; - - // While i/o is not done pump i/o completion - while(!ret) - { - auto t(_service->run_until(d)); - // If i/o service pump failed or timed out, cancel outstanding i/o and return - if(!t) - { - return std::move(t).error(); - } -#ifndef NDEBUG - if(!ret && t && !t.value()) - { - LLFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - return std::move(*ret); -} - -template result async_file_handle::_begin_io(span mem, async_file_handle::operation_t operation, async_file_handle::io_request reqs, async_file_handle::_erased_completion_handler &&completion, IORoutine && /*unused*/) noexcept -{ - // Need to keep a set of aiocbs matching the scatter-gather buffers - struct state_type final : public _erased_io_state_type - { -#if LLFIO_USE_POSIX_AIO - struct aiocb aiocbs[1]{}; -#else -#error todo -#endif - _erased_completion_handler *completion; - state_type() = delete; - state_type(state_type &&) = delete; - state_type(const state_type &) = delete; - state_type &operator=(state_type &&) = delete; - state_type &operator=(const state_type &) = delete; - state_type(async_file_handle *_parent, operation_t _operation, bool must_deallocate_self, size_t _items) - : _erased_io_state_type(_parent, _operation, must_deallocate_self, _items) - , completion(nullptr) - { - } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC _erased_completion_handler *erased_completion_handler() noexcept final { return completion; } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept final - { -#if LLFIO_USE_POSIX_AIO - auto **_paiocb = static_cast(internal_state); - struct aiocb *aiocb = *_paiocb; - assert(aiocb >= aiocbs && aiocb < aiocbs + this->items); - *_paiocb = nullptr; -#else -#error todo -#endif - auto &result = this->result.write; - if(result) - { - if(errcode) - { - result = posix_error(static_cast(errcode)); - } - else - { -// Figure out which i/o I am and update the buffer in question -#if LLFIO_USE_POSIX_AIO - size_t idx = aiocb - aiocbs; -#else -#error todo -#endif - if(idx >= this->items) - { - LLFIO_LOG_FATAL(0, "file_handle::io_state::operator() called with invalid index"); - std::terminate(); - } - result.value()[idx] = {result.value()[idx].data(), (size_type) bytes_transferred}; - } - } - this->parent->service()->_work_done(); - // Are we done? - if(!--this->items_to_go) - { - (*completion)(this); - } - } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~state_type() final - { - // Do we need to cancel pending i/o? - if(this->items_to_go) - { - for(size_t n = 0; n < this->items; n++) - { -#if LLFIO_USE_POSIX_AIO - int ret = aio_cancel(this->parent->native_handle().fd, aiocbs + n); - (void) ret; -#if 0 - if(ret<0 || ret==AIO_NOTCANCELED) - { - std::cout << "Failed to cancel " << (aiocbs+n) << std::endl; - } - else if(ret==AIO_CANCELED) - { - std::cout << "Cancelled " << (aiocbs+n) << std::endl; - } - else if(ret==AIO_ALLDONE) - { - std::cout << "Already done " << (aiocbs+n) << std::endl; - } -#endif -#else -#error todo -#endif - } - // Pump the i/o service until all pending i/o is completed - while(this->items_to_go) - { - auto res = this->parent->service()->run(); - (void) res; -#ifndef NDEBUG - if(res.has_error()) - { - LLFIO_LOG_FATAL(0, "file_handle: io_service failed"); - std::terminate(); - } - if(!res.value()) - { - LLFIO_LOG_FATAL(0, "file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - } - completion->~_erased_completion_handler(); - } - } * state; - extent_type offset = reqs.offset; - size_t statelen = sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(struct aiocb) + completion.bytes(); - if(!mem.empty() && statelen > mem.size()) - { - return errc::not_enough_memory; - } - size_t items(reqs.buffers.size()); -#if LLFIO_USE_POSIX_AIO && defined(AIO_LISTIO_MAX) - // If this i/o could never be done atomically, reject - if(items > AIO_LISTIO_MAX) - return errc::invalid_argument; -#if defined(__FreeBSD__) || defined(__APPLE__) - if(!service()->using_kqueues()) - { - // BSD and Apple put a tight limit on how many entries - // aio_suspend() will take in order to have reasonable - // performance. But their documentation lies, if you - // feed more than AIO_LISTIO_MAX items to aio_suspend - // it does NOT return EINVAL as specified, but rather - // simply marks all items past AIO_LISTIO_MAX as failed - // with EAGAIN. That punishes performance for LLFIO - // because we loop setting up and tearing down - // the handlers, so if we would overload llfio_suspend, - // better to error out now rather that later in io_service. - if(service()->_aiocbsv.size() + items > AIO_LISTIO_MAX) - { - return errc::resource_unavailable_try_again; - } - } -#endif -#endif - bool must_deallocate_self = false; - if(mem.empty()) - { - void *_mem = ::calloc(1, statelen); // NOLINT - if(!_mem) - { - return errc::not_enough_memory; - } - mem = {static_cast(_mem), statelen}; - must_deallocate_self = true; - } - io_state_ptr _state(reinterpret_cast(mem.data())); - new((state = reinterpret_cast(mem.data()))) state_type(this, operation, must_deallocate_self, items); - state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast(state) + sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(struct aiocb)); - completion.move(state->completion); - - // Noexcept move the buffers from req into result - BuffersType &out = state->result.write.value(); - out = std::move(reqs.buffers); - for(size_t n = 0; n < items; n++) - { -#if LLFIO_USE_POSIX_AIO -#ifndef NDEBUG - if(_v.requires_aligned_io()) - { - assert((offset & 511) == 0); - assert(((uintptr_t) out[n].data() & 511) == 0); - assert((out[n].size() & 511) == 0); - } -#endif - struct aiocb *aiocb = state->aiocbs + n; - aiocb->aio_fildes = _v.fd; - aiocb->aio_offset = offset; - aiocb->aio_buf = reinterpret_cast(const_cast(out[n].data())); - aiocb->aio_nbytes = out[n].size(); - aiocb->aio_sigevent.sigev_notify = SIGEV_NONE; - aiocb->aio_sigevent.sigev_value.sival_ptr = reinterpret_cast(state); - switch(operation) - { - case operation_t::read: - aiocb->aio_lio_opcode = LIO_READ; - break; - case operation_t::write: - aiocb->aio_lio_opcode = LIO_WRITE; - break; - case operation_t::fsync_async: - case operation_t::fsync_sync: - aiocb->aio_lio_opcode = LIO_NOP; - break; - case operation_t::dsync_async: - case operation_t::dsync_sync: - aiocb->aio_lio_opcode = LIO_NOP; - break; - } -#else -#error todo -#endif - offset += out[n].size(); - ++state->items_to_go; - } - int ret = 0; -#if LLFIO_USE_POSIX_AIO - if(service()->using_kqueues()) - { -#if LLFIO_COMPILE_KQUEUES - // Only issue one kqueue event when entire scatter-gather has completed - struct _sigev = {0}; -#error todo -#endif - } - else - { - // Add these i/o's to the quick aio_suspend list - service()->_aiocbsv.resize(service()->_aiocbsv.size() + items); - struct aiocb **thislist = service()->_aiocbsv.data() + service()->_aiocbsv.size() - items; - for(size_t n = 0; n < items; n++) - { - struct aiocb *aiocb = state->aiocbs + n; - thislist[n] = aiocb; - } - switch(operation) - { - case operation_t::read: - case operation_t::write: - ret = lio_listio(LIO_NOWAIT, thislist, items, nullptr); - break; - case operation_t::fsync_async: - case operation_t::fsync_sync: - case operation_t::dsync_async: - case operation_t::dsync_sync: - for(size_t n = 0; n < items; n++) - { - struct aiocb *aiocb = state->aiocbs + n; -#if defined(__FreeBSD__) || defined(__APPLE__) // neither of these have fdatasync() - ret = aio_fsync(O_SYNC, aiocb); -#else - ret = aio_fsync((operation == operation_t::dsync_async || operation == operation_t::dsync_sync) ? O_DSYNC : O_SYNC, aiocb); -#endif - } - break; - } - } -#else -#error todo -#endif - if(ret < 0) - { - service()->_aiocbsv.resize(service()->_aiocbsv.size() - items); - state->items_to_go = 0; - state->result.write = posix_error(); - (*state->completion)(state); - return success(std::move(_state)); - } - service()->_work_enqueued(items); - return success(std::move(_state)); -} - -result async_file_handle::_begin_io(span mem, async_file_handle::operation_t operation, io_request reqs, async_file_handle::_erased_completion_handler &&completion) noexcept -{ - return _begin_io(mem, operation, reqs, std::move(completion), nullptr); -} - -async_file_handle::io_result async_file_handle::read(async_file_handle::io_request reqs, deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - optional> ret; - OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result &&result) { ret = std::move(result); })); - (void) io_state; - - // While i/o is not done pump i/o completion - while(!ret) - { - auto t(_service->run_until(d)); - // If i/o service pump failed or timed out, cancel outstanding i/o and return - if(!t) - { - return std::move(t).error(); - } -#ifndef NDEBUG - if(!ret && t && !t.value()) - { - LLFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - return std::move(*ret); -} - -async_file_handle::io_result async_file_handle::write(async_file_handle::io_request reqs, deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - optional> ret; - OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result &&result) { ret = std::move(result); })); - (void) io_state; - - // While i/o is not done pump i/o completion - while(!ret) - { - auto t(_service->run_until(d)); - // If i/o service pump failed or timed out, cancel outstanding i/o and return - if(!t) - { - return std::move(t).error(); - } -#ifndef NDEBUG - if(!ret && t && !t.value()) - { - LLFIO_LOG_FATAL(_v.fd, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - return std::move(*ret); -} - -LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/posix/io_handle.ipp b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp index 20d253ac..c1058b7b 100644 --- a/include/llfio/v2.0/detail/impl/posix/io_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/io_handle.ipp @@ -31,9 +31,8 @@ Distributed under the Boost Software License, Version 1.0. #include #include // for preadv etc #include -#if LLFIO_USE_POSIX_AIO -#include -#endif + +#include "quickcpplib/signal_guard.hpp" LLFIO_V2_NAMESPACE_BEGIN @@ -44,7 +43,7 @@ constexpr inline void _check_iovec_match() static_assert(offsetof(io_handle::buffer_type, _len) == offsetof(iovec, iov_len), "buffer_type and struct iovec do not have same offset of len member"); } -size_t io_handle::max_buffers() const noexcept +size_t io_handle::_do_max_buffers() const noexcept { static size_t v; if(v == 0u) @@ -67,7 +66,7 @@ size_t io_handle::max_buffers() const noexcept return v; } -io_handle::io_result io_handle::read(io_handle::io_request reqs, deadline d) noexcept +io_handle::io_result io_handle::_do_read(io_handle::io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); if(d && !_v.is_nonblocking()) @@ -123,23 +122,26 @@ io_handle::io_result io_handle::read(io_handle::io_requ do { bytesread = ::readv(_v.fd, iov, reqs.buffers.size()); - if(bytesread < 0) + if(bytesread <= 0) { - if(EWOULDBLOCK != errno && EAGAIN != errno) + if(bytesread < 0 && EWOULDBLOCK != errno && EAGAIN != errno) { return posix_error(); } - LLFIO_POSIX_DEADLINE_TO_SLEEP_LOOP(d); - int mstimeout = (timeout == nullptr) ? -1 : (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000LL); - pollfd p; - memset(&p, 0, sizeof(p)); - p.fd = _v.fd; - p.events = POLLIN | POLLERR; - if(-1 == ::poll(&p, 1, mstimeout)) + if(!d || !d.steady || d.nsecs != 0) { - return posix_error(); + LLFIO_POSIX_DEADLINE_TO_SLEEP_LOOP(d); + int mstimeout = (timeout == nullptr) ? -1 : (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000LL); + pollfd p; + memset(&p, 0, sizeof(p)); + p.fd = _v.fd; + p.events = POLLIN | POLLERR; + if(-1 == ::poll(&p, 1, mstimeout)) + { + return posix_error(); + } } - LLFIO_POSIX_DEADLINE_TO_TIMEOUT_LOOP(d) + LLFIO_POSIX_DEADLINE_TO_TIMEOUT_LOOP(d); } } while(bytesread <= 0); } @@ -160,7 +162,7 @@ io_handle::io_result io_handle::read(io_handle::io_requ return {reqs.buffers}; } -io_handle::io_result io_handle::write(io_handle::io_request reqs, deadline d) noexcept +io_handle::io_result io_handle::_do_write(io_handle::io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); if(d && !_v.is_nonblocking()) @@ -215,24 +217,33 @@ io_handle::io_result io_handle::write(io_handle:: { do { - byteswritten = ::writev(_v.fd, iov, reqs.buffers.size()); - if(byteswritten < 0) + // Can't guarantee that user code hasn't enabled SIGPIPE + byteswritten = QUICKCPPLIB_NAMESPACE::signal_guard::signal_guard( + QUICKCPPLIB_NAMESPACE::signal_guard::signalc_set::broken_pipe, [&] { return ::writev(_v.fd, iov, reqs.buffers.size()); }, + [&](const QUICKCPPLIB_NAMESPACE::signal_guard::raised_signal_info * /*unused*/) { + errno = EPIPE; + return -1; + }); + if(byteswritten <= 0) { - if(EWOULDBLOCK != errno && EAGAIN != errno) + if(byteswritten < 0 && EWOULDBLOCK != errno && EAGAIN != errno) { return posix_error(); } - LLFIO_POSIX_DEADLINE_TO_SLEEP_LOOP(d); - int mstimeout = (timeout == nullptr) ? -1 : (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000LL); - pollfd p; - memset(&p, 0, sizeof(p)); - p.fd = _v.fd; - p.events = POLLOUT | POLLERR; - if(-1 == ::poll(&p, 1, mstimeout)) + if(!d || !d.steady || d.nsecs != 0) { - return posix_error(); + LLFIO_POSIX_DEADLINE_TO_SLEEP_LOOP(d); + int mstimeout = (timeout == nullptr) ? -1 : (timeout->tv_sec * 1000 + timeout->tv_nsec / 1000000LL); + pollfd p; + memset(&p, 0, sizeof(p)); + p.fd = _v.fd; + p.events = POLLOUT | POLLERR; + if(-1 == ::poll(&p, 1, mstimeout)) + { + return posix_error(); + } } - LLFIO_POSIX_DEADLINE_TO_TIMEOUT_LOOP(d) + LLFIO_POSIX_DEADLINE_TO_TIMEOUT_LOOP(d); } } while(byteswritten <= 0); } @@ -253,7 +264,7 @@ io_handle::io_result io_handle::write(io_handle:: return {reqs.buffers}; } -io_handle::io_result io_handle::barrier(io_handle::io_request reqs, barrier_kind kind, deadline d) noexcept +io_handle::io_result io_handle::_do_barrier(io_handle::io_request reqs, barrier_kind kind, deadline d) noexcept { (void) kind; LLFIO_LOG_FUNCTION_CALL(this); diff --git a/include/llfio/v2.0/detail/impl/posix/io_service.ipp b/include/llfio/v2.0/detail/impl/posix/io_service.ipp deleted file mode 100644 index c68f40c4..00000000 --- a/include/llfio/v2.0/detail/impl/posix/io_service.ipp +++ /dev/null @@ -1,410 +0,0 @@ -/* Multiplex file i/o -(C) 2015-2017 Niall Douglas (4 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#include "../../../async_file_handle.hpp" - -#include -#if LLFIO_USE_POSIX_AIO -#include -#include -#if LLFIO_COMPILE_KQUEUES -#include -#include -#include -#endif -#endif - -LLFIO_V2_NAMESPACE_BEGIN - -static int interrupt_signal; -static struct sigaction interrupt_signal_handler_old_action; -struct ucontext; -static inline void interrupt_signal_handler(int /*unused*/, siginfo_t * /*unused*/, void * /*unused*/) -{ - // We do nothing, and aio_suspend should exit with EINTR -} - -int io_service::interruption_signal() noexcept -{ - return interrupt_signal; -} - -int io_service::set_interruption_signal(int signo) -{ - int ret = interrupt_signal; - if(interrupt_signal != 0) - { - if(sigaction(interrupt_signal, &interrupt_signal_handler_old_action, nullptr) < 0) - { - throw std::system_error(errno, std::system_category()); // NOLINT - } - interrupt_signal = 0; - } - if(signo != 0) - { -#if LLFIO_HAVE_REALTIME_SIGNALS - if(-1 == signo) - { - for(signo = SIGRTMIN; signo < SIGRTMAX; signo++) - { - struct sigaction sigact - { - }; - memset(&sigact, 0, sizeof(sigact)); - if(sigaction(signo, nullptr, &sigact) >= 0) - { - if(sigact.sa_handler == SIG_DFL) - { - break; - } - } - } - } -#endif - // Install process wide signal handler for signal - struct sigaction sigact - { - }; - memset(&sigact, 0, sizeof(sigact)); - sigact.sa_sigaction = &interrupt_signal_handler; - sigact.sa_flags = SA_SIGINFO; - sigemptyset(&sigact.sa_mask); - if(sigaction(signo, &sigact, &interrupt_signal_handler_old_action) < 0) - { - throw std::system_error(errno, std::system_category()); // NOLINT - } - interrupt_signal = signo; - } - return ret; -} - -void io_service::_block_interruption() noexcept -{ - if(_use_kqueues) - { - return; - } - assert(!_blocked_interrupt_signal); - sigset_t set{}; - sigemptyset(&set); - sigaddset(&set, interrupt_signal); - pthread_sigmask(SIG_BLOCK, &set, nullptr); - _blocked_interrupt_signal = interrupt_signal; - _need_signal = false; -} - -void io_service::_unblock_interruption() noexcept -{ - if(_use_kqueues) - { - return; - } - assert(_blocked_interrupt_signal); - if(_blocked_interrupt_signal != 0) - { - sigset_t set{}; - sigemptyset(&set); - sigaddset(&set, _blocked_interrupt_signal); - pthread_sigmask(SIG_UNBLOCK, &set, nullptr); - _blocked_interrupt_signal = 0; - _need_signal = true; - } -} - -io_service::io_service() - : _work_queued(0) -{ - _threadh = pthread_self(); -#if LLFIO_USE_POSIX_AIO - _use_kqueues = true; - _blocked_interrupt_signal = 0; -#if LLFIO_COMPILE_KQUEUES - _kqueueh = 0; -#error todo -#else - disable_kqueues(); -#endif -#else -#error todo -#endif -} - -io_service::~io_service() -{ - if(_work_queued != 0u) - { -#ifndef NDEBUG - fprintf(stderr, "WARNING: ~io_service() sees work still queued, blocking until no work queued\n"); -#endif - while(_work_queued != 0u) - { - std::this_thread::yield(); - } - } -#if LLFIO_USE_POSIX_AIO -#if LLFIO_COMPILE_KQUEUES - if(_kqueueh) - ::close(_kqueueh); -#endif - _aiocbsv.clear(); - if(pthread_self() == _threadh) - { - _unblock_interruption(); - } -#else -#error todo -#endif -} - -#if LLFIO_USE_POSIX_AIO -void io_service::disable_kqueues() -{ - if(_use_kqueues) - { - if(_work_queued != 0u) - { - throw std::runtime_error("Cannot disable kqueues if work is pending"); // NOLINT - } - if(pthread_self() != _threadh) - { - throw std::runtime_error("Cannot disable kqueues except from owning thread"); // NOLINT - } - // Is the global signal handler set yet? - if(interrupt_signal == 0) - { - set_interruption_signal(); - } - _use_kqueues = false; - // Block interruption on this thread - _block_interruption(); -// Prepare for aio_suspend -#ifdef AIO_LISTIO_MAX - _aiocbsv.reserve(AIO_LISTIO_MAX); -#else - _aiocbsv.reserve(16); -#endif - } -} -#endif - -result io_service::run_until(deadline d) noexcept -{ - if(_work_queued == 0u) - { - return false; - } - if(pthread_self() != _threadh) - { - return errc::operation_not_supported; - } - std::chrono::steady_clock::time_point began_steady; - std::chrono::system_clock::time_point end_utc; - if(d) - { - if(d.steady) - { - began_steady = std::chrono::steady_clock::now(); - } - else - { - end_utc = d.to_time_point(); - } - } - struct timespec *ts = nullptr, _ts{}; - memset(&_ts, 0, sizeof(_ts)); - bool done = false; - do - { - if(d) - { - std::chrono::nanoseconds ns{}; - if(d.steady) - { - ns = std::chrono::duration_cast((began_steady + std::chrono::nanoseconds(d.nsecs)) - std::chrono::steady_clock::now()); - } - else - { - ns = std::chrono::duration_cast(end_utc - std::chrono::system_clock::now()); - } - ts = &_ts; - if(ns.count() <= 0) - { - ts->tv_sec = 0; - ts->tv_nsec = 0; - } - else - { - ts->tv_sec = ns.count() / 1000000000ULL; - ts->tv_nsec = ns.count() % 1000000000ULL; - } - } - bool timedout = false; - // Unblock the interruption signal - _unblock_interruption(); - // Execute any pending posts - { - std::unique_lock g(_posts_lock); - if(!_posts.empty()) - { - post_info *pi = &_posts.front(); - g.unlock(); - pi->f(this); - _post_done(pi); - // We did work, so exit - // Block the interruption signal - _block_interruption(); - return _work_queued != 0; - } - } -#if LLFIO_USE_POSIX_AIO - int errcode = 0; - if(_use_kqueues) - { -#if LLFIO_COMPILE_KQUEUES -#error todo -#endif - } - else - { - if(aio_suspend(_aiocbsv.data(), _aiocbsv.size(), ts) < 0) - { - errcode = errno; - } - } - // Block the interruption signal - _block_interruption(); - if(errcode != 0) - { - switch(errcode) - { - case EAGAIN: - if(d) - { - timedout = true; - } - break; - case EINTR: - // Let him loop, recalculate any timeout and check for posts to be executed - break; - default: - return posix_error(errcode); - } - } - else - { - // Poll the outstanding aiocbs to see which are ready - for(auto &aiocb : _aiocbsv) - { - int ioerr = aio_error(aiocb); - if(EINPROGRESS == ioerr) - { - continue; - } - if(0 == ioerr) - { - // Scavenge the aio - int ioret = aio_return(aiocb); - if(ioret < 0) - { - return posix_error(); - } - // std::cout << "aiocb " << aiocb << " sees succesful return " << ioret << std::endl; - // The aiocb aio_sigevent.sigev_value.sival_ptr field will point to a file_handle::_io_state_type - auto io_state = static_cast(aiocb->aio_sigevent.sigev_value.sival_ptr); - assert(io_state); - io_state->_system_io_completion(0, ioret, &aiocb); - } - else - { - // Either cancelled or errored out - // std::cout << "aiocb " << aiocb << " sees failed return " << ioerr << std::endl; - // The aiocb aio_sigevent.sigev_value.sival_ptr field will point to a file_handle::_io_state_type - auto io_state = static_cast(aiocb->aio_sigevent.sigev_value.sival_ptr); - assert(io_state); - io_state->_system_io_completion(ioerr, 0, &aiocb); - } - } - // Eliminate any empty holes in the quick aiocbs vector - _aiocbsv.erase(std::remove(_aiocbsv.begin(), _aiocbsv.end(), nullptr), _aiocbsv.end()); - done = true; - } -#else -#error todo -#endif - if(timedout) - { - if(d.steady) - { - if(std::chrono::steady_clock::now() >= (began_steady + std::chrono::nanoseconds(d.nsecs))) - { - return errc::timed_out; - } - } - else - { - if(std::chrono::system_clock::now() >= end_utc) - { - return errc::timed_out; - } - } - } - } while(!done); - return _work_queued != 0; -} - -void io_service::_post(detail::function_ptr &&f) -{ - { - post_info pi(this, std::move(f)); - std::lock_guard g(_posts_lock); - _posts.push_back(std::move(pi)); - } - _work_enqueued(); -#if LLFIO_USE_POSIX_AIO - if(_use_kqueues) - { -#if LLFIO_COMPILE_KQUEUES -#error todo -#endif - } - else - { - // If run_until() is exactly between the unblock of the signal and the beginning - // of the aio_suspend(), we need to pump this until run_until() notices - while(_need_signal) - { - //# if LLFIO_HAVE_REALTIME_SIGNALS - // sigval val = { 0 }; - // pthread_sigqueue(_threadh, interrupt_signal, val); - //#else - pthread_kill(_threadh, interrupt_signal); - //# endif - } - } -#else -#error todo -#endif -} - -LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/posix/map_handle.ipp b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp index 59b64a97..2f383f49 100644 --- a/include/llfio/v2.0/detail/impl/posix/map_handle.ipp +++ b/include/llfio/v2.0/detail/impl/posix/map_handle.ipp @@ -213,7 +213,7 @@ native_handle_type map_handle::release() noexcept return {}; } -map_handle::io_result map_handle::barrier(map_handle::io_request reqs, barrier_kind kind, deadline d) noexcept +map_handle::io_result map_handle::_do_barrier(map_handle::io_request reqs, barrier_kind kind, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); byte *addr = _addr + reqs.offset; @@ -632,7 +632,7 @@ result map_handle::do_not_store(buffer_type region) noe return region; } -map_handle::io_result map_handle::read(io_request reqs, deadline /*d*/) noexcept +map_handle::io_result map_handle::_do_read(io_request reqs, deadline /*d*/) noexcept { LLFIO_LOG_FUNCTION_CALL(this); byte *addr = _addr + reqs.offset; @@ -653,7 +653,7 @@ map_handle::io_result map_handle::read(io_request map_handle::write(io_request reqs, deadline d) noexcept +map_handle::io_result map_handle::_do_write(io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); if(!!(_flag & section_handle::flag::write_via_syscall) && _section != nullptr && _section->backing() != nullptr) diff --git a/include/llfio/v2.0/detail/impl/storage_profile.ipp b/include/llfio/v2.0/detail/impl/storage_profile.ipp index ddae6345..44788bd7 100644 --- a/include/llfio/v2.0/detail/impl/storage_profile.ipp +++ b/include/llfio/v2.0/detail/impl/storage_profile.ipp @@ -391,13 +391,13 @@ namespace storage_profile { outcome atomic_rewrite_quantum(storage_profile &sp, file_handle &srch) noexcept { - if(sp.atomic_rewrite_quantum.value != static_cast(-1)) + if(sp.atomic_rewrite_quantum.value != static_cast(-1)) { return success(); } try { - using off_t = io_service::extent_type; + using off_t = io_handle::extent_type; sp.max_aligned_atomic_rewrite.value = 1; sp.atomic_rewrite_quantum.value = static_cast(-1); size_t size = srch.requires_aligned_io() ? @@ -451,7 +451,7 @@ namespace storage_profile { concurrency = 4; } - std::atomic atomic_rewrite_quantum(sp.atomic_rewrite_quantum.value); + std::atomic atomic_rewrite_quantum(sp.atomic_rewrite_quantum.value); std::atomic failed(false); for(unsigned no = 0; no < concurrency; no++) { @@ -596,7 +596,7 @@ namespace storage_profile { concurrency = 4; } - std::atomic max_aligned_atomic_rewrite(sp.max_aligned_atomic_rewrite.value); + std::atomic max_aligned_atomic_rewrite(sp.max_aligned_atomic_rewrite.value); std::atomic failed(false); for(unsigned no = 0; no < concurrency; no++) { @@ -685,7 +685,7 @@ namespace storage_profile outcome atomic_rewrite_offset_boundary(storage_profile &sp, file_handle &srch) noexcept { - if(sp.atomic_rewrite_offset_boundary.value != static_cast(-1)) + if(sp.atomic_rewrite_offset_boundary.value != static_cast(-1)) { return success(); } @@ -697,7 +697,7 @@ namespace storage_profile #endif try { - using off_t = io_service::extent_type; + using off_t = io_handle::extent_type; auto size = static_cast(sp.max_aligned_atomic_rewrite.value); auto maxsize = static_cast(sp.max_aligned_atomic_rewrite.value); if(size > 1024) @@ -757,7 +757,7 @@ namespace storage_profile { concurrency = 4; } - std::atomic atomic_rewrite_offset_boundary(sp.atomic_rewrite_offset_boundary.value); + std::atomic atomic_rewrite_offset_boundary(sp.atomic_rewrite_offset_boundary.value); std::atomic failed(false); for(unsigned no = 0; no < concurrency; no++) { diff --git a/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp b/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp deleted file mode 100644 index ee970a30..00000000 --- a/include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp +++ /dev/null @@ -1,277 +0,0 @@ -/* A handle to a file -(C) 2015-2017 Niall Douglas (7 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#include "../../../handle.hpp" -#include "import.hpp" - -LLFIO_V2_NAMESPACE_BEGIN - -async_file_handle::io_result async_file_handle::barrier(async_file_handle::io_request reqs, barrier_kind kind, deadline d) noexcept -{ - // Pass through the file_handle's implementation, it understands overlapped handles - return file_handle::barrier(reqs, kind, d); -} - -template result async_file_handle::_begin_io(span mem, async_file_handle::operation_t operation, async_file_handle::io_request reqs, async_file_handle::_erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept -{ - // Need to keep a set of OVERLAPPED matching the scatter-gather buffers - struct state_type : public _erased_io_state_type - { - OVERLAPPED ols[1]; - _erased_completion_handler *completion; - state_type(async_file_handle *_parent, operation_t _operation, bool must_deallocate_self, size_t _items) // NOLINT - : _erased_io_state_type(_parent, _operation, must_deallocate_self, _items), - completion(nullptr) - { - } - state_type(state_type &&) = delete; - state_type(const state_type &) = delete; - state_type &operator=(state_type &&) = delete; - state_type &operator=(const state_type &) = delete; - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC _erased_completion_handler *erased_completion_handler() noexcept override final { return completion; } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC void _system_io_completion(long errcode, long bytes_transferred, void *internal_state) noexcept override final - { - auto ol = static_cast(internal_state); - ol->hEvent = nullptr; - auto &result = this->result.write; - if(result) - { - if(errcode) - { - result = win32_error(errcode); - } - else - { - // Figure out which i/o I am and update the buffer in question - size_t idx = ol - ols; - if(idx >= this->items) - { - LLFIO_LOG_FATAL(0, "async_file_handle::io_state::operator() called with invalid index"); - std::terminate(); - } - result.value()[idx] = {result.value()[idx].data(), (size_t) bytes_transferred}; - } - } - this->parent->service()->_work_done(); - // Are we done? - if(!--this->items_to_go) - { - (*completion)(this); - } - } - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~state_type() override final - { - // Do we need to cancel pending i/o? - if(this->items_to_go) - { - for(size_t n = 0; n < this->items; n++) - { - // If this is non-zero, probably this i/o still in flight - if(ols[n].hEvent) - { - CancelIoEx(this->parent->native_handle().h, ols + n); - } - } - // Pump the i/o service until all pending i/o is completed - while(this->items_to_go) - { - auto res = this->parent->service()->run(); - (void) res; -#ifndef NDEBUG - if(res.has_error()) - { - LLFIO_LOG_FATAL(0, "async_file_handle: io_service failed"); - std::terminate(); - } - if(!res.value()) - { - LLFIO_LOG_FATAL(0, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - } - completion->~_erased_completion_handler(); - } - } * state; - extent_type offset = reqs.offset; - size_t statelen = sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(OVERLAPPED) + completion.bytes(); - if(!mem.empty() && statelen > mem.size()) - { - return errc::not_enough_memory; - } - size_t items(reqs.buffers.size()); - // On Windows i/o must be scheduled on the same thread pumping completion - if(GetCurrentThreadId() != service()->_threadid) - { - return errc::operation_not_supported; - } - - bool must_deallocate_self = false; - if(mem.empty()) - { - void *_mem = ::calloc(1, statelen); // NOLINT - if(!_mem) - { - return errc::not_enough_memory; - } - mem = {static_cast(_mem), statelen}; - must_deallocate_self = true; - } - io_state_ptr _state(reinterpret_cast(mem.data())); - new((state = reinterpret_cast(mem.data()))) state_type(this, operation, must_deallocate_self, items); - state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast(state) + sizeof(state_type) + (reqs.buffers.size() - 1) * sizeof(OVERLAPPED)); - completion.move(state->completion); - - // To be called once each buffer is read - struct handle_completion - { - static VOID CALLBACK Do(DWORD errcode, DWORD bytes_transferred, LPOVERLAPPED ol) - { - auto *state = reinterpret_cast(ol->hEvent); - state->_system_io_completion(errcode, bytes_transferred, ol); - } - }; - // Noexcept move the buffers from req into result - auto &out = state->result.write.value(); - out = std::move(reqs.buffers); - for(size_t n = 0; n < items; n++) - { - LPOVERLAPPED ol = state->ols + n; - ol->Internal = static_cast(-1); - if(_v.is_append_only()) - { - ol->OffsetHigh = ol->Offset = 0xffffffff; - } - else - { -#ifndef NDEBUG - if(_v.requires_aligned_io()) - { - assert((offset & 511) == 0); - } -#endif - ol->Offset = offset & 0xffffffff; - ol->OffsetHigh = (offset >> 32) & 0xffffffff; - } - // Use the unused hEvent member to pass through the state - ol->hEvent = reinterpret_cast(state); - offset += out[n].size(); - ++state->items_to_go; -#ifndef NDEBUG - if(_v.requires_aligned_io()) - { - assert((reinterpret_cast(out[n].data()) & 511) == 0); - assert((out[n].size() & 511) == 0); - } -#endif - if(!ioroutine(_v.h, const_cast(out[n].data()), static_cast(out[n].size()), ol, handle_completion::Do)) - { - --state->items_to_go; - state->result.write = win32_error(); - // Fire completion now if we didn't schedule anything - if(!n) - { - (*state->completion)(state); - } - return _state; - } - service()->_work_enqueued(); - } - return _state; -} - -result async_file_handle::_begin_io(span mem, async_file_handle::operation_t operation, io_request reqs, async_file_handle::_erased_completion_handler &&completion) noexcept -{ - switch(operation) - { - case operation_t::read: - return _begin_io(mem, operation, reqs, std::move(completion), ReadFileEx); - case operation_t::write: - return _begin_io(mem, operation, reqs, std::move(completion), WriteFileEx); - case operation_t::fsync_async: - case operation_t::dsync_async: - case operation_t::fsync_sync: - case operation_t::dsync_sync: - break; - } - return errc::operation_not_supported; -} - -async_file_handle::io_result async_file_handle::read(async_file_handle::io_request reqs, deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - optional> ret; - OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result &&result) { ret = std::move(result); })); - (void) io_state; // holds i/o open until it completes - - // While i/o is not done pump i/o completion - while(!ret) - { - auto t(_service->run_until(d)); - // If i/o service pump failed or timed out, cancel outstanding i/o and return - if(!t) - { - return std::move(t).error(); - } -#ifndef NDEBUG - if(!ret && t && !t.value()) - { - LLFIO_LOG_FATAL(_v.h, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - return std::move(*ret); -} - -async_file_handle::io_result async_file_handle::write(async_file_handle::io_request reqs, deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - optional> ret; - OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result &&result) { ret = std::move(result); })); - (void) io_state; // holds i/o open until it completes - - // While i/o is not done pump i/o completion - while(!ret) - { - auto t(_service->run_until(d)); - // If i/o service pump failed or timed out, cancel outstanding i/o and return - if(!t) - { - return std::move(t).error(); - } -#ifndef NDEBUG - if(!ret && t && !t.value()) - { - LLFIO_LOG_FATAL(_v.h, "async_file_handle: io_service returns no work when i/o has not completed"); - std::terminate(); - } -#endif - } - return std::move(*ret); -} - - -LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp index dfcc9ab4..9b3d2dcf 100644 --- a/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/directory_handle.ipp @@ -113,7 +113,7 @@ result directory_handle::directory(const path_handle &base, pa } if(ntstat < 0) { - if(creation::always_new == _creation && 0xc0000035 /*STATUS_OBJECT_NAME_COLLISION*/ == ntstat) + if(creation::always_new == _creation && (NTSTATUS) 0xc0000035 /*STATUS_OBJECT_NAME_COLLISION*/ == ntstat) { return errc::directory_not_empty; } diff --git a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp index 501e0f88..5af8817d 100644 --- a/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/fs_handle.ipp @@ -207,8 +207,9 @@ result fs_handle::unlink(deadline d) noexcept if(h.is_symlink()) ntflags |= 0x00200000 /*FILE_OPEN_REPARSE_POINT*/; NTSTATUS ntstat = NtOpenFile(&duph, SYNCHRONIZE | DELETE, &oa, &isb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, ntflags); - // Can't duplicate the handle of a pipe not in the listening state - if(ntstat < 0 && !h.is_pipe()) + // Note that Windows appears to not permit renaming nor deletion of pipe handles right now, + // so do nothing about the STATUS_PIPE_NOT_AVAILABLE failure here + if(ntstat < 0) { return ntkernel_error(ntstat); } @@ -235,7 +236,7 @@ result fs_handle::unlink(deadline d) noexcept } if(failed) { - if((h.is_regular() || h.is_symlink() || h.is_pipe()) && !(h.flags() & flag::win_disable_unlink_emulation)) + if((h.is_regular() || h.is_symlink()) && !(h.flags() & flag::win_disable_unlink_emulation)) { // Rename it to something random to emulate immediate unlinking std::string randomname; diff --git a/include/llfio/v2.0/detail/impl/windows/handle.ipp b/include/llfio/v2.0/detail/impl/windows/handle.ipp index 3fff259d..f6f68256 100644 --- a/include/llfio/v2.0/detail/impl/windows/handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/handle.ipp @@ -102,7 +102,7 @@ result handle::close() noexcept result handle::clone() const noexcept { LLFIO_LOG_FUNCTION_CALL(this); - result ret(handle(native_handle_type(), _caching, _flags)); + result ret(handle(native_handle_type(), kernel_caching(), _flags)); ret.value()._v.behaviour = _v.behaviour; if(DuplicateHandle(GetCurrentProcess(), _v.h, GetCurrentProcess(), &ret.value()._v.h, 0, 0, DUPLICATE_SAME_ACCESS) == 0) { diff --git a/include/llfio/v2.0/detail/impl/windows/import.hpp b/include/llfio/v2.0/detail/impl/windows/import.hpp index 5429da98..1cc75e82 100644 --- a/include/llfio/v2.0/detail/impl/windows/import.hpp +++ b/include/llfio/v2.0/detail/impl/windows/import.hpp @@ -238,7 +238,7 @@ namespace windows_nt_kernel PVOID SecurityQualityOfService; } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; - using PIO_APC_ROUTINE = void(NTAPI *)(IN PVOID ApcContext, IN PIO_STATUS_BLOCK IoStatusBlock, IN ULONG Reserved); + using PIO_APC_ROUTINE = void(NTAPI *)(_In_ PVOID ApcContext, _In_ PIO_STATUS_BLOCK IoStatusBlock, _In_ ULONG Reserved); typedef struct _IMAGEHLP_LINE64 // NOLINT { @@ -297,8 +297,18 @@ namespace windows_nt_kernel using NtCreateFile_t = NTSTATUS(NTAPI *)(_Out_ PHANDLE FileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER AllocationSize, _In_ ULONG FileAttributes, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_ ULONG CreateOptions, _In_opt_ PVOID EaBuffer, _In_ ULONG EaLength); + using NtReadFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID Buffer, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key); + + using NtReadFileScatter_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ FILE_SEGMENT_ELEMENT SegmentArray, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key); + + using NtWriteFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PVOID Buffer, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key); + + using NtWriteFileGather_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ FILE_SEGMENT_ELEMENT SegmentArray, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key); + using NtDeleteFile_t = NTSTATUS(NTAPI *)(_In_ POBJECT_ATTRIBUTES ObjectAttributes); + using NtCancelIoFileEx_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _Out_ PIO_STATUS_BLOCK IoRequestToCancel, _Out_ PIO_STATUS_BLOCK IoStatusBlock); + using NtClose_t = NTSTATUS(NTAPI *)(_Out_ HANDLE FileHandle); // From https://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/NT%20Objects/File/NtCreateNamedPipeFile.html @@ -328,6 +338,19 @@ namespace windows_nt_kernel using NtDelayExecution_t = NTSTATUS(NTAPI *)(_In_ BOOLEAN Alertable, _In_opt_ LARGE_INTEGER *Interval); + using NtSetIoCompletion_t = NTSTATUS(NTAPI *)(_In_ HANDLE IoCompletionHandle, _In_ ULONG KeyContext, _In_ PVOID ApcContext, _In_ NTSTATUS IoStatus, _In_ ULONG IoStatusInformation); + + using NtRemoveIoCompletion_t = NTSTATUS(NTAPI *)(_In_ HANDLE IoCompletionHandle, _Out_ PVOID* CompletionKey, _Out_ PVOID* ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER Timeout); + + typedef struct _FILE_IO_COMPLETION_INFORMATION + { + PVOID KeyContext; + PVOID ApcContext; + IO_STATUS_BLOCK IoStatusBlock; + } FILE_IO_COMPLETION_INFORMATION, *PFILE_IO_COMPLETION_INFORMATION; + + using NtRemoveIoCompletionEx_t = NTSTATUS(NTAPI *)(_In_ HANDLE IoCompletionHandle, PFILE_IO_COMPLETION_INFORMATION IoCompletionInformation, _In_ ULONG Count, _Out_ PULONG NumEntriesRemoved, _In_opt_ PLARGE_INTEGER Timeout, _In_ BOOLEAN Alertable); + // From https://msdn.microsoft.com/en-us/library/windows/hardware/ff566474(v=vs.85).aspx using NtLockFile_t = NTSTATUS(NTAPI *)(_In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PLARGE_INTEGER ByteOffset, _In_ PLARGE_INTEGER Length, _In_ ULONG Key, _In_ BOOLEAN FailImmediately, _In_ BOOLEAN ExclusiveLock); @@ -573,6 +596,12 @@ namespace windows_nt_kernel ULONG Flags; } FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX; + typedef struct _FILE_COMPLETION_INFORMATION // NOLINT + { + HANDLE Port; + PVOID Key; + } FILE_COMPLETION_INFORMATION, *PFILE_COMPLETION_INFORMATION; + typedef struct _FILE_ALL_INFORMATION // NOLINT { FILE_BASIC_INFORMATION BasicInformation; @@ -690,6 +719,11 @@ namespace windows_nt_kernel static NtOpenDirectoryObject_t NtOpenDirectoryObject; static NtOpenFile_t NtOpenFile; static NtCreateFile_t NtCreateFile; + static NtReadFile_t NtReadFile; + static NtReadFileScatter_t NtReadFileScatter; + static NtWriteFile_t NtWriteFile; + static NtWriteFileGather_t NtWriteFileGather; + static NtCancelIoFileEx_t NtCancelIoFileEx; static NtDeleteFile_t NtDeleteFile; static NtClose_t NtClose; static NtCreateNamedPipeFile_t NtCreateNamedPipeFile; @@ -698,6 +732,9 @@ namespace windows_nt_kernel static NtWaitForSingleObject_t NtWaitForSingleObject; static NtWaitForMultipleObjects_t NtWaitForMultipleObjects; static NtDelayExecution_t NtDelayExecution; + static NtSetIoCompletion_t NtSetIoCompletion; + static NtRemoveIoCompletion_t NtRemoveIoCompletion; + static NtRemoveIoCompletionEx_t NtRemoveIoCompletionEx; static NtLockFile_t NtLockFile; static NtUnlockFile_t NtUnlockFile; static NtCreateSection_t NtCreateSection; @@ -788,6 +825,41 @@ namespace windows_nt_kernel abort(); } } + if(NtReadFile == nullptr) + { + if((NtReadFile = reinterpret_cast(GetProcAddress(ntdllh, "NtReadFile"))) == nullptr) + { + abort(); + } + } + if(NtReadFileScatter == nullptr) + { + if((NtReadFileScatter = reinterpret_cast(GetProcAddress(ntdllh, "NtReadFileScatter"))) == nullptr) + { + abort(); + } + } + if(NtWriteFile == nullptr) + { + if((NtWriteFile = reinterpret_cast(GetProcAddress(ntdllh, "NtWriteFile"))) == nullptr) + { + abort(); + } + } + if(NtWriteFileGather == nullptr) + { + if((NtWriteFileGather = reinterpret_cast(GetProcAddress(ntdllh, "NtWriteFileGather"))) == nullptr) + { + abort(); + } + } + if(NtCancelIoFileEx == nullptr) + { + if((NtCancelIoFileEx = reinterpret_cast(GetProcAddress(ntdllh, "NtCancelIoFileEx"))) == nullptr) + { + abort(); + } + } if(NtDeleteFile == nullptr) { if((NtDeleteFile = reinterpret_cast(GetProcAddress(ntdllh, "NtDeleteFile"))) == nullptr) @@ -844,6 +916,27 @@ namespace windows_nt_kernel abort(); } } + if(NtSetIoCompletion == nullptr) + { + if((NtSetIoCompletion = reinterpret_cast(GetProcAddress(ntdllh, "NtSetIoCompletion"))) == nullptr) + { + abort(); + } + } + if(NtRemoveIoCompletion == nullptr) + { + if((NtRemoveIoCompletion = reinterpret_cast(GetProcAddress(ntdllh, "NtRemoveIoCompletion"))) == nullptr) + { + abort(); + } + } + if(NtRemoveIoCompletionEx == nullptr) + { + if((NtRemoveIoCompletionEx = reinterpret_cast(GetProcAddress(ntdllh, "NtRemoveIoCompletionEx"))) == nullptr) + { + abort(); + } + } if(NtLockFile == nullptr) { if((NtLockFile = reinterpret_cast(GetProcAddress(ntdllh, "NtLockFile"))) == nullptr) @@ -1241,26 +1334,62 @@ inline windows_nt_kernel::IO_STATUS_BLOCK make_iostatus() noexcept return isb; } +inline NTSTATUS ntcancel_pending_io(HANDLE h, windows_nt_kernel::IO_STATUS_BLOCK &isb) noexcept +{ + windows_nt_kernel::init(); + using namespace windows_nt_kernel; + if(isb.Status != 0x103 /*STATUS_PENDING*/) + { + return isb.Status; + } + NTSTATUS ntstat = NtCancelIoFileEx(h, &isb, &isb); + if(ntstat < 0) + { + if(ntstat == (NTSTATUS) 0xC0000225 /*STATUS_NOT_FOUND*/) + { + // In the moment between the check of isb.Status and NtCancelIoFileEx() + // the i/o completed, so consider this a success. + isb.Status = (NTSTATUS) 0xC0000120 /*STATUS_CANCELLED*/; + return isb.Status; + } + return ntstat; + } + if(isb.Status == 0) + { + isb.Status = 0xC0000120 /*STATUS_CANCELLED*/; + } + return isb.Status; +} + // Wait for an overlapped handle to complete a specific operation inline NTSTATUS ntwait(HANDLE h, windows_nt_kernel::IO_STATUS_BLOCK &isb, const deadline &d) noexcept { windows_nt_kernel::init(); using namespace windows_nt_kernel; + if(isb.Status != 0x103 /*STATUS_PENDING*/) + { + assert(isb.Status != -1); // should not call ntwait() if operation failed to start + // He's done already (or never started) + return isb.Status; + } LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d); do // needs to be a do, not while in order to flip auto reset event objects etc. { LLFIO_WIN_DEADLINE_TO_SLEEP_LOOP(d); - // Pump alerts and APCs - NTSTATUS ntstat = NtWaitForSingleObject(h, 1u, timeout); + NTSTATUS ntstat = NtWaitForSingleObject(h, 0u, timeout); if(STATUS_TIMEOUT == ntstat) { - auto expected = static_cast(-1); - // Have to be very careful here, atomically swap timed out for the -1 only - InterlockedCompareExchange(&isb.Status, ntstat, expected); - // If it's no longer -1 or the i/o completes, that's fine. - return isb.Status; + // If he'll still pending, we must cancel the i/o + ntstat = ntcancel_pending_io(h, isb); + if(ntstat < 0 && ntstat != (NTSTATUS) 0xC0000120 /*STATUS_CANCELLED*/) + { + LLFIO_LOG_FATAL(nullptr, "Failed to cancel earlier i/o"); + abort(); + } + isb.Status = ntstat = STATUS_TIMEOUT; + return ntstat; } - } while(isb.Status == -1); + } while(isb.Status == 0x103 /*STATUS_PENDING*/); return isb.Status; } inline NTSTATUS ntwait(HANDLE h, OVERLAPPED &ol, const deadline &d) noexcept diff --git a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp index 0f7dbef9..b59e9cfd 100644 --- a/include/llfio/v2.0/detail/impl/windows/io_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/io_handle.ipp @@ -27,48 +27,80 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_BEGIN -size_t io_handle::max_buffers() const noexcept +size_t io_handle::_do_max_buffers() const noexcept { - return 1; // async_file_handle may override this virtual function + return 1; // TODO FIXME support ReadFileScatter/WriteFileGather } -template inline io_handle::io_result do_read_write(const native_handle_type &nativeh, Syscall &&syscall, io_handle::io_request reqs, deadline d) noexcept +template inline bool do_cancel(const native_handle_type &nativeh, span ols, io_handle::io_request reqs) noexcept { + using namespace windows_nt_kernel; + using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK; + bool did_cancel = false; + ols = span(ols.data(), reqs.buffers.size()); + for(auto &ol : ols) + { + if(ol.Status == -1) + { + // No need to cancel an i/o never begun + continue; + } + NTSTATUS ntstat = ntcancel_pending_io(nativeh.h, (IO_STATUS_BLOCK &) ol); + if(ntstat < 0 && ntstat != (NTSTATUS) 0xC0000120 /*STATUS_CANCELLED*/) + { + LLFIO_LOG_FATAL(nullptr, "Failed to cancel earlier i/o"); + abort(); + } + if(ntstat == (NTSTATUS) 0xC0000120 /*STATUS_CANCELLED*/) + { + did_cancel = true; + } + } + return did_cancel; +} + +template +inline bool do_read_write(io_handle::io_result &ret, size_t &scheduled, Syscall &&syscall, const native_handle_type &nativeh, windows_nt_kernel::PIO_APC_ROUTINE routine, detail::io_operation_connection *op, span ols, + io_handle::io_request reqs, deadline d) noexcept +{ + using namespace windows_nt_kernel; + using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK; if(d && !nativeh.is_nonblocking()) { - return errc::not_supported; + ret=errc::not_supported; + return true; } if(reqs.buffers.size() > 64) { - return errc::argument_list_too_long; + ret=errc::argument_list_too_long; + return true; } - LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d); - std::array _ols{}; - memset(_ols.data(), 0, reqs.buffers.size() * sizeof(OVERLAPPED)); - span ols(_ols.data(), reqs.buffers.size()); + ols = span(ols.data(), reqs.buffers.size()); + memset(ols.data(), 0, reqs.buffers.size() * sizeof(EIOSB)); auto ol_it = ols.begin(); - DWORD transferred = 0; + for(auto &req : reqs.buffers) + { + EIOSB &ol = *ol_it++; + ol.Status = -1; + } auto cancel_io = undoer([&] { if(nativeh.is_nonblocking()) { - for(auto &ol : ols) - { - CancelIoEx(nativeh.h, &ol); - } - for(auto &ol : ols) + if(ol_it != ols.begin() + 1) { - ntwait(nativeh.h, ol, deadline()); + do_cancel(nativeh, ols, reqs); } } }); + ol_it = ols.begin(); for(auto &req : reqs.buffers) { - OVERLAPPED &ol = *ol_it++; - ol.Internal = static_cast(-1); + EIOSB &ol = *ol_it++; + LARGE_INTEGER offset; if(nativeh.is_append_only()) { - ol.OffsetHigh = ol.Offset = 0xffffffff; + offset.QuadPart = -1; } else { @@ -78,8 +110,7 @@ template inline io_handle::io_result> 32) & 0xffffffff; - ol.Offset = reqs.offset & 0xffffffff; + offset.QuadPart = reqs.offset; } #ifndef NDEBUG if(nativeh.requires_aligned_io()) @@ -88,52 +119,96 @@ template inline io_handle::io_result(req.size()), &transferred, &ol) && ERROR_IO_PENDING != GetLastError()) + reqs.offset += req.size(); + ol.Status = 0x103 /*STATUS_PENDING*/; + NTSTATUS ntstat = syscall(nativeh.h, nullptr, routine, op, (PIO_STATUS_BLOCK) &ol, (PVOID) req.data(), static_cast(req.size()), &offset, nullptr); + if(ntstat < 0 && ntstat != 0x103 /*STATUS_PENDING*/) { - return win32_error(); + InterlockedCompareExchange(&ol.Status, ntstat, 0x103 /*STATUS_PENDING*/); + ret=ntkernel_error(ntstat); + return true; } - reqs.offset += req.size(); + ++scheduled; } // If handle is overlapped, wait for completion of each i/o. - if(nativeh.is_nonblocking()) + if(nativeh.is_nonblocking() && blocking) { for(auto &ol : ols) { deadline nd; LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); - if(STATUS_TIMEOUT == ntwait(nativeh.h, ol, nd)) + if(STATUS_TIMEOUT == ntwait(nativeh.h, (IO_STATUS_BLOCK &) ol, nd)) { - LLFIO_WIN_DEADLINE_TO_TIMEOUT_LOOP(d); + // ntwait cancels the i/o, undoer will cancel all the other i/o + auto r = [&]() -> result { + LLFIO_WIN_DEADLINE_TO_TIMEOUT_LOOP(d); + return success(); + }(); + if(!r) + { + ret = r.error(); + return true; + } } } } cancel_io.dismiss(); + if(!blocking) + { + // If all the operations already completed, great + for(size_t n = 0; n < reqs.buffers.size(); n++) + { + if(ols[n].Status == static_cast(0x103 /*STATUS_PENDING*/)) + { + return false; // at least one buffer is not completed yet + } + } + } + ret = {reqs.buffers.data(), 0}; for(size_t n = 0; n < reqs.buffers.size(); n++) { - // It seems the NT kernel is guilty of casting bugs sometimes - ols[n].Internal = ols[n].Internal & 0xffffffff; - if(ols[n].Internal != 0) + assert(ols[n].Status != -1); + if(ols[n].Status < 0) + { + ret= ntkernel_error(static_cast(ols[n].Status)); + return true; + } + reqs.buffers[n] = {reqs.buffers[n].data(), ols[n].Information}; + if(reqs.buffers[n].size() != 0) { - return ntkernel_error(static_cast(ols[n].Internal)); + ret = {reqs.buffers.data(), n + 1}; } - reqs.buffers[n] = {reqs.buffers[n].data(), ols[n].InternalHigh}; } - return io_handle::io_result(std::move(reqs.buffers)); + return true; } -io_handle::io_result io_handle::read(io_handle::io_request reqs, deadline d) noexcept +io_handle::io_result io_handle::_do_read(io_handle::io_request reqs, deadline d) noexcept { + windows_nt_kernel::init(); + using namespace windows_nt_kernel; LLFIO_LOG_FUNCTION_CALL(this); - return do_read_write(_v, &ReadFile, reqs, d); + using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK; + std::array _ols{}; + io_handle::io_result ret(reqs.buffers); + size_t scheduled = 0; + do_read_write(ret, scheduled, NtReadFile, _v, nullptr, nullptr, {_ols.data(), _ols.size()}, reqs, d); + return ret; } -io_handle::io_result io_handle::write(io_handle::io_request reqs, deadline d) noexcept +io_handle::io_result io_handle::_do_write(io_handle::io_request reqs, deadline d) noexcept { + windows_nt_kernel::init(); + using namespace windows_nt_kernel; LLFIO_LOG_FUNCTION_CALL(this); - return do_read_write(_v, &WriteFile, reqs, d); + using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK; + std::array _ols{}; + io_handle::io_result ret(reqs.buffers); + size_t scheduled = 0; + do_read_write(ret, scheduled, NtWriteFile, _v, nullptr, nullptr, {_ols.data(), _ols.size()}, reqs, d); + return ret; } -io_handle::io_result io_handle::barrier(io_handle::io_request reqs, barrier_kind kind, deadline d) noexcept +io_handle::io_result io_handle::_do_barrier(io_handle::io_request reqs, barrier_kind kind, deadline d) noexcept { windows_nt_kernel::init(); using namespace windows_nt_kernel; @@ -143,7 +218,8 @@ io_handle::io_result io_handle::barrier(io_handle return errc::not_supported; } LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d); - OVERLAPPED ol{}; + using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK; + EIOSB ol{}; memset(&ol, 0, sizeof(ol)); auto *isb = reinterpret_cast(&ol); *isb = make_iostatus(); @@ -159,10 +235,9 @@ io_handle::io_result io_handle::barrier(io_handle NTSTATUS ntstat = NtFlushBuffersFileEx(_v.h, flags, nullptr, 0, isb); if(STATUS_PENDING == ntstat) { - ntstat = ntwait(_v.h, ol, d); + ntstat = ntwait(_v.h, (IO_STATUS_BLOCK &) ol, d); if(STATUS_TIMEOUT == ntstat) { - CancelIoEx(_v.h, &ol); return errc::timed_out; } } diff --git a/include/llfio/v2.0/detail/impl/windows/io_service.ipp b/include/llfio/v2.0/detail/impl/windows/io_service.ipp deleted file mode 100644 index 9832fff1..00000000 --- a/include/llfio/v2.0/detail/impl/windows/io_service.ipp +++ /dev/null @@ -1,102 +0,0 @@ -/* Multiplex file i/o -(C) 2015-2017 Niall Douglas (4 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#include "../../../io_service.hpp" -#include "import.hpp" - -LLFIO_V2_NAMESPACE_BEGIN - -io_service::io_service() - : _work_queued(0) -{ - LLFIO_LOG_FUNCTION_CALL(this); - if(DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &_threadh, 0, 0, DUPLICATE_SAME_ACCESS) == 0) - { - throw std::runtime_error("Failed to create creating thread handle"); - } - _threadid = GetCurrentThreadId(); -} - -io_service::~io_service() -{ - LLFIO_LOG_FUNCTION_CALL(this); - if(_work_queued != 0u) - { - fprintf(stderr, "WARNING: ~io_service() sees work still queued, blocking until no work queued\n"); - while(_work_queued != 0u) - { - std::this_thread::yield(); - } - } - CloseHandle(_threadh); -} - -result io_service::run_until(deadline d) noexcept -{ - LLFIO_LOG_FUNCTION_CALL(this); - if(_work_queued == 0u) - { - return false; - } - if(GetCurrentThreadId() != _threadid) - { - return errc::operation_not_supported; - } - ntsleep(d, true); - return _work_queued != 0; -} - -void io_service::_post(detail::function_ptr &&f) -{ - LLFIO_LOG_FUNCTION_CALL(this); - void *data = nullptr; - { - post_info pi(this, std::move(f)); - std::lock_guard g(_posts_lock); - _posts.push_back(std::move(pi)); - data = static_cast(&_posts.back()); - } - // lambdas can't be __stdcall on winclang, so ... - struct lambda - { - static void __stdcall _(ULONG_PTR data) - { - auto *pi = reinterpret_cast(data); - pi->f(pi->service); - pi->service->_post_done(pi); - } - }; - PAPCFUNC apcf = lambda::_; - if(QueueUserAPC(apcf, _threadh, reinterpret_cast(data)) != 0u) - { - _work_enqueued(); - } - else - { - auto *pi = static_cast(data); - pi->service->_post_done(pi); - } -} - -LLFIO_V2_NAMESPACE_END diff --git a/include/llfio/v2.0/detail/impl/windows/map_handle.ipp b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp index 59ec8701..8e441aab 100644 --- a/include/llfio/v2.0/detail/impl/windows/map_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/map_handle.ipp @@ -506,7 +506,7 @@ native_handle_type map_handle::release() noexcept return {}; } -map_handle::io_result map_handle::barrier(map_handle::io_request reqs, barrier_kind kind, deadline d) noexcept +map_handle::io_result map_handle::_do_barrier(map_handle::io_request reqs, barrier_kind kind, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); byte *addr = _addr + reqs.offset; @@ -893,7 +893,7 @@ result map_handle::do_not_store(buffer_type region) noe return region; } -map_handle::io_result map_handle::read(io_request reqs, deadline /*d*/) noexcept +map_handle::io_result map_handle::_do_read(io_request reqs, deadline /*d*/) noexcept { LLFIO_LOG_FUNCTION_CALL(this); byte *addr = _addr + reqs.offset; @@ -914,7 +914,7 @@ map_handle::io_result map_handle::read(io_request map_handle::write(io_request reqs, deadline d) noexcept +map_handle::io_result map_handle::_do_write(io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); if(!!(_flag & section_handle::flag::write_via_syscall) && _section != nullptr && _section->backing() != nullptr) diff --git a/include/llfio/v2.0/detail/impl/windows/pipe_handle.ipp b/include/llfio/v2.0/detail/impl/windows/pipe_handle.ipp index 42fb3826..6ee3ee44 100644 --- a/include/llfio/v2.0/detail/impl/windows/pipe_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/pipe_handle.ipp @@ -31,6 +31,7 @@ result pipe_handle::pipe(pipe_handle::path_view_type path, pipe_han { windows_nt_kernel::init(); using namespace windows_nt_kernel; + flags &= ~flag::unlink_on_first_close; result ret(pipe_handle(native_handle_type(), 0, 0, _caching, flags)); native_handle_type &nativeh = ret.value()._v; LLFIO_LOG_FUNCTION_CALL(&ret); @@ -72,13 +73,20 @@ result pipe_handle::pipe(pipe_handle::path_view_type path, pipe_han path_view::c_str<> zpath(path, true); UNICODE_STRING _path{}; - _path.Buffer = const_cast(zpath.buffer); - _path.MaximumLength = (_path.Length = static_cast(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t); - if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\') + if(path.empty()) { - _path.Buffer += 3; - _path.Length -= 3 * sizeof(wchar_t); - _path.MaximumLength -= 3 * sizeof(wchar_t); + memset(&_path, 0, sizeof(_path)); + } + else + { + _path.Buffer = const_cast(zpath.buffer); + _path.MaximumLength = (_path.Length = static_cast(zpath.length * sizeof(wchar_t))) + sizeof(wchar_t); + if(zpath.length >= 4 && _path.Buffer[0] == '\\' && _path.Buffer[1] == '!' && _path.Buffer[2] == '!' && _path.Buffer[3] == '\\') + { + _path.Buffer += 3; + _path.Length -= 3 * sizeof(wchar_t); + _path.MaximumLength -= 3 * sizeof(wchar_t); + } } OBJECT_ATTRIBUTES oa{}; @@ -107,7 +115,7 @@ result pipe_handle::pipe(pipe_handle::path_view_type path, pipe_han } // If writable and not readable, fail if other end is not connected // This matches full duplex pipe behaviour on Linux - if(nativeh.is_readable() && nativeh.is_writable() && 0xC00000AE /*STATUS_PIPE_BUSY*/ == ntstat) + if(nativeh.is_readable() && nativeh.is_writable() && (NTSTATUS) 0xC00000AE /*STATUS_PIPE_BUSY*/ == ntstat) { return errc::no_such_device_or_address; // ENXIO, as per Linux } @@ -118,7 +126,7 @@ result pipe_handle::pipe(pipe_handle::path_view_type path, pipe_han } // loop } - ret.value()._set_is_connected(true); + ret.value()._is_connected=true; } else { @@ -138,56 +146,75 @@ result pipe_handle::pipe(pipe_handle::path_view_type path, pipe_han { return ntkernel_error(ntstat); } - ret.value()._flags |= flag::unlink_on_first_close; - } - // If opening a pipe for reading and not writing, and this pipe is blocking, - // block until the other end opens for write - if(nativeh.is_readable() && !nativeh.is_writable() && !nativeh.is_nonblocking()) - { - // Opening blocking pipes for reads must block until other end opens with write - if(!ConnectNamedPipe(nativeh.h, nullptr)) + + // If creating a pipe, and this pipe is blocking, block until the other end opens + if(!path.empty() && !nativeh.is_nonblocking()) { - return win32_error(); + // Opening blocking pipes must block until other end opens + if(!ConnectNamedPipe(nativeh.h, nullptr) && ERROR_PIPE_CONNECTED != GetLastError()) + { + return win32_error(); + } + ret.value()._is_connected=true; } - ret.value()._set_is_connected(true); } return ret; } result> pipe_handle::anonymous_pipe(caching _caching, flag flags) noexcept { - // TODO FIXME Use HANDLE cloning from https://stackoverflow.com/questions/40844884/windows-named-pipe-access-control + // Uses true anonymous pipe creation technique from https://stackoverflow.com/questions/40844884/windows-named-pipe-access-control windows_nt_kernel::init(); using namespace windows_nt_kernel; - std::pair ret(pipe_handle(native_handle_type(), 0, 0, _caching, flags), pipe_handle(native_handle_type(), 0, 0, _caching, flags)); + // Create an unnamed new pipe + flags &= ~flag::unlink_on_first_close; + OUTCOME_TRY(anonpipe, pipe({}, mode::read, creation::only_if_not_exist, _caching, flags)); + std::pair ret(std::move(anonpipe), pipe_handle(native_handle_type(), 0, 0, _caching, flags)); native_handle_type &readnativeh = ret.first._v, &writenativeh = ret.second._v; - LLFIO_LOG_FUNCTION_CALL(&ret); - readnativeh.behaviour |= native_handle_type::disposition::pipe; - readnativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable + DWORD fileshare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + OUTCOME_TRY(access, access_mask_from_handle_mode(writenativeh, mode::append, flags)); + access = SYNCHRONIZE | DELETE | GENERIC_WRITE; // correct for pipes + OUTCOME_TRY(attribs, attributes_from_handle_caching_and_flags(writenativeh, _caching, flags)); writenativeh.behaviour |= native_handle_type::disposition::pipe; writenativeh.behaviour &= ~native_handle_type::disposition::seekable; // not seekable - if(!CreatePipe(&readnativeh.h, &writenativeh.h, nullptr, 65536)) + LLFIO_LOG_FUNCTION_CALL(&ret.first); + + // Now clone the handle, but as a write privileged handle + attribs &= 0x00ffffff; // the real attributes only, not the win32 flags + OUTCOME_TRY(ntflags, ntflags_from_handle_caching_and_flags(writenativeh, _caching, flags)); + IO_STATUS_BLOCK isb = make_iostatus(); + + UNICODE_STRING _path{}; + memset(&_path, 0, sizeof(_path)); + OBJECT_ATTRIBUTES oa{}; + memset(&oa, 0, sizeof(oa)); + oa.Length = sizeof(OBJECT_ATTRIBUTES); + oa.ObjectName = &_path; + oa.RootDirectory = readnativeh.h; + oa.Attributes = 0x40 /*OBJ_CASE_INSENSITIVE*/; + NTSTATUS ntstat = NtOpenFile(&writenativeh.h, access, &oa, &isb, fileshare, ntflags); + if(STATUS_PENDING == ntstat) + { + ntstat = ntwait(writenativeh.h, isb, deadline()); + } + if(ntstat < 0) { - DWORD errcode = GetLastError(); - // assert(false); - return win32_error(errcode); + return ntkernel_error(ntstat); } - ret.first._set_is_connected(true); - ret.second._set_is_connected(true); return ret; } -pipe_handle::io_result pipe_handle::read(pipe_handle::io_request reqs, deadline d) noexcept +pipe_handle::io_result pipe_handle::_do_read(pipe_handle::io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); // If not connected, it'll be non-blocking, so connect now. - if(!_is_connected()) + if(!_is_connected) { LLFIO_WIN_DEADLINE_TO_SLEEP_INIT(d); OVERLAPPED ol{}; memset(&ol, 0, sizeof(ol)); ol.Internal = static_cast(-1); - if(!ConnectNamedPipe(_v.h, &ol)) + if(!ConnectNamedPipe(_v.h, &ol) && ERROR_PIPE_CONNECTED != GetLastError()) { if(ERROR_IO_PENDING != GetLastError()) { @@ -195,6 +222,7 @@ pipe_handle::io_result pipe_handle::read(pipe_handle: } if(STATUS_TIMEOUT == ntwait(_v.h, ol, d)) { + // ntwait cancels timed out i/o return errc::timed_out; } // It seems the NT kernel is guilty of casting bugs sometimes @@ -213,12 +241,12 @@ pipe_handle::io_result pipe_handle::read(pipe_handle: d = deadline(remaining); } } - _set_is_connected(true); + _is_connected=true; } return io_handle::read(reqs, d); } -pipe_handle::io_result pipe_handle::write(pipe_handle::io_request reqs, deadline d) noexcept +pipe_handle::io_result pipe_handle::_do_write(pipe_handle::io_request reqs, deadline d) noexcept { LLFIO_LOG_FUNCTION_CALL(this); return io_handle::write(reqs, d); diff --git a/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp index 4bfc908e..dd926449 100644 --- a/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp @@ -58,7 +58,9 @@ LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result symlink_handle::symlink(c HANDLE token; if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) { - TOKEN_PRIVILEGES privs = {1}; + TOKEN_PRIVILEGES privs; + memset(&privs, 0, sizeof(privs)); + privs.PrivilegeCount = 1; if(LookupPrivilegeValueW(NULL, L"SeCreateSymbolicLinkPrivilege", &privs.Privileges[0].Luid)) { privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; diff --git a/include/llfio/v2.0/fast_random_file_handle.hpp b/include/llfio/v2.0/fast_random_file_handle.hpp index e1c2049b..34082c2c 100644 --- a/include/llfio/v2.0/fast_random_file_handle.hpp +++ b/include/llfio/v2.0/fast_random_file_handle.hpp @@ -132,6 +132,30 @@ protected: return success(); } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t _do_max_buffers() const noexcept override { return 0; } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_barrier(io_request reqs = io_request(), barrier_kind /*unused*/ = barrier_kind::nowait_data_only, deadline /* unused */ = deadline()) noexcept override + { + OUTCOME_TRY(_perms_check()); + // Return null written + for(auto &buffer : reqs.buffers) + { + buffer = {buffer.data(), 0}; + } + return std::move(reqs.buffers); + } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_read(io_request reqs, deadline d = deadline()) noexcept override; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_write(io_request reqs, deadline d = deadline()) noexcept override + { + (void) d; + OUTCOME_TRY(_perms_check()); + // Return null written + for(auto &buffer : reqs.buffers) + { + buffer = {buffer.data(), 0}; + } + return std::move(reqs.buffers); + } + public: //! Default constructor fast_random_file_handle() = default; @@ -227,10 +251,6 @@ public: //! \brief Return a single extent of the maximum extent LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result> extents() const noexcept override { return std::vector{{0, _length}}; } - - using file_handle::read; - using file_handle::write; - /*! \brief Read data from the random file. Note that ensuring that the scatter buffers are address and size aligned to 16 byte (128 bit) multiples will give @@ -243,8 +263,7 @@ public: \errors None possible. \mallocs None possible. */ - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override; + using file_handle::read; /*! \brief Fails to write to the random file. @@ -257,30 +276,7 @@ public: \errors None possible if handle was opened with write permissions. \mallocs None possible. */ - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override - { - (void) d; - OUTCOME_TRY(_perms_check()); - // Return null written - for(auto &buffer : reqs.buffers) - { - buffer = {buffer.data(), 0}; - } - return std::move(reqs.buffers); - } - - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind /*unused*/ = barrier_kind::nowait_data_only, deadline /* unused */ = deadline()) noexcept override - { - OUTCOME_TRY(_perms_check()); - // Return null written - for(auto &buffer : reqs.buffers) - { - buffer = {buffer.data(), 0}; - } - return std::move(reqs.buffers); - } + using file_handle::write; private: struct _extent_guard : public extent_guard diff --git a/include/llfio/v2.0/file_handle.hpp b/include/llfio/v2.0/file_handle.hpp index dade453d..3afb0bbc 100644 --- a/include/llfio/v2.0/file_handle.hpp +++ b/include/llfio/v2.0/file_handle.hpp @@ -38,8 +38,6 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_EXPORT_BEGIN -class io_service; - /*! \class file_handle \brief A handle to a regular file or device, kept data layout compatible with async_file_handle. @@ -74,9 +72,6 @@ public: using ino_t = fs_handle::ino_t; using path_view_type = fs_handle::path_view_type; -protected: - io_service *_service{nullptr}; - public: //! Default constructor constexpr file_handle() {} // NOLINT @@ -84,7 +79,6 @@ public: constexpr file_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none) : lockable_io_handle(std::move(h), caching, flags) , fs_handle(devid, inode) - , _service(nullptr) { } //! No copy construction (use clone()) @@ -95,15 +89,12 @@ public: constexpr file_handle(file_handle &&o) noexcept : lockable_io_handle(std::move(o)) , fs_handle(std::move(o)) - , _service(o._service) { - o._service = nullptr; } //! Explicit conversion from handle and io_handle permitted explicit constexpr file_handle(handle &&o, dev_t devid, ino_t inode) noexcept : lockable_io_handle(std::move(o)) , fs_handle(devid, inode) - , _service(nullptr) { } //! Move assignment of file_handle permitted @@ -245,34 +236,6 @@ public: LLFIO_DEADLINE_TRY_FOR_UNTIL(reopen) - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result clone() const noexcept override - { - // For file handles, we need a deeper clone than duph() - OUTCOME_TRY(ret, reopen()); - return handle(ret.release()); - } - - //! The i/o service this handle is attached to, if any - io_service *service() const noexcept { return _service; } - - using io_handle::read; - //! Convenience initialiser list based overload for `read()` - LLFIO_MAKE_FREE_FUNCTION - io_result read(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept - { - buffer_type *_reqs = reinterpret_cast(alloca(sizeof(buffer_type) * lst.size())); - memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size()); - io_request reqs(buffers_type(_reqs, lst.size()), offset); - auto ret = read(reqs, d); - if(ret) - { - return ret.bytes_transferred(); - } - return std::move(ret).error(); - } - - LLFIO_DEADLINE_TRY_FOR_UNTIL(read) - /*! Return the current maximum permitted extent of the file. \errors Any of the values POSIX fstat() or GetFileInformationByHandleEx() can return. @@ -335,6 +298,8 @@ public: */ LLFIO_MAKE_FREE_FUNCTION LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result zero(extent_type offset, extent_type bytes, deadline d = deadline()) noexcept; + + LLFIO_DEADLINE_TRY_FOR_UNTIL(zero) }; //! \brief Constructor for `file_handle` diff --git a/include/llfio/v2.0/fs_handle.hpp b/include/llfio/v2.0/fs_handle.hpp index 92ea29d7..7caec3c9 100644 --- a/include/llfio/v2.0/fs_handle.hpp +++ b/include/llfio/v2.0/fs_handle.hpp @@ -143,6 +143,8 @@ public: */ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result parent_path_handle(deadline d = std::chrono::seconds(30)) const noexcept; + LLFIO_DEADLINE_TRY_FOR_UNTIL(parent_path_handle) + /*! Relinks the current path of this open handle to the new path specified. If `atomic_replace` is true, the relink \b atomically and silently replaces any item at the new path specified. This operation is both atomic and silent matching POSIX behaviour even on Microsoft Windows where @@ -175,6 +177,8 @@ public: LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result relink(const path_handle &base, path_view_type path, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept; + LLFIO_DEADLINE_TRY_FOR_UNTIL(relink) + #if 0 // Not implemented yet for absolutely no good reason /*! Links the inode referred to by this open handle to the path specified. The current path of this open handle is not changed, unless it has no current path due to being unlinked. @@ -199,6 +203,8 @@ public: LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result link(const path_handle &base, path_view_type path, deadline d = std::chrono::seconds(30)) noexcept; + LLFIO_DEADLINE_TRY_FOR_UNTIL(link) + /*! Clones the inode referenced by the open handle into a new inode referencing the same extents for the file content, with a copy of the same metadata, apart from ownership which is for the current user. Changes to either inode are guaranteed to not be seen by the other inode i.e. they @@ -217,6 +223,8 @@ public: LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result clone_inode(const path_handle &base, path_view_type path) noexcept; + + LLFIO_DEADLINE_TRY_FOR_UNTIL(clone_inode) #endif /*! Unlinks the current path of this open handle, causing its entry to immediately disappear @@ -244,6 +252,8 @@ public: LLFIO_MAKE_FREE_FUNCTION LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result unlink(deadline d = std::chrono::seconds(30)) noexcept; + + LLFIO_DEADLINE_TRY_FOR_UNTIL(unlink) }; namespace detail diff --git a/include/llfio/v2.0/handle.hpp b/include/llfio/v2.0/handle.hpp index 4426978b..985f1f5f 100644 --- a/include/llfio/v2.0/handle.hpp +++ b/include/llfio/v2.0/handle.hpp @@ -197,24 +197,49 @@ public: protected: // vptr takes 4 or 8 bytes - io_context *_ctx{nullptr}; // 8 or 16 bytes - native_handle_type _v; // 16 or 28 bytes - caching _caching{caching::none}; - char _spare1{0}; // used by pipe_handle - char _spare2{0}; - char _spare3{0}; // 20 or 32 bytes - flag _flags{flag::none}; // 24 or 36 bytes + native_handle_type _v; // +8 or +12: total 12 or 20 bytes + flag _flags{flag::none}; // +4: total 16 or 24 bytes + + static constexpr void _set_caching(native_handle_type &nativeh, caching caching) noexcept + { + nativeh.behaviour &= ~(native_handle_type::disposition::safety_barriers | native_handle_type::disposition::cache_metadata | native_handle_type::disposition::cache_reads | native_handle_type::disposition::cache_writes | native_handle_type::disposition::cache_temporary); + switch(caching) + { + case caching::unchanged: + break; + case caching::none: + nativeh.behaviour |= native_handle_type::disposition::safety_barriers; + break; + case caching::only_metadata: + nativeh.behaviour |= native_handle_type::disposition::cache_metadata; + break; + case caching::reads: + nativeh.behaviour |= native_handle_type::disposition::cache_reads | native_handle_type::disposition::safety_barriers; + break; + case caching::reads_and_metadata: + nativeh.behaviour |= native_handle_type::disposition::cache_reads | native_handle_type::disposition::cache_metadata | native_handle_type::disposition::safety_barriers; + break; + case caching::all: + nativeh.behaviour |= native_handle_type::disposition::cache_reads | native_handle_type::disposition::cache_writes | native_handle_type::disposition::cache_metadata; + break; + case caching::safety_barriers: + nativeh.behaviour |= native_handle_type::disposition::cache_reads | native_handle_type::disposition::cache_writes | native_handle_type::disposition::cache_metadata | native_handle_type::disposition::safety_barriers; + break; + case caching::temporary: + nativeh.behaviour |= native_handle_type::disposition::cache_reads | native_handle_type::disposition::cache_writes | native_handle_type::disposition::cache_metadata | native_handle_type::disposition::cache_temporary; + break; + } + } public: //! Default constructor constexpr handle() {} // NOLINT //! Construct a handle from a supplied native handle - explicit constexpr handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none, io_context *ctx = nullptr) noexcept - : _ctx(ctx) - , _v(std::move(h)) - , _caching(caching) + explicit constexpr handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none) noexcept + : _v(std::move(h)) , _flags(flags) { + _set_caching(_v, caching); } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~handle(); //! No copy construction (use clone()) @@ -223,17 +248,10 @@ public: handle &operator=(const handle &o) = delete; //! Move the handle. constexpr handle(handle &&o) noexcept - : _ctx(o._ctx) - , _v(std::move(o._v)) - , _caching(o._caching) - , _spare1(o._spare1) - , _spare2(o._spare2) - , _spare3(o._spare3) + : _v(std::move(o._v)) , _flags(o._flags) { - o._ctx = nullptr; o._v = native_handle_type(); - o._caching = caching::none; o._flags = flag::none; } //! Move assignment of handle @@ -252,9 +270,6 @@ public: o = std::move(temp); } - //! The i/o context this handle will use to multiplex i/o, if any - io_context *multiplexer() const noexcept { return _ctx; } - /*! Returns the current path of the open handle as said by the operating system. Note that you are NOT guaranteed that any path refreshed bears any resemblance to the original, some operating systems will return some different path which still reaches the same inode @@ -300,7 +315,7 @@ public: \errors Any of the values POSIX dup() or DuplicateHandle() can return. */ - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result clone() const noexcept; + LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result clone() const noexcept; //! Release the native handle type managed by this handle LLFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept { @@ -358,20 +373,47 @@ public: bool is_allocation() const noexcept { return _v.is_allocation(); } //! Kernel cache strategy used by this handle - caching kernel_caching() const noexcept { return _caching; } + caching kernel_caching() const noexcept { + const bool safety_barriers = !!(_v.behaviour & native_handle_type::disposition::safety_barriers); + const bool cache_metadata = !!(_v.behaviour & native_handle_type::disposition::cache_metadata); + const bool cache_reads = !!(_v.behaviour & native_handle_type::disposition::cache_reads); + const bool cache_writes = !!(_v.behaviour & native_handle_type::disposition::cache_writes); + const bool cache_temporary = !!(_v.behaviour & native_handle_type::disposition::cache_temporary); + if(cache_temporary) + { + return caching::temporary; + } + if(cache_metadata && cache_reads && cache_writes) + { + return safety_barriers ? caching::safety_barriers : caching::all; + } + if(cache_metadata && cache_reads) + { + return caching::reads_and_metadata; + } + if(cache_reads) + { + return caching::reads; + } + if(cache_metadata) + { + return caching::only_metadata; + } + return caching::none; + } //! True if the handle uses the kernel page cache for reads - bool are_reads_from_cache() const noexcept { return _caching != caching::none && _caching != caching::only_metadata; } + bool are_reads_from_cache() const noexcept { return !!(_v.behaviour & native_handle_type::disposition::cache_reads); } //! True if writes are safely on storage on completion - bool are_writes_durable() const noexcept { return _caching == caching::none || _caching == caching::reads || _caching == caching::reads_and_metadata; } + bool are_writes_durable() const noexcept { return !(_v.behaviour & native_handle_type::disposition::cache_writes); } //! True if issuing safety fsyncs is on - bool are_safety_barriers_issued() const noexcept { return !(_flags & flag::disable_safety_barriers) && !((static_cast(_caching) & 1U) == 0U); } + bool are_safety_barriers_issued() const noexcept { return !!(_v.behaviour & native_handle_type::disposition::safety_barriers); } //! The flags this handle was opened with flag flags() const noexcept { return _flags; } //! The native handle used by this handle native_handle_type native_handle() const noexcept { return _v; } }; -static_assert((sizeof(void *) == 4 && sizeof(handle) == 24) || (sizeof(void *) == 8 && sizeof(handle) == 36), "handle is not 24 or 36 bytes in size!"); +static_assert((sizeof(void *) == 4 && sizeof(handle) == 16) || (sizeof(void *) == 8 && sizeof(handle) == 24), "handle is not 16 or 24 bytes in size!"); #pragma pack(pop) diff --git a/include/llfio/v2.0/io_handle.hpp b/include/llfio/v2.0/io_handle.hpp index 5cfceb14..9c5434ba 100644 --- a/include/llfio/v2.0/io_handle.hpp +++ b/include/llfio/v2.0/io_handle.hpp @@ -1,5 +1,5 @@ /* A handle to something -(C) 2015-2017 Niall Douglas (20 commits) +(C) 2015-2019 Niall Douglas (20 commits) File Created: Dec 2015 @@ -25,9 +25,9 @@ Distributed under the Boost Software License, Version 1.0. #ifndef LLFIO_IO_HANDLE_H #define LLFIO_IO_HANDLE_H -#include "handle.hpp" +#include "io_multiplexer.hpp" -//! \file io_handle.hpp Provides i/o handle +//! \file io_handle.hpp Provides a byte-orientated i/o handle #ifdef _MSC_VER #pragma warning(push) @@ -37,7 +37,7 @@ Distributed under the Boost Software License, Version 1.0. LLFIO_V2_NAMESPACE_EXPORT_BEGIN /*! \class io_handle -\brief A handle to something capable of scatter-gather i/o. +\brief A handle to something capable of scatter-gather byte i/o. */ class LLFIO_DECL io_handle : public handle { @@ -49,247 +49,35 @@ public: using creation = handle::creation; using caching = handle::caching; using flag = handle::flag; - - //! The kinds of write reordering barrier which can be performed. - enum class barrier_kind - { - nowait_data_only, //!< Barrier data only, non-blocking. This is highly optimised on NV-DIMM storage, but consider using `nvram_barrier()` for even better performance. - wait_data_only, //!< Barrier data only, block until it is done. This is highly optimised on NV-DIMM storage, but consider using `nvram_barrier()` for even better performance. - nowait_all, //!< Barrier data and the metadata to retrieve it, non-blocking. - wait_all //!< Barrier data and the metadata to retrieve it, block until it is done. - }; - - //! The scatter buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. - //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. - struct buffer_type - { - //! Type of the pointer to memory. - using pointer = byte *; - //! Type of the pointer to memory. - using const_pointer = const byte *; - //! Type of the iterator to memory. - using iterator = byte *; - //! Type of the iterator to memory. - using const_iterator = const byte *; - //! Type of the length of memory. - using size_type = size_t; - - //! Default constructor - buffer_type() = default; - //! Constructor - constexpr buffer_type(pointer data, size_type len) noexcept - : _data(data) - , _len(len) - { - } - buffer_type(const buffer_type &) = default; - buffer_type(buffer_type &&) = default; - buffer_type &operator=(const buffer_type &) = default; - buffer_type &operator=(buffer_type &&) = default; - ~buffer_type() = default; - - // Emulation of this being a span in the TS - - //! Returns the address of the bytes for this buffer - constexpr pointer data() noexcept { return _data; } - //! Returns the address of the bytes for this buffer - constexpr const_pointer data() const noexcept { return _data; } - //! Returns the number of bytes in this buffer - constexpr size_type size() const noexcept { return _len; } - - //! Returns an iterator to the beginning of the buffer - constexpr iterator begin() noexcept { return _data; } - //! Returns an iterator to the beginning of the buffer - constexpr const_iterator begin() const noexcept { return _data; } - //! Returns an iterator to the beginning of the buffer - constexpr const_iterator cbegin() const noexcept { return _data; } - //! Returns an iterator to after the end of the buffer - constexpr iterator end() noexcept { return _data + _len; } - //! Returns an iterator to after the end of the buffer - constexpr const_iterator end() const noexcept { return _data + _len; } - //! Returns an iterator to after the end of the buffer - constexpr const_iterator cend() const noexcept { return _data + _len; } - - private: - friend constexpr inline void _check_iovec_match(); - pointer _data; - size_type _len; - }; - //! The gather buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. - //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. - struct const_buffer_type - { - //! Type of the pointer to memory. - using pointer = const byte *; - //! Type of the pointer to memory. - using const_pointer = const byte *; - //! Type of the iterator to memory. - using iterator = const byte *; - //! Type of the iterator to memory. - using const_iterator = const byte *; - //! Type of the length of memory. - using size_type = size_t; - - //! Default constructor - const_buffer_type() = default; - //! Constructor - constexpr const_buffer_type(pointer data, size_type len) noexcept - : _data(data) - , _len(len) - { - } - //! Converting constructor from non-const buffer type - constexpr const_buffer_type(buffer_type b) noexcept - : _data(b.data()) - , _len(b.size()) - { - } - const_buffer_type(const const_buffer_type &) = default; - const_buffer_type(const_buffer_type &&) = default; - const_buffer_type &operator=(const const_buffer_type &) = default; - const_buffer_type &operator=(const_buffer_type &&) = default; - ~const_buffer_type() = default; - - // Emulation of this being a span in the TS - - //! Returns the address of the bytes for this buffer - constexpr pointer data() noexcept { return _data; } - //! Returns the address of the bytes for this buffer - constexpr const_pointer data() const noexcept { return _data; } - //! Returns the number of bytes in this buffer - constexpr size_type size() const noexcept { return _len; } - - //! Returns an iterator to the beginning of the buffer - constexpr iterator begin() noexcept { return _data; } - //! Returns an iterator to the beginning of the buffer - constexpr const_iterator begin() const noexcept { return _data; } - //! Returns an iterator to the beginning of the buffer - constexpr const_iterator cbegin() const noexcept { return _data; } - //! Returns an iterator to after the end of the buffer - constexpr iterator end() noexcept { return _data + _len; } - //! Returns an iterator to after the end of the buffer - constexpr const_iterator end() const noexcept { return _data + _len; } - //! Returns an iterator to after the end of the buffer - constexpr const_iterator cend() const noexcept { return _data + _len; } - - private: - pointer _data; - size_type _len; - }; -#ifndef NDEBUG - static_assert(std::is_trivial::value, "buffer_type is not a trivial type!"); - static_assert(std::is_trivial::value, "const_buffer_type is not a trivial type!"); - static_assert(std::is_standard_layout::value, "buffer_type is not a standard layout type!"); - static_assert(std::is_standard_layout::value, "const_buffer_type is not a standard layout type!"); -#endif - //! The scatter buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. - using buffers_type = span; - //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. - using const_buffers_type = span; -#ifndef NDEBUG - // Is trivial in all ways, except default constructibility - static_assert(std::is_trivially_copyable::value, "buffers_type is not trivially copyable!"); - // static_assert(std::is_trivially_assignable::value, "buffers_type is not trivially assignable!"); - // static_assert(std::is_trivially_destructible::value, "buffers_type is not trivially destructible!"); - // static_assert(std::is_trivially_copy_constructible::value, "buffers_type is not trivially copy constructible!"); - // static_assert(std::is_trivially_move_constructible::value, "buffers_type is not trivially move constructible!"); - // static_assert(std::is_trivially_copy_assignable::value, "buffers_type is not trivially copy assignable!"); - // static_assert(std::is_trivially_move_assignable::value, "buffers_type is not trivially move assignable!"); - static_assert(std::is_standard_layout::value, "buffers_type is not a standard layout type!"); -#endif - //! The i/o request type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. - template struct io_request - { - T buffers{}; - extent_type offset{0}; - constexpr io_request() {} // NOLINT (defaulting this breaks clang and GCC, so don't do it!) - constexpr io_request(T _buffers, extent_type _offset) - : buffers(std::move(_buffers)) - , offset(_offset) - { - } - }; -#ifndef NDEBUG - // Is trivial in all ways, except default constructibility - static_assert(std::is_trivially_copyable>::value, "io_request is not trivially copyable!"); - // static_assert(std::is_trivially_assignable, io_request>::value, "io_request is not trivially assignable!"); - // static_assert(std::is_trivially_destructible>::value, "io_request is not trivially destructible!"); - // static_assert(std::is_trivially_copy_constructible>::value, "io_request is not trivially copy constructible!"); - // static_assert(std::is_trivially_move_constructible>::value, "io_request is not trivially move constructible!"); - // static_assert(std::is_trivially_copy_assignable>::value, "io_request is not trivially copy assignable!"); - // static_assert(std::is_trivially_move_assignable>::value, "io_request is not trivially move assignable!"); - static_assert(std::is_standard_layout>::value, "io_request is not a standard layout type!"); -#endif - //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction. - template struct io_result : public LLFIO_V2_NAMESPACE::result - { - using Base = LLFIO_V2_NAMESPACE::result; - size_type _bytes_transferred{static_cast(-1)}; - -#if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug - constexpr io_result() - : Base() - { - } - template - constexpr io_result(Args &&... args) - : Base(std::forward(args)...) - { - } -#else - using Base::Base; - io_result() = default; -#endif - ~io_result() = default; - io_result &operator=(io_result &&) = default; // NOLINT -#if LLFIO_EXPERIMENTAL_STATUS_CODE - io_result(const io_result &) = delete; - io_result &operator=(const io_result &) = delete; -#else - io_result(const io_result &) = default; - io_result &operator=(const io_result &) = default; -#endif - io_result(io_result &&) = default; // NOLINT - //! Returns bytes transferred - size_type bytes_transferred() noexcept - { - if(_bytes_transferred == static_cast(-1)) - { - _bytes_transferred = 0; - for(auto &i : this->value()) - { - _bytes_transferred += i.size(); - } - } - return _bytes_transferred; - } - }; -#if !defined(NDEBUG) && !LLFIO_EXPERIMENTAL_STATUS_CODE - // Is trivial in all ways, except default constructibility - static_assert(std::is_trivially_copyable>::value, "io_result is not trivially copyable!"); -// static_assert(std::is_trivially_assignable, io_result>::value, "io_result is not trivially assignable!"); -// static_assert(std::is_trivially_destructible>::value, "io_result is not trivially destructible!"); -// static_assert(std::is_trivially_copy_constructible>::value, "io_result is not trivially copy constructible!"); -// static_assert(std::is_trivially_move_constructible>::value, "io_result is not trivially move constructible!"); -// static_assert(std::is_trivially_copy_assignable>::value, "io_result is not trivially copy assignable!"); -// static_assert(std::is_trivially_move_assignable>::value, "io_result is not trivially move assignable!"); -//! \todo Why is io_result not a standard layout type? -// static_assert(std::is_standard_layout>::value, "result is not a standard layout type!"); -// static_assert(std::is_standard_layout>::value, "io_result is not a standard layout type!"); -#endif + using buffer_type = io_multiplexer::buffer_type; + using const_buffer_type = io_multiplexer::const_buffer_type; + using registered_buffer_type = io_multiplexer::registered_buffer_type; + using registered_const_buffer_type = io_multiplexer::registered_const_buffer_type; + using buffers_type = io_multiplexer::buffers_type; + using const_buffers_type = io_multiplexer::const_buffers_type; + using registered_buffers_type = io_multiplexer::registered_buffers_type; + using registered_const_buffers_type = io_multiplexer::registered_const_buffers_type; + template using io_request = io_multiplexer::io_request; + template using io_result = io_multiplexer::io_result; + using barrier_kind = io_multiplexer::barrier_kind; + +protected: + io_multiplexer *_ctx{nullptr}; // +4 or +8 bytes public: //! Default constructor constexpr io_handle() {} // NOLINT ~io_handle() = default; //! Construct a handle from a supplied native handle - constexpr explicit io_handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none, io_context *ctx = nullptr) - : handle(h, caching, flags, ctx) + constexpr explicit io_handle(native_handle_type h, caching caching = caching::none, flag flags = flag::none, io_multiplexer *ctx = nullptr) + : handle(h, caching, flags) + , _ctx(ctx) { } //! Explicit conversion from handle permitted - explicit constexpr io_handle(handle &&o) noexcept + explicit constexpr io_handle(handle &&o, io_multiplexer *ctx = nullptr) noexcept : handle(std::move(o)) + , _ctx(ctx) { } //! Move construction permitted @@ -301,26 +89,125 @@ public: //! No copy assignment io_handle &operator=(const io_handle &) = delete; - /*! \brief The *maximum* number of buffers which a single read or write syscall can process at a - time for this specific open handle. On POSIX, this is known as `IOV_MAX`. + LLFIO_MAKE_FREE_FUNCTION + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result close() noexcept override + { + if(_ctx != nullptr) + { + OUTCOME_TRY(set_multiplexer(nullptr)); + } + return handle::close(); + } + + /*! \brief The i/o multiplexer this handle will use to multiplex i/o. If this returns null, + then this handle has not been registered with an i/o multiplexer yet. + */ + io_multiplexer *multiplexer() const noexcept { return _ctx; } + + /*! \brief Sets the i/o multiplexer this handle will use to implement `read()`, `write()` and `barrier()`. + + Note that this call deregisters this handle from any existing i/o multiplexer, and registers + it with the new i/o multiplexer. You must therefore not call it if any i/o is currently + outstanding on this handle. You should also be aware that multiple dynamic memory + allocations and deallocations may occur, as well as multiple syscalls (i.e. this is + an expensive call, try to do it from cold code). + + \mallocs Multiple dynamic memory allocations and deallocations. + */ + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result set_multiplexer(io_multiplexer *c = this_thread::multiplexer()) noexcept + { + if(c == _ctx) + { + return success(); + } + if(_ctx != nullptr) + { + OUTCOME_TRY(_ctx->_deregister_io_handle(this)); + _ctx = nullptr; + } + if(c != nullptr) + { + OUTCOME_TRY(type, c->_register_io_handle(this)); + (void) type; + } + _ctx = c; + return success(); + } + +protected: + //! The virtualised implementation of `max_buffers()` used if no multiplexer has been set. + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t _do_max_buffers() const noexcept; + //! The virtualised implementation of `allocate_registered_buffer()` used if no multiplexer has been set. + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result _do_allocate_registered_buffer(size_t &bytes) noexcept; // default implementation is in map_handle.hpp + //! The virtualised implementation of `read()` used if no multiplexer has been set. + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_read(io_request reqs, deadline d) noexcept; + //! The virtualised implementation of `write()` used if no multiplexer has been set. + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_write(io_request reqs, deadline d) noexcept; + //! The virtualised implementation of `barrier()` used if no multiplexer has been set. + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_barrier(io_request reqs, barrier_kind kind, deadline d) noexcept; + +public: + /*! \brief The *maximum* number of buffers which a single read or write syscall can (atomically) + process at a time for this specific open handle. On POSIX, this is known as `IOV_MAX`. + Preferentially uses any i/o multiplexer set over the virtually overridable per-class implementation. Note that the actual number of buffers accepted for a read or a write may be significantly lower than this system-defined limit, depending on available resources. The `read()` or `write()` - call will return the buffers accepted. + call will return the buffers accepted at the time of invoking the syscall. Note also that some OSs will error out if you supply more than this limit to `read()` or `write()`, but other OSs do not. Some OSs guarantee that each i/o syscall has effects atomically visible or not to other i/o, other OSs do not. - OS X does not implement scatter-gather file i/o syscalls. - Thus this function will always return `1` in that situation. + OS X does not implement scatter-gather file i/o syscalls. Thus this function will always return + `1` in that situation. + + Microsoft Windows *may* implement scatter-gather i/o under certain handle configurations. + Most of the time for non-socket handles this function will return `1`. + + For handles which implement i/o entirely in user space, and thus syscalls are not involved, + this function will return `0`. + */ + size_t max_buffers() const noexcept + { + if(_ctx == nullptr) + { + return _do_max_buffers(); + } + return _ctx->do_io_handle_max_buffers(this); + } + + /*! \brief Request the allocation of a new registered i/o buffer with the system suitable + for maximum performance i/o, preferentially using any i/o multiplexer set over the + virtually overridable per-class implementation. - Microsoft Windows *may* implement scatter-gather file i/o under very limited circumstances. - Most of the time this function will return `1`. + \return A shared pointer to the i/o buffer. Note that the pointer returned is not + the resource under management, using shared ptr's aliasing feature. + \param bytes The size of the i/o buffer requested. This may be rounded (considerably) + upwards, you should always use the value returned. + + Some i/o multiplexer implementations have the ability to allocate i/o buffers in special + memory shared between the i/o hardware and user space processes. Using registered i/o + buffers can entirely eliminate all kernel transitions and memory copying during i/o, and + can saturate very high end hardware from a single kernel thread. + + If no multiplexer is set, the default implementation uses `map_handle` to allocate raw + memory pages from the OS kernel. If the requested buffer size is a multiple of one of + the larger page sizes from `utils::page_sizes()`, an attempt to satisfy the request + using the larger page size will be attempted first. */ - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t max_buffers() const noexcept; + LLFIO_MAKE_FREE_FUNCTION + result allocate_registered_buffer(size_t &bytes) noexcept + { + if(_ctx == nullptr) + { + return _do_allocate_registered_buffer(bytes); + } + return _ctx->do_io_handle_allocate_registered_buffer(this, bytes); + } - /*! \brief Read data from the open handle. + /*! \brief Read data from the open handle, preferentially using any i/o multiplexer set over the + virtually overridable per-class implementation. \warning Depending on the implementation backend, **very** different buffers may be returned than you supplied. You should **always** use the buffers returned and assume that they point to different @@ -340,11 +227,36 @@ public: The asynchronous implementation in async_file_handle performs one calloc and one free. */ LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept; + io_result read(io_request reqs, deadline d = deadline()) noexcept + { + if(_ctx == nullptr) + { + return _do_read(reqs, d); + } + return _ctx->do_io_handle_read(this, reqs, d); + } + //! \overload Registered buffers overload + LLFIO_MAKE_FREE_FUNCTION + io_result read(io_request reqs, deadline d = deadline()) noexcept { return _ctx->do_io_handle_read(this, reqs, d); } + //! \overload Convenience initialiser list based overload for `read()` + LLFIO_MAKE_FREE_FUNCTION + io_result read(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept + { + buffer_type *_reqs = reinterpret_cast(alloca(sizeof(buffer_type) * lst.size())); + memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size()); + io_request reqs(buffers_type(_reqs, lst.size()), offset); + auto ret = read(reqs, d); + if(ret) + { + return ret.bytes_transferred(); + } + return std::move(ret).error(); + } LLFIO_DEADLINE_TRY_FOR_UNTIL(read) - /*! \brief Write data to the open handle. + /*! \brief Write data to the open handle, preferentially using any i/o multiplexer set over the + virtually overridable per-class implementation. \warning Depending on the implementation backend, not all of the buffers input may be written. For example, with a zeroed deadline, some backends may only consume as many buffers as the system has available write slots @@ -365,8 +277,18 @@ public: The asynchronous implementation in async_file_handle performs one calloc and one free. */ LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept; - //! \overload + io_result write(io_request reqs, deadline d = deadline()) noexcept + { + if(_ctx == nullptr) + { + return _do_write(reqs, d); + } + return _ctx->do_io_handle_write(this, reqs, d); + } + //! \overload Registered buffers overload + LLFIO_MAKE_FREE_FUNCTION + io_result read(io_request reqs, deadline d = deadline()) noexcept { return _ctx->do_io_handle_write(this, reqs, d); } + //! \overload Convenience initialiser list based overload for `write()` LLFIO_MAKE_FREE_FUNCTION io_result write(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept { @@ -384,7 +306,8 @@ public: LLFIO_DEADLINE_TRY_FOR_UNTIL(write) /*! \brief Issue a write reordering barrier such that writes preceding the barrier will reach - storage before writes after this barrier. + storage before writes after this barrier, preferentially using any i/o multiplexer set over the + virtually overridable per-class implementation. \warning **Assume that this call is a no-op**. It is not reliably implemented in many common use cases, for example if your code is running inside a LXC container, or if the user has mounted @@ -412,10 +335,21 @@ public: \mallocs None. */ LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept + { + if(_ctx == nullptr) + { + return _do_barrier(reqs, kind, d); + } + return _ctx->do_io_handle_barrier(this, reqs, kind, d); + } + //! \overload Registered buffers overload + LLFIO_MAKE_FREE_FUNCTION + io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept { return _ctx->do_io_handle_barrier(this, reqs, kind, d); } LLFIO_DEADLINE_TRY_FOR_UNTIL(barrier) }; +static_assert((sizeof(void *) == 4 && sizeof(io_handle) == 20) || (sizeof(void *) == 8 && sizeof(io_handle) == 32), "io_handle is not 20 or 32 bytes in size!"); // BEGIN make_free_functions.py diff --git a/include/llfio/v2.0/io_multiplexer.hpp b/include/llfio/v2.0/io_multiplexer.hpp new file mode 100644 index 00000000..8b9a874c --- /dev/null +++ b/include/llfio/v2.0/io_multiplexer.hpp @@ -0,0 +1,412 @@ +/* Multiplex file i/o +(C) 2019 Niall Douglas (9 commits) +File Created: Nov 2019 + + +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 in the accompanying file +Licence.txt or 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. + + +Distributed under the Boost Software License, Version 1.0. + (See accompanying file Licence.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +#ifndef LLFIO_IO_MULTIPLEXER_H +#define LLFIO_IO_MULTIPLEXER_H + +//#define LLFIO_DEBUG_PRINT + +#include "handle.hpp" + +#include // for unique_ptr and shared_ptr + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) // dll interface +#endif + +LLFIO_V2_NAMESPACE_EXPORT_BEGIN + +class io_handle; + +/*! \class io_multiplexer +\brief A multiplexer of byte-orientated i/o. + +LLFIO does not provide out-of-the-box multiplexing of byte i/o, however it does provide the ability +to create `io_handle` instances with the `handle::flag::multiplexable` set. With that flag set, the +following LLFIO classes change how they create handles with the kernel: + + +
LLFIO i/o classPOSIXWindows +
directory_handleNo effectCreates `HANDLE` as `OVERLAPPED` +
file_handleNo effectCreates `HANDLE` as `OVERLAPPED` +
map_handleNo effectNo effect +
mapped_file_handleNo effectCreates `HANDLE` as `OVERLAPPED`, but i/o is to map not file +
pipe_handleCreates file descriptor as non-blockingCreates `HANDLE` as `OVERLAPPED` +
section_handleNo effectCreates `HANDLE` as `OVERLAPPED` +
symlink_handleNo effectCreates `HANDLE` as `OVERLAPPED` +
+ +If the i/o handle's multiplexer pointer is not null, the multiplexer instance is invoked to implement +`io_handle::read()`, `io_handle::write()` and `io_handle::barrier()`. You can define in your +multiplexer implementation the byte i/o read, write and barrier implementations to anything you like, +though you should not break the behaviour guarantees documented for those operations. + +If the i/o handle's multiplexer pointer is null, `io_handle::read()`, `io_handle::write()` and +`io_handle::barrier()` all use virtually overridable implementations. The default implementations +emulate blocking semantics using the kernel's i/o poll function (literally `poll()` on POSIX, +`NtWaitForSingleObject()` on Windows) to sleep the thread until at least one byte of i/o occurs, or +the deadline specified is exceeded. This, obviously enough, can double the number of kernel syscalls +done per i/o, so using handles with the `handle::flag::multiplexable` flag set is not wise unless +you really need non-infinite deadline i/o. +*/ +class LLFIO_DECL io_multiplexer : public handle +{ + friend class io_handle; + + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result _register_io_handle(io_handle *h) noexcept = 0; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result _deregister_io_handle(io_handle *h) noexcept = 0; + +public: + using path_type = handle::path_type; + using extent_type = handle::extent_type; + using size_type = handle::size_type; + using mode = handle::mode; + using creation = handle::creation; + using caching = handle::caching; + using flag = handle::flag; + + //! The kinds of write reordering barrier which can be performed. + enum class barrier_kind : uint8_t + { + nowait_data_only, //!< Barrier data only, non-blocking. This is highly optimised on NV-DIMM storage, but consider using `nvram_barrier()` for even better performance. + wait_data_only, //!< Barrier data only, block until it is done. This is highly optimised on NV-DIMM storage, but consider using `nvram_barrier()` for even better performance. + nowait_all, //!< Barrier data and the metadata to retrieve it, non-blocking. + wait_all //!< Barrier data and the metadata to retrieve it, block until it is done. + }; + + //! The scatter buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. + //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. + struct buffer_type + { + //! Type of the pointer to memory. + using pointer = byte *; + //! Type of the pointer to memory. + using const_pointer = const byte *; + //! Type of the iterator to memory. + using iterator = byte *; + //! Type of the iterator to memory. + using const_iterator = const byte *; + //! Type of the length of memory. + using size_type = size_t; + + //! Default constructor + buffer_type() = default; + //! Constructor + constexpr buffer_type(pointer data, size_type len) noexcept + : _data(data) + , _len(len) + { + } + //! Constructor + constexpr buffer_type(span s) noexcept + : _data(s.data()) + , _len(s.size()) + { + } + buffer_type(const buffer_type &) = default; + buffer_type(buffer_type &&) = default; + buffer_type &operator=(const buffer_type &) = default; + buffer_type &operator=(buffer_type &&) = default; + ~buffer_type() = default; + + // Emulation of this being a span in the TS + + //! Returns the address of the bytes for this buffer + constexpr pointer data() noexcept { return _data; } + //! Returns the address of the bytes for this buffer + constexpr const_pointer data() const noexcept { return _data; } + //! Returns the number of bytes in this buffer + constexpr size_type size() const noexcept { return _len; } + + //! Returns an iterator to the beginning of the buffer + constexpr iterator begin() noexcept { return _data; } + //! Returns an iterator to the beginning of the buffer + constexpr const_iterator begin() const noexcept { return _data; } + //! Returns an iterator to the beginning of the buffer + constexpr const_iterator cbegin() const noexcept { return _data; } + //! Returns an iterator to after the end of the buffer + constexpr iterator end() noexcept { return _data + _len; } + //! Returns an iterator to after the end of the buffer + constexpr const_iterator end() const noexcept { return _data + _len; } + //! Returns an iterator to after the end of the buffer + constexpr const_iterator cend() const noexcept { return _data + _len; } + + private: + friend constexpr inline void _check_iovec_match(); + pointer _data; + size_type _len; + }; + //! The registered scatter buffer type used by this handle. + using registered_buffer_type = std::shared_ptr; + + //! The gather buffer type used by this handle. Guaranteed to be `TrivialType` and `StandardLayoutType`. + //! Try to make address and length 64 byte, or ideally, `page_size()` aligned where possible. + struct const_buffer_type + { + //! Type of the pointer to memory. + using pointer = const byte *; + //! Type of the pointer to memory. + using const_pointer = const byte *; + //! Type of the iterator to memory. + using iterator = const byte *; + //! Type of the iterator to memory. + using const_iterator = const byte *; + //! Type of the length of memory. + using size_type = size_t; + + //! Default constructor + const_buffer_type() = default; + //! Constructor + constexpr const_buffer_type(pointer data, size_type len) noexcept + : _data(data) + , _len(len) + { + } + //! Constructor + constexpr const_buffer_type(span s) noexcept + : _data(s.data()) + , _len(s.size()) + { + } + //! Converting constructor from non-const buffer type + constexpr const_buffer_type(buffer_type b) noexcept + : _data(b.data()) + , _len(b.size()) + { + } + //! Converting constructor from non-const buffer type + constexpr const_buffer_type(span s) noexcept + : _data(s.data()) + , _len(s.size()) + { + } + const_buffer_type(const const_buffer_type &) = default; + const_buffer_type(const_buffer_type &&) = default; + const_buffer_type &operator=(const const_buffer_type &) = default; + const_buffer_type &operator=(const_buffer_type &&) = default; + ~const_buffer_type() = default; + + // Emulation of this being a span in the TS + + //! Returns the address of the bytes for this buffer + constexpr pointer data() noexcept { return _data; } + //! Returns the address of the bytes for this buffer + constexpr const_pointer data() const noexcept { return _data; } + //! Returns the number of bytes in this buffer + constexpr size_type size() const noexcept { return _len; } + + //! Returns an iterator to the beginning of the buffer + constexpr iterator begin() noexcept { return _data; } + //! Returns an iterator to the beginning of the buffer + constexpr const_iterator begin() const noexcept { return _data; } + //! Returns an iterator to the beginning of the buffer + constexpr const_iterator cbegin() const noexcept { return _data; } + //! Returns an iterator to after the end of the buffer + constexpr iterator end() noexcept { return _data + _len; } + //! Returns an iterator to after the end of the buffer + constexpr const_iterator end() const noexcept { return _data + _len; } + //! Returns an iterator to after the end of the buffer + constexpr const_iterator cend() const noexcept { return _data + _len; } + + private: + pointer _data; + size_type _len; + }; +#ifndef NDEBUG + static_assert(std::is_trivial::value, "buffer_type is not a trivial type!"); + static_assert(std::is_trivial::value, "const_buffer_type is not a trivial type!"); + static_assert(std::is_standard_layout::value, "buffer_type is not a standard layout type!"); + static_assert(std::is_standard_layout::value, "const_buffer_type is not a standard layout type!"); +#endif + //! The registered gather buffer type used by this handle. + using registered_const_buffer_type = std::shared_ptr; + + //! The scatter buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + using buffers_type = span; + //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + using const_buffers_type = span; +#ifndef NDEBUG + // Is trivial in all ways, except default constructibility + static_assert(std::is_trivially_copyable::value, "buffers_type is not trivially copyable!"); + // static_assert(std::is_trivially_assignable::value, "buffers_type is not trivially assignable!"); + // static_assert(std::is_trivially_destructible::value, "buffers_type is not trivially destructible!"); + // static_assert(std::is_trivially_copy_constructible::value, "buffers_type is not trivially copy constructible!"); + // static_assert(std::is_trivially_move_constructible::value, "buffers_type is not trivially move constructible!"); + // static_assert(std::is_trivially_copy_assignable::value, "buffers_type is not trivially copy assignable!"); + // static_assert(std::is_trivially_move_assignable::value, "buffers_type is not trivially move assignable!"); + static_assert(std::is_standard_layout::value, "buffers_type is not a standard layout type!"); +#endif + //! The registered scatter buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + using registered_buffers_type = span; + //! The registered gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + using registered_const_buffers_type = span; + + //! The i/o request type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`. + template struct io_request + { + T buffers{}; + extent_type offset{0}; + constexpr io_request() {} // NOLINT (defaulting this breaks clang and GCC, so don't do it!) + constexpr io_request(T _buffers, extent_type _offset) + : buffers(std::move(_buffers)) + , offset(_offset) + { + } + }; +#ifndef NDEBUG + // Is trivial in all ways, except default constructibility + static_assert(std::is_trivially_copyable>::value, "io_request is not trivially copyable!"); + // static_assert(std::is_trivially_assignable, io_request>::value, "io_request is not trivially assignable!"); + // static_assert(std::is_trivially_destructible>::value, "io_request is not trivially destructible!"); + // static_assert(std::is_trivially_copy_constructible>::value, "io_request is not trivially copy constructible!"); + // static_assert(std::is_trivially_move_constructible>::value, "io_request is not trivially move constructible!"); + // static_assert(std::is_trivially_copy_assignable>::value, "io_request is not trivially copy assignable!"); + // static_assert(std::is_trivially_move_assignable>::value, "io_request is not trivially move assignable!"); + static_assert(std::is_standard_layout>::value, "io_request is not a standard layout type!"); +#endif + //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction. + template struct io_result : public LLFIO_V2_NAMESPACE::result + { + using Base = LLFIO_V2_NAMESPACE::result; + size_type _bytes_transferred{static_cast(-1)}; + +#if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug + constexpr io_result() + : Base() + { + } + template + constexpr io_result(Args &&... args) + : Base(std::forward(args)...) + { + } +#else + using Base::Base; + io_result() = default; +#endif + ~io_result() = default; + io_result &operator=(io_result &&) = default; // NOLINT +#if LLFIO_EXPERIMENTAL_STATUS_CODE + io_result(const io_result &) = delete; + io_result &operator=(const io_result &) = delete; +#else + io_result(const io_result &) = default; + io_result &operator=(const io_result &) = default; +#endif + io_result(io_result &&) = default; // NOLINT + //! Returns bytes transferred + size_type bytes_transferred() noexcept + { + if(_bytes_transferred == static_cast(-1)) + { + _bytes_transferred = 0; + for(auto &i : this->value()) + { + _bytes_transferred += i.size(); + } + } + return _bytes_transferred; + } + }; +#if !defined(NDEBUG) && !LLFIO_EXPERIMENTAL_STATUS_CODE + // Is trivial in all ways, except default constructibility + static_assert(std::is_trivially_copyable>::value, "io_result is not trivially copyable!"); +// static_assert(std::is_trivially_assignable, io_result>::value, "io_result is not trivially assignable!"); +// static_assert(std::is_trivially_destructible>::value, "io_result is not trivially destructible!"); +// static_assert(std::is_trivially_copy_constructible>::value, "io_result is not trivially copy constructible!"); +// static_assert(std::is_trivially_move_constructible>::value, "io_result is not trivially move constructible!"); +// static_assert(std::is_trivially_copy_assignable>::value, "io_result is not trivially copy assignable!"); +// static_assert(std::is_trivially_move_assignable>::value, "io_result is not trivially move assignable!"); +//! \todo Why is io_result not a standard layout type? +// static_assert(std::is_standard_layout>::value, "result is not a standard layout type!"); +// static_assert(std::is_standard_layout>::value, "io_result is not a standard layout type!"); +#endif + + using handle::handle; + io_multiplexer(io_multiplexer &&) = default; + io_multiplexer(const io_multiplexer &) = delete; + io_multiplexer &operator=(io_multiplexer &&) = default; + io_multiplexer &operator=(const io_multiplexer &) = delete; + ~io_multiplexer() = default; + +public: + //! Implements `io_handle::max_buffers()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t do_io_handle_max_buffers(const io_handle *h) const noexcept = 0; + //! Implements `io_handle::allocate_registered_buffer()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result do_io_handle_allocate_registered_buffer(io_handle *h, size_t &bytes) noexcept = 0; + //! Implements `io_handle::read()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_read(io_handle *h, io_request reqs, deadline d) noexcept = 0; + //! Implements `io_handle::read()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_read(io_handle *h, io_request reqs, deadline d) noexcept = 0; + //! Implements `io_handle::write()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_write(io_handle *h, io_request reqs, deadline d) noexcept = 0; + //! Implements `io_handle::write()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_write(io_handle *h, io_request reqs, deadline d) noexcept = 0; + //! Implements `io_handle::barrier()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_barrier(io_handle *h, io_request reqs, barrier_kind kind, deadline d) noexcept = 0; + //! Implements `io_handle::barrier()` + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result do_io_handle_barrier(io_handle *h, io_request reqs, barrier_kind kind, deadline d) noexcept = 0; +}; +//! A unique ptr to an i/o multiplexer implementation. +using io_multiplexer_ptr = std::unique_ptr; + +//! \brief Choose the best available i/o multiplexer implementation for this platform. +// LLFIO_HEADERS_ONLY_FUNC_SPEC result multiplexer_best_available(size_t threads) noexcept; +#if defined(__linux__) || DOXYGEN_IS_IN_THE_HOUSE +// LLFIO_HEADERS_ONLY_FUNC_SPEC result multiplexer_linux_epoll(size_t threads) noexcept; +// LLFIO_HEADERS_ONLY_FUNC_SPEC result multiplexer_linux_io_uring() noexcept; +#endif +#if(defined(__FreeBSD__) || defined(__APPLE__)) || DOXYGEN_IS_IN_THE_HOUSE +// LLFIO_HEADERS_ONLY_FUNC_SPEC result multiplexer_bsd_kqueue(size_t threads) noexcept; +#endif +#if defined(_WIN32) || DOXYGEN_IS_IN_THE_HOUSE +// LLFIO_HEADERS_ONLY_FUNC_SPEC result multiplexer_win_iocp(size_t threads) noexcept; +#endif + +//! \brief Thread local settings +namespace this_thread +{ + //! \brief Return the calling thread's current i/o multiplexer. + LLFIO_HEADERS_ONLY_FUNC_SPEC io_multiplexer *multiplexer() noexcept; + //! \brief Set the calling thread's current i/o multiplexer. + LLFIO_HEADERS_ONLY_FUNC_SPEC void set_multiplexer(io_multiplexer *ctx) noexcept; +} // namespace this_thread + +// BEGIN make_free_functions.py +// END make_free_functions.py + +LLFIO_V2_NAMESPACE_END + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if LLFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#define LLFIO_INCLUDED_BY_HEADER 1 +#include "detail/impl/io_multiplexer.ipp" +#undef LLFIO_INCLUDED_BY_HEADER +#endif + +#endif diff --git a/include/llfio/v2.0/io_service.hpp b/include/llfio/v2.0/io_service.hpp deleted file mode 100644 index 420836d8..00000000 --- a/include/llfio/v2.0/io_service.hpp +++ /dev/null @@ -1,301 +0,0 @@ -/* Multiplex file i/o -(C) 2015-2017 Niall Douglas (9 commits) -File Created: Dec 2015 - - -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 in the accompanying file -Licence.txt or 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. - - -Distributed under the Boost Software License, Version 1.0. - (See accompanying file Licence.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ - -#ifndef LLFIO_IO_SERVICE_H -#define LLFIO_IO_SERVICE_H - -#include "handle.hpp" - -#include -#include -#include - -#undef _threadid // windows macro splosh sigh - -//! \file io_service.hpp Provides io_service. - -// Need to decide which kind of POSIX AIO to use -#ifndef _WIN32 -// Right now the only thing we support is POSIX AIO -#if !defined(LLFIO_USE_POSIX_AIO) -/*! \brief Undefined to autodetect, 1 to use POSIX AIO, 0 to not use - -\warning On FreeBSD the AIO kernel module needs to be loaded for POSIX AIO to work. -Run as root 'kldload aio' or add 'aio_load=YES' in loader.conf. -*/ -#define LLFIO_USE_POSIX_AIO 1 -#endif -// BSD kqueues not implemented yet -//# if defined(__FreeBSD__) && !defined(LLFIO_COMPILE_KQUEUES) -//# define LLFIO_COMPILE_KQUEUES 1 -//# endif -#if LLFIO_COMPILE_KQUEUES -#if defined(LLFIO_USE_POSIX_AIO) && !LLFIO_USE_POSIX_AIO -#error BSD kqueues must be combined with POSIX AIO! -#endif -#if !defined(LLFIO_USE_POSIX_AIO) -#define LLFIO_USE_POSIX_AIO 1 -#endif -#endif -#if DOXYGEN_SHOULD_SKIP_THIS -//! Undefined to autodetect, 1 to compile in BSD kqueue support, 0 to leave it out -#define LLFIO_COMPILE_KQUEUES 0 -#endif - -#if LLFIO_USE_POSIX_AIO -// We'll be using POSIX AIO and signal based interruption for post() -#include -// Do we have realtime signals? -#if !defined(LLFIO_HAVE_REALTIME_SIGNALS) && defined(_POSIX_RTSIG_MAX) && defined(SIGRTMIN) -#ifndef LLFIO_IO_POST_SIGNAL -#define LLFIO_IO_POST_SIGNAL (-1) -#endif -#define LLFIO_HAVE_REALTIME_SIGNALS 1 -#else -#ifndef LLFIO_IO_POST_SIGNAL -//! Undefined to autoset to first free SIGRTMIN if realtime signals available, else SIGUSR1. Only used if LLFIO_USE_KQUEUES=0. -#define LLFIO_IO_POST_SIGNAL (SIGUSR1) -#endif -//! Undefined to autodetect. 0 to use non-realtime signals. Note performance in this use case is abysmal. -#define LLFIO_HAVE_REALTIME_SIGNALS 0 -#endif -struct aiocb; -#endif -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4251) // dll interface -#endif - -LLFIO_V2_NAMESPACE_EXPORT_BEGIN - -class io_service; -class async_file_handle; - -/*! \class io_service -\brief An asynchronous i/o multiplexer service. - -This service is used in conjunction with `async_file_handle` to multiplex -initating i/o and completing it onto a single kernel thread. -Unlike the `io_service` in ASIO or the Networking TS, this `io_service` -is much simpler, in particular it is single threaded per instance only -i.e. you must run a separate `io_service` instance one per kernel thread -if you wish to run i/o processing across multiple threads. LLFIO does not -do this for you (and for good reason, unlike socket i/o, it is generally -unwise to distribute file i/o across kernel threads due to the much -more code executable between user space and physical storage i.e. keeping -processing per CPU core hot in cache delivers outsize benefits compared -to socket i/o). - -Furthermore, you cannot use this i/o service in any way from any -thread other than where it was created. You cannot call its `run()` -from any thread other than where it was created. And you cannot -initiate i/o on an `async_file_handle` from any thread other than where -its owning i/o service was created. - -In other words, keep your i/o service and all associated file handles -on their owning thread. The sole function you can call from another -thread is `post()` which lets you execute some callback in the `run()` -of the owning thread. This lets you schedule i/o from other threads -if you really must do that. - -\snippet coroutines.cpp coroutines_example -*/ -class LLFIO_DECL io_service -{ - friend class async_file_handle; - -public: - //! The file extent type used by this i/o service - using extent_type = io_handle::extent_type; - //! The memory extent type used by this i/o service - using size_type = io_handle::size_type; - //! The scatter buffer type used by this i/o service - using buffer_type = io_handle::buffer_type; - //! The gather buffer type used by this i/o service - using const_buffer_type = io_handle::const_buffer_type; - //! The scatter buffers type used by this i/o service - using buffers_type = io_handle::buffers_type; - //! The gather buffers type used by this i/o service - using const_buffers_type = io_handle::const_buffers_type; - //! The i/o request type used by this i/o service - template using io_request = io_handle::io_request; - //! The i/o result type used by this i/o service - template using io_result = io_handle::io_result; - -private: -#ifdef _WIN32 - win::handle _threadh{}; - win::dword _threadid; -#else - pthread_t _threadh; -#endif - std::mutex _posts_lock; - struct post_info - { - io_service *service; - detail::function_ptr f; - post_info(io_service *s, detail::function_ptr _f) - : service(s) - , f(std::move(_f)) - { - } - }; - std::deque _posts; - using shared_size_type = std::atomic; - shared_size_type _work_queued; -#if LLFIO_USE_POSIX_AIO - bool _use_kqueues; -#if LLFIO_COMPILE_KQUEUES - int _kqueueh; -#endif - std::vector _aiocbsv; // for fast aio_suspend() -#endif -public: - // LOCK MUST BE HELD ON ENTRY! - void __post_done(post_info *pi) - { - // Find the post_info and remove it - for(auto &i : _posts) - { - if(&i == pi) - { - i.f.reset(); - i.service = nullptr; - pi = nullptr; - break; - } - } - assert(!pi); - if(pi != nullptr) - { - abort(); - } - _work_done(); - while(!_posts.empty() && (_posts.front().service == nullptr)) - { - _posts.pop_front(); - } - } - void _post_done(post_info *pi) - { - std::lock_guard g(_posts_lock); - return __post_done(pi); - } - void _work_enqueued(size_type i = 1) { _work_queued += i; } - void _work_done() { --_work_queued; } - /*! Creates an i/o service for the calling thread, installing a - global signal handler via set_interruption_signal() if not yet installed - if on POSIX and BSD kqueues not in use. - */ - LLFIO_HEADERS_ONLY_MEMFUNC_SPEC io_service(); - io_service(io_service &&) = delete; - io_service(const io_service &) = delete; - io_service &operator=(io_service &&) = delete; - io_service &operator=(const io_service &) = delete; - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~io_service(); - -#ifdef LLFIO_IO_POST_SIGNAL -private: - int _blocked_interrupt_signal{0}; - std::atomic _need_signal{false}; // false = signal not needed, true = signal needed - LLFIO_HEADERS_ONLY_MEMFUNC_SPEC void _block_interruption() noexcept; - LLFIO_HEADERS_ONLY_MEMFUNC_SPEC void _unblock_interruption() noexcept; - -public: - /*! Returns the signal used for interrupting run_until(). Only used on POSIX when - BSD kqueues are not used. Defaults to LLFIO_IO_POST_SIGNAL on platforms which use it. - - \note Only present if LLFIO_IO_POST_SIGNAL is defined. - */ - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC int interruption_signal() noexcept; - /*! Sets the signal used for interrupting run_until(), returning the former signal - setting. Only used on POSIX when BSD kqueues are not used. Special values are - 0 for deinstall global signal handler, and -1 for install to first unused signal - between SIGRTMIN and SIGRTMAX. Changing this while any io_service instances exist - is a bad idea. - - \note Only present if LLFIO_IO_POST_SIGNAL is defined. - */ - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC int set_interruption_signal(int signo = LLFIO_IO_POST_SIGNAL); -#endif - -#if LLFIO_USE_POSIX_AIO - //! True if this i/o service is using BSD kqueues - bool using_kqueues() const noexcept { return _use_kqueues; } - //! Force disable any use of BSD kqueues - LLFIO_HEADERS_ONLY_MEMFUNC_SPEC void disable_kqueues(); -#endif - - /*! Runs the i/o service for the thread owning this i/o service. Returns true if more - work remains and we just handled an i/o or post; false if there is no more work; `errc::timed_out` if - the deadline passed; `errc::operation_not_supported` if you try to call it from a non-owning thread; `errc::invalid_argument` - if deadline is invalid. - */ - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result run_until(deadline d) noexcept; - //! \overload - result run() noexcept { return run_until(deadline()); } - -private: - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC void _post(detail::function_ptr &&f); - -public: - /*! Schedule the callable to be invoked by the thread owning this object and executing `run()` at its next - available opportunity. Unlike any other function in this API layer, this function is thread safe. - */ - template void post(U &&f) { _post(detail::make_function_ptr(std::forward(f))); } - -#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE) - /*! An awaitable suspending execution of this coroutine on the current kernel thread, - and resuming execution on the kernel thread running this i/o service. This is a - convenience wrapper for `post()`. - */ - struct awaitable_post_to_self - { - io_service *service; - - //! Constructor, takes the i/o service whose kernel thread we are to reschedule onto - explicit awaitable_post_to_self(io_service &_service) - : service(&_service) - { - } - - bool await_ready() { return false; } - void await_suspend(coroutine_handle<> co) - { - service->post([co = co](io_service * /*unused*/) mutable { co.resume(); }); - } - void await_resume() {} - }; -#endif -}; - -LLFIO_V2_NAMESPACE_END - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#endif diff --git a/include/llfio/v2.0/llfio.hpp b/include/llfio/v2.0/llfio.hpp index 66fe2121..3565747a 100644 --- a/include/llfio/v2.0/llfio.hpp +++ b/include/llfio/v2.0/llfio.hpp @@ -62,11 +62,7 @@ import LLFIO_MODULE_NAME; #include "stat.hpp" #include "utils.hpp" -#ifdef LLFIO_INCLUDE_ASYNC_FILE_HANDLE -#include "async_file_handle.hpp" -#else #include "file_handle.hpp" -#endif #include "pipe_handle.hpp" #include "directory_handle.hpp" #include "statfs.hpp" diff --git a/include/llfio/v2.0/map_handle.hpp b/include/llfio/v2.0/map_handle.hpp index eb880738..0db64042 100644 --- a/include/llfio/v2.0/map_handle.hpp +++ b/include/llfio/v2.0/map_handle.hpp @@ -506,9 +506,14 @@ public: LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result close() noexcept override; //! Releases the mapped view, but does NOT release the native handle. LLFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override; - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override; +protected: + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t _do_max_buffers() const noexcept override { return 0; } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_read(io_request reqs, deadline d = deadline()) noexcept override; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_write(io_request reqs, deadline d = deadline()) noexcept override; + +public: /*! Map unused memory into view, creating new memory if insufficient unused memory is available (i.e. add the returned memory to the process' commit charge, unless `flag::nocommit` was specified). Note that the memory mapped by this call may contain non-zero bits (recycled memory) @@ -710,8 +715,6 @@ public: \errors None, though the various signals and structured exception throws common to using memory maps may occur. \mallocs None. */ - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override; using io_handle::read; /*! \brief Write data to the mapped view. @@ -722,7 +725,7 @@ public: a unified kernel page cache, so it should not be normally enabled. However, this technique is known to work around various kernel bugs, quirks and race conditions found in modern OS kernels when memory mapped i/o is performed at scale (files of many tens of Gb each). - + \note This call traps signals and structured exception throws using `QUICKCPPLIB_NAMESPACE::signal_guard`. Instantiating a `QUICKCPPLIB_NAMESPACE::signal_guard_install` somewhere much higher up in the call stack will improve performance enormously. The signal guard may cost less than 100 CPU cycles depending on how @@ -737,8 +740,6 @@ public: of the raised signal, but it is by far the most likely. \mallocs None if a `QUICKCPPLIB_NAMESPACE::signal_guard_install` is already instanced. */ - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override; using io_handle::write; }; @@ -776,6 +777,84 @@ template constexpr inline span in_place_attach(map_handle &mh) noex return span{reinterpret_cast(mh.address()), mh.length() / sizeof(T)}; } +namespace detail +{ + inline result map_handle_allocate_registered_buffer(size_t &bytes) noexcept + { + try + { + auto make_shared = [](map_handle h) { + struct registered_buffer_type_indirect + { + map_handle h; + io_handle::buffer_type buffer; + registered_buffer_type_indirect(map_handle _h) + : h(std::move(_h)) + , buffer(h.as_span()) + { + } + }; + auto ptr = std::make_shared(std::move(h)); + return io_handle::registered_buffer_type(ptr, &ptr->buffer); + }; + const auto &page_sizes = utils::page_sizes(true); + size_t idx = 0; + for(size_t n = 0; n < page_sizes.size(); ++n) + { + if(page_sizes[n] > bytes) + { + break; + } + if((bytes & (page_sizes[n] - 1)) == 0) + { + idx = n; + } + } + section_handle::flag flags = section_handle::flag::readwrite; + if(idx > 0) + { + switch(idx) + { + case 1: + flags |= section_handle::flag::page_sizes_1; + break; + case 2: + flags |= section_handle::flag::page_sizes_2; + break; + case 3: + flags |= section_handle::flag::page_sizes_3; + break; + default: + break; + } + auto r = map_handle::map(bytes, false, flags); + if(r) + { + bytes = (bytes + page_sizes[idx] - 1) & ~(page_sizes[idx] - 1); + return make_shared(std::move(r).value()); + } + } + auto r = map_handle::map(bytes, false); + if(r) + { + bytes = (bytes + page_sizes[0] - 1) & ~(page_sizes[0] - 1); + return make_shared(std::move(r).value()); + } + return errc::not_enough_memory; + } + catch(...) + { + return error_from_exception(); + } + } +} // namespace detail + +// Implement io_handle::_do_allocate_registered_buffer() +LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result io_handle::_do_allocate_registered_buffer(size_t &bytes) noexcept +{ + return detail::map_handle_allocate_registered_buffer(bytes); +} + // BEGIN make_free_functions.py //! Swap with another instance inline void swap(section_handle &self, section_handle &o) noexcept diff --git a/include/llfio/v2.0/mapped_file_handle.hpp b/include/llfio/v2.0/mapped_file_handle.hpp index 5db92eaa..f75bca1a 100644 --- a/include/llfio/v2.0/mapped_file_handle.hpp +++ b/include/llfio/v2.0/mapped_file_handle.hpp @@ -155,6 +155,42 @@ protected: section_handle _sh; // Tracks the file (i.e. *this) somewhat lazily map_handle _mh; // The current map with valid extent + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC size_t _do_max_buffers() const noexcept override { return _mh.max_buffers(); } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override { return _mh.barrier(reqs, kind, d); } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_read(io_request reqs, deadline d = deadline()) noexcept override { return _mh.read(reqs, d); } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_write(io_request reqs, deadline d = deadline()) noexcept override + { + if(!!(_sh.section_flags() & section_handle::flag::write_via_syscall)) + { + const auto batch = max_buffers(); + io_request thisreq(reqs); + LLFIO_DEADLINE_TO_SLEEP_INIT(d); + for(size_t n = 0; n < reqs.buffers.size();) + { + deadline nd; + LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); + thisreq.buffers = reqs.buffers.subspan(n, std::min(batch, reqs.buffers.size() - n)); + OUTCOME_TRY(written, file_handle::write(thisreq, nd)); + if(written.empty()) + { + reqs.buffers = reqs.buffers.subspan(0, n); + break; + } + for(auto &b : written) + { + thisreq.offset += b.size(); + n++; + } + } + if(thisreq.offset > _mh.length()) + { + OUTCOME_TRY(_mh.update_map()); + } + return reqs.buffers; + } + return _mh.write(reqs, d); + } + public: //! Default constructor constexpr mapped_file_handle() {} // NOLINT @@ -369,12 +405,16 @@ public: } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result close() noexcept override; LLFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override; - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result barrier(io_request reqs = io_request(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override { return _mh.barrier(reqs, kind, d); } result reopen(size_type reservation, mode mode_ = mode::unchanged, caching caching_ = caching::unchanged, deadline d = std::chrono::seconds(30)) const noexcept { OUTCOME_TRY(fh, file_handle::reopen(mode_, caching_, d)); return mapped_file_handle(std::move(fh), reservation, _sh.section_flags()); } + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result set_multiplexer(io_multiplexer *c = this_thread::multiplexer()) noexcept override + { + OUTCOME_TRY(file_handle::set_multiplexer(c)); + return _mh.set_multiplexer(file_handle::multiplexer()); + } /*! \brief Return the current maximum permitted extent of the file, after updating the map. Firstly calls `update_map()` to efficiently update the map to match that of the underlying @@ -424,8 +464,6 @@ public: return bytes; } - using file_handle::read; - using file_handle::write; /*! \brief Read data from the mapped file. @@ -440,7 +478,7 @@ public: \errors None, though the various signals and structured exception throws common to using memory maps may occur. \mallocs None. */ - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override { return _mh.read(reqs, d); } + using file_handle::read; /*! \brief Write data to the mapped file. If this mapped file handle was constructed with `section_handle::flag::write_via_syscall`, this function is @@ -464,38 +502,7 @@ public: of the raised signal, but it is by far the most likely. \mallocs None if a `QUICKCPPLIB_NAMESPACE::signal_guard_install` is already instanced. */ - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override - { - if(!!(_sh.section_flags() & section_handle::flag::write_via_syscall)) - { - const auto batch = max_buffers(); - io_request thisreq(reqs); - LLFIO_DEADLINE_TO_SLEEP_INIT(d); - for(size_t n = 0; n < reqs.buffers.size();) - { - deadline nd; - LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); - thisreq.buffers = reqs.buffers.subspan(n, std::min(batch, reqs.buffers.size() - n)); - OUTCOME_TRY(written, file_handle::write(thisreq, nd)); - if(written.empty()) - { - reqs.buffers = reqs.buffers.subspan(0, n); - break; - } - for(auto &b : written) - { - thisreq.offset += b.size(); - n++; - } - } - if(thisreq.offset > _mh.length()) - { - OUTCOME_TRY(_mh.update_map()); - } - return reqs.buffers; - } - return _mh.write(reqs, d); - } + using file_handle::write; }; //! \brief Constructor for `mapped_file_handle` diff --git a/include/llfio/v2.0/native_handle_type.hpp b/include/llfio/v2.0/native_handle_type.hpp index f2aef085..5d9c2cfc 100644 --- a/include/llfio/v2.0/native_handle_type.hpp +++ b/include/llfio/v2.0/native_handle_type.hpp @@ -63,7 +63,14 @@ struct native_handle_type // NOLINT section = 1U << 15U, //!< Is a memory section allocation = 1U << 16U, //!< Is a memory allocation - _child_close_executed = 1U << 28U // used to trap when vptr has become corrupted + safety_barriers = 1U << 20U, //!< Issue write reordering barriers at various points + cache_metadata = 1U << 21U, //!< Is serving metadata from the kernel cache + cache_reads = 1U << 22U, //!< Is serving reads from the kernel cache + cache_writes = 1U << 23U, //!< Is writing back from kernel cache rather than writing through + cache_temporary = 1U << 24U, //!< Writes are not flushed to storage quickly + + _is_connected = 1U << 28U, // used by pipe_handle on Windows to store connectedness + _child_close_executed = 1U << 30U // used to trap when vptr has become corrupted } QUICKCPPLIB_BITFIELD_END(disposition) union { @@ -74,6 +81,8 @@ struct native_handle_type // NOLINT int pid; // NOLINT //! A Windows HANDLE win::handle h; // NOLINT + //! A third party pointer + void *ptr; }; disposition behaviour; //! The behaviour of the handle //! Constructs a default instance diff --git a/include/llfio/v2.0/path_handle.hpp b/include/llfio/v2.0/path_handle.hpp index 1c5e2814..5a740315 100644 --- a/include/llfio/v2.0/path_handle.hpp +++ b/include/llfio/v2.0/path_handle.hpp @@ -1,5 +1,5 @@ /* A handle to a filesystem location -(C) 2017 Niall Douglas (20 commits) +(C) 2017-2020 Niall Douglas (20 commits) File Created: Jul 2017 @@ -70,7 +70,10 @@ public: { } //! Explicit conversion from handle permitted - explicit constexpr path_handle(handle &&o) noexcept : handle(std::move(o)) {} + explicit constexpr path_handle(handle &&o) noexcept + : handle(std::move(o)) + { + } //! Move construction permitted path_handle(path_handle &&) = default; //! No copy construction (use `clone()`) @@ -93,6 +96,13 @@ public: o = std::move(temp); } + //! Replaces `handle::clone()` with a `path_handle returning edition` + result clone() const noexcept + { + OUTCOME_TRY(newh, handle::clone()); + return path_handle(std::move(newh)); + } + /*! Create a path handle opening access to some location on the filing system. Some operating systems provide a particularly lightweight method of doing this (Linux: `O_PATH`, Windows: no access perms) which is much faster than opening diff --git a/include/llfio/v2.0/pipe_handle.hpp b/include/llfio/v2.0/pipe_handle.hpp index 97400423..30fa8edb 100644 --- a/include/llfio/v2.0/pipe_handle.hpp +++ b/include/llfio/v2.0/pipe_handle.hpp @@ -36,10 +36,8 @@ LLFIO_V2_NAMESPACE_EXPORT_BEGIN The only fully portable use of this class is to *create* a named pipe with read-only privileges (`pipe_create()`), and then to *open* an existing named pipe with -append-only privileges (`pipe_open()`). This ordering is important - on POSIX opening -pipes for reads blocks until a writer connects to the other side, and -opening pipes for writes fails if no reader on the other side is present. -The Windows implementation of this class matches these POSIX semantics. +append-only privileges (`pipe_open()`). This ordering is important - it +works irrespective of whether the pipe is multiplexable or not. This class doesn't prevent you opening fully duplex pipes (i.e. `mode::write`) if your system supports them, but semantics in this @@ -47,18 +45,22 @@ situation are implementation defined. Linux and Windows support fully duplex pipes, and the Windows implementation matches the Linux bespoke semantics. -When you create a named pipe, `flag::unlink_on_first_close` is -always forced on. This is due to portability reasons - +For the static functions which create a pipe, `flag::unlink_on_first_close` +is the default. This is due to portability reasons - on some platforms (e.g. Windows), named pipes always get deleted when the last handle to them is closed in the system, so the closest -matching semantic is for the creating handle to unlink its creation on -first close on all platforms. If you don't want this, release the native -handle before closing the handle instance, and take over its management. +matching semantic on POSIX is for the creating handle to unlink its creation on +first close on all platforms. If you don't want this, change the flags +given during creation. Note that on Windows, `flag::unlink_on_first_close` +is always masked out, this is because Windows appears to not permit +renaming nor unlinking of open pipes. If `flag::multiplexable` is specified which causes the handle to be created as `native_handle_type::disposition::nonblocking`, opening pipes for reads no longer blocks in the constructor. However it will then -block in `read()`, unless its deadline is zero. +block in `read()`, unless its deadline is zero. Opening pipes for write +in nonblocking mode will now fail if there is no reader present on the +other side of the pipe. \warning On POSIX neither `creation::only_if_not_exist` nor `creation::always_new` is atomic due to lack of kernel API support. @@ -85,9 +87,6 @@ class LLFIO_DECL pipe_handle : public io_handle, public fs_handle { LLFIO_HEADERS_ONLY_VIRTUAL_SPEC const handle &_get_handle() const noexcept final { return *this; } - void _set_is_connected(bool v) noexcept { this->_spare1 = v; } - bool _is_connected() const noexcept { return this->_spare1 != 0; } - public: using path_type = io_handle::path_type; using extent_type = io_handle::extent_type; @@ -110,7 +109,7 @@ public: //! Default constructor constexpr pipe_handle() {} // NOLINT //! Construct a handle from a supplied native handle - constexpr pipe_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none, io_context *ctx = nullptr) + constexpr pipe_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none, io_multiplexer *ctx = nullptr) : io_handle(std::move(h), caching, flags, ctx) , fs_handle(devid, inode) { @@ -165,7 +164,7 @@ public: is specified, this will block until the other end connects. */ LLFIO_MAKE_FREE_FUNCTION - static inline result pipe_create(path_view_type path, caching _caching = caching::all, flag flags = flag::none, const path_handle &base = path_discovery::temporary_named_pipes_directory()) noexcept { return pipe(path, mode::read, creation::if_needed, _caching, flags, base); } + static inline result pipe_create(path_view_type path, caching _caching = caching::all, flag flags = flag::unlink_on_first_close, const path_handle &base = path_discovery::temporary_named_pipes_directory()) noexcept { return pipe(path, mode::read, creation::if_needed, _caching, flags, base); } /*! Convenience overload for `pipe()` opening an existing named pipe with write-only privileges. This will fail if no reader is waiting on the other end of the pipe. @@ -180,7 +179,7 @@ public: \errors Any of the values POSIX `open()`, `mkfifo()`, `NtCreateFile()` or `NtCreateNamedPipeFile()` can return. */ LLFIO_MAKE_FREE_FUNCTION - static inline result random_pipe(mode _mode = mode::read, caching _caching = caching::all, flag flags = flag::none, const path_handle &dirpath = path_discovery::temporary_named_pipes_directory()) noexcept + static inline result random_pipe(mode _mode = mode::read, caching _caching = caching::all, flag flags = flag::unlink_on_first_close, const path_handle &dirpath = path_discovery::temporary_named_pipes_directory()) noexcept { try { @@ -243,36 +242,19 @@ public: return io_handle::close(); } - using io_handle::read; - using io_handle::write; #ifdef _WIN32 LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result parent_path_handle(deadline /*unused*/ = std::chrono::seconds(30)) const noexcept override { // Pipes parent handle is always the NT kernel namespace for pipes - OUTCOME_TRY(h, path_discovery::temporary_named_pipes_directory().clone()); - return path_handle(std::move(h)); - } - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result read(io_request reqs, deadline d = deadline()) noexcept override; - LLFIO_MAKE_FREE_FUNCTION - LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result write(io_request reqs, deadline d = deadline()) noexcept override; -#endif - //! Convenience initialiser list based overload for `read()` - LLFIO_MAKE_FREE_FUNCTION - io_result read(extent_type offset, std::initializer_list lst, deadline d = deadline()) noexcept - { - buffer_type *_reqs = reinterpret_cast(alloca(sizeof(buffer_type) * lst.size())); - memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size()); - io_request reqs(buffers_type(_reqs, lst.size()), offset); - auto ret = read(reqs, d); - if(ret) - { - return ret.bytes_transferred(); - } - return std::move(ret).error(); + return path_discovery::temporary_named_pipes_directory().clone(); } - LLFIO_DEADLINE_TRY_FOR_UNTIL(read) +protected: + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_read(io_request reqs, deadline d = deadline()) noexcept override; + LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result _do_write(io_request reqs, deadline d = deadline()) noexcept override; + +public: +#endif }; //! \brief Constructor for `pipe_handle` diff --git a/include/llfio/v2.0/status_code.hpp b/include/llfio/v2.0/status_code.hpp index bd201ae1..c809ffa1 100644 --- a/include/llfio/v2.0/status_code.hpp +++ b/include/llfio/v2.0/status_code.hpp @@ -59,12 +59,10 @@ as that (a) enables safe header only LLFIO on Windows (b) produces better codege #include "outcome/try.hpp" // Bring in status code utility #include "outcome/experimental/status-code/include/system_code_from_exception.hpp" -#if __cpp_coroutines #include "outcome/experimental/coroutine_support.hpp" -#ifdef OUTCOME_FOUND_COROUTINE_HEADER +#if !defined(LLFIO_ENABLE_COROUTINES) && defined(OUTCOME_FOUND_COROUTINE_HEADER) #define LLFIO_ENABLE_COROUTINES 1 #endif -#endif LLFIO_V2_NAMESPACE_BEGIN @@ -255,6 +253,9 @@ template using atomic_lazy = OUTCOME_V2_NAMESPACE::experimental::await template using eager = OUTCOME_V2_NAMESPACE::experimental::awaitables::eager; template using lazy = OUTCOME_V2_NAMESPACE::experimental::awaitables::lazy; template using coroutine_handle = OUTCOME_V2_NAMESPACE::awaitables::coroutine_handle; +template using coroutine_traits = OUTCOME_V2_NAMESPACE::awaitables::coroutine_traits; +using OUTCOME_V2_NAMESPACE::awaitables::suspend_always; +using OUTCOME_V2_NAMESPACE::awaitables::suspend_never; #endif //! Choose an errc implementation @@ -291,12 +292,10 @@ LLFIO_V2_NAMESPACE_END #include "outcome/result.hpp" #include "outcome/try.hpp" #include "outcome/utils.hpp" -#if __cpp_coroutines #include "outcome/coroutine_support.hpp" -#ifdef OUTCOME_FOUND_COROUTINE_HEADER +#if !defined(LLFIO_ENABLE_COROUTINES) && defined(OUTCOME_FOUND_COROUTINE_HEADER) #define LLFIO_ENABLE_COROUTINES 1 #endif -#endif LLFIO_V2_NAMESPACE_BEGIN @@ -524,6 +523,9 @@ template using atomic_lazy = OUTCOME_V2_NAMESPACE::awaitables::atomic_ template using eager = OUTCOME_V2_NAMESPACE::awaitables::eager; template using lazy = OUTCOME_V2_NAMESPACE::awaitables::lazy; template using coroutine_handle = OUTCOME_V2_NAMESPACE::awaitables::coroutine_handle; +template using coroutine_traits = OUTCOME_V2_NAMESPACE::awaitables::coroutine_traits; +using OUTCOME_V2_NAMESPACE::awaitables::suspend_always; +using OUTCOME_V2_NAMESPACE::awaitables::suspend_never; #endif static_assert(OUTCOME_V2_NAMESPACE::trait::is_error_code_available_v, "error_info is not detected to be an error code"); diff --git a/include/llfio/v2.0/storage_profile.hpp b/include/llfio/v2.0/storage_profile.hpp index 80ebf32a..d93eab49 100644 --- a/include/llfio/v2.0/storage_profile.hpp +++ b/include/llfio/v2.0/storage_profile.hpp @@ -25,7 +25,7 @@ Distributed under the Boost Software License, Version 1.0. #ifndef LLFIO_STORAGE_PROFILE_H #define LLFIO_STORAGE_PROFILE_H -#include "io_service.hpp" +#include "io_handle.hpp" #if LLFIO_EXPERIMENTAL_STATUS_CODE #include "outcome/experimental/status_outcome.hpp" @@ -73,8 +73,8 @@ namespace storage_profile //! Specialise for a different default value for T template constexpr T default_value() { return T{}; } - template <> constexpr storage_types map_to_storage_type() { return storage_types::extent_type; } - template <> constexpr io_service::extent_type default_value() { return static_cast(-1); } + template <> constexpr storage_types map_to_storage_type() { return storage_types::extent_type; } + template <> constexpr io_handle::extent_type default_value() { return static_cast(-1); } template <> constexpr storage_types map_to_storage_type() { return storage_types::unsigned_int; } template <> constexpr unsigned int default_value() { return static_cast(-1); } // template<> constexpr storage_types map_to_storage_type() { return storage_types::unsigned_long_long; } @@ -148,7 +148,7 @@ namespace storage_profile switch(type) { case storage_types::extent_type: - return f(*reinterpret_cast *>(static_cast(this))); + return f(*reinterpret_cast *>(static_cast(this))); case storage_types::unsigned_int: return f(*reinterpret_cast *>(static_cast(this))); case storage_types::unsigned_long_long: @@ -309,21 +309,21 @@ namespace storage_profile // Storage characteristics item device_name = {"storage:device:name", &storage::device}; // e.g. WDC WD30EFRX-68EUZN0 item device_min_io_size = {"storage:device:min_io_size", &storage::device}; // e.g. 4096 - item device_size = {"storage:device:size", &storage::device}; + item device_size = {"storage:device:size", &storage::device}; // Filing system characteristics item fs_name = {"storage:fs:name", &storage::fs}; item fs_config = {"storage:fs:config", &storage::fs}; // POSIX mount options, ZFS pool properties etc // item fs_ffeatures = { "storage:fs:features" }; // Standardised features??? - item fs_size = {"storage:fs:size", &storage::fs}; + item fs_size = {"storage:fs:size", &storage::fs}; item fs_in_use = {"storage:fs:in_use", &storage::fs}; // Test results on this filing system, storage and system - item atomic_rewrite_quantum = {"concurrency:atomic_rewrite_quantum", concurrency::atomic_rewrite_quantum, "The i/o modify quantum guaranteed to be atomically visible to readers irrespective of rewrite quantity"}; - item max_aligned_atomic_rewrite = {"concurrency:max_aligned_atomic_rewrite", concurrency::atomic_rewrite_quantum, + item atomic_rewrite_quantum = {"concurrency:atomic_rewrite_quantum", concurrency::atomic_rewrite_quantum, "The i/o modify quantum guaranteed to be atomically visible to readers irrespective of rewrite quantity"}; + item max_aligned_atomic_rewrite = {"concurrency:max_aligned_atomic_rewrite", concurrency::atomic_rewrite_quantum, "The maximum single aligned i/o modify quantity atomically visible to readers (can be [potentially unreliably] much larger than atomic_rewrite_quantum). " "A very common value on modern hardware with direct i/o thanks to PCIe DMA is 4096, don't trust values higher than this because of potentially discontiguous memory page mapping."}; - item atomic_rewrite_offset_boundary = {"concurrency:atomic_rewrite_offset_boundary", concurrency::atomic_rewrite_offset_boundary, "The multiple of offset in a file where update atomicity breaks, so if you wrote 4096 bytes at a 512 offset and " + item atomic_rewrite_offset_boundary = {"concurrency:atomic_rewrite_offset_boundary", concurrency::atomic_rewrite_offset_boundary, "The multiple of offset in a file where update atomicity breaks, so if you wrote 4096 bytes at a 512 offset and " "this value was 4096, your write would tear at 3584 because all writes would tear on a 4096 offset multiple. " "Linux has a famously broken kernel i/o design which causes this value to be a page multiple, except on " "filing systems which take special measures to work around it. Windows NT appears to lose all atomicity as soon as " -- cgit v1.2.3