Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/llfio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2020-04-03 12:40:41 +0300
committerNiall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com>2020-04-03 12:40:41 +0300
commit9e21c2f49d2691549fc20f472658ebc3e288584c (patch)
treeb395ae9819af2bee8255674cfb6a00b8f4699055 /include
parent434d73013a31d86cabf894328f13764b136579e2 (diff)
WiP merge of parts of the resumable_io branch into develop branch.
Diffstat (limited to 'include')
-rw-r--r--include/llfio/revision.hpp6
-rw-r--r--include/llfio/v2.0/algorithm/remove_all.hpp4
-rw-r--r--include/llfio/v2.0/async_file_handle.hpp802
-rw-r--r--include/llfio/v2.0/config.hpp98
-rw-r--r--include/llfio/v2.0/detail/impl/fast_random_file_handle.ipp2
-rw-r--r--include/llfio/v2.0/detail/impl/io_multiplexer.ipp39
-rw-r--r--include/llfio/v2.0/detail/impl/posix/async_file_handle.ipp381
-rw-r--r--include/llfio/v2.0/detail/impl/posix/io_handle.ipp71
-rw-r--r--include/llfio/v2.0/detail/impl/posix/io_service.ipp410
-rw-r--r--include/llfio/v2.0/detail/impl/posix/map_handle.ipp6
-rw-r--r--include/llfio/v2.0/detail/impl/storage_profile.ipp14
-rw-r--r--include/llfio/v2.0/detail/impl/windows/async_file_handle.ipp277
-rw-r--r--include/llfio/v2.0/detail/impl/windows/directory_handle.ipp2
-rw-r--r--include/llfio/v2.0/detail/impl/windows/fs_handle.ipp7
-rw-r--r--include/llfio/v2.0/detail/impl/windows/handle.ipp2
-rw-r--r--include/llfio/v2.0/detail/impl/windows/import.hpp147
-rw-r--r--include/llfio/v2.0/detail/impl/windows/io_handle.ipp157
-rw-r--r--include/llfio/v2.0/detail/impl/windows/io_service.ipp102
-rw-r--r--include/llfio/v2.0/detail/impl/windows/map_handle.ipp6
-rw-r--r--include/llfio/v2.0/detail/impl/windows/pipe_handle.ipp96
-rw-r--r--include/llfio/v2.0/detail/impl/windows/symlink_handle.ipp4
-rw-r--r--include/llfio/v2.0/fast_random_file_handle.hpp56
-rw-r--r--include/llfio/v2.0/file_handle.hpp39
-rw-r--r--include/llfio/v2.0/fs_handle.hpp10
-rw-r--r--include/llfio/v2.0/handle.hpp98
-rw-r--r--include/llfio/v2.0/io_handle.hpp434
-rw-r--r--include/llfio/v2.0/io_multiplexer.hpp412
-rw-r--r--include/llfio/v2.0/io_service.hpp301
-rw-r--r--include/llfio/v2.0/llfio.hpp4
-rw-r--r--include/llfio/v2.0/map_handle.hpp93
-rw-r--r--include/llfio/v2.0/mapped_file_handle.hpp79
-rw-r--r--include/llfio/v2.0/native_handle_type.hpp11
-rw-r--r--include/llfio/v2.0/path_handle.hpp14
-rw-r--r--include/llfio/v2.0/pipe_handle.hpp62
-rw-r--r--include/llfio/v2.0/status_code.hpp14
-rw-r--r--include/llfio/v2.0/storage_profile.hpp18
36 files changed, 1329 insertions, 2949 deletions
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<size_t> remove_all(directory_handle &&dirh, LLFIO_V2_NAMESPACE::detail::function_ptr<result<void>(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<size_t> remove_all(directory_handle &&dirh, LLFIO_V2_NAMESPACE::function_ptr<result<void>(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 <class F> inline result<size_t> 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<result<void>(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)>(std::forward<F>(callback)), threads);
+ return detail::remove_all(std::move(dirh), LLFIO_V2_NAMESPACE::emplace_function_ptr<result<void>(remove_all_callback_reason reason, remove_all_callback_arg arg1, remove_all_callback_arg arg2)>(std::forward<F>(callback)), threads);
}
//! \overload With default callback with removal failure timeout of 10 seconds
inline result<size_t> 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 <http://www.nedproductions.biz/> (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 <class R, class Fn, class... Args> using is_invocable_r = std::is_invocable_r<R, Fn, Args...>;
-#else
- template <class R, class Fn, class... Args> 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.
-
-<table>
-<tr><th></th><th>Cost of opening</th><th>Cost of i/o</th><th>Concurrency and Atomicity</th><th>Other remarks</th></tr>
-<tr><td>`file_handle`</td><td>Least</td><td>Syscall</td><td>POSIX guarantees (usually)</td><td>Least gotcha</td></tr>
-<tr><td>`async_file_handle`</td><td>More</td><td>Most (syscall + malloc/free + reactor)</td><td>POSIX guarantees (usually)</td><td>Makes no sense to use with cached i/o as it's a very expensive way to call `memcpy()`</td></tr>
-<tr><td>`mapped_file_handle`</td><td>Most</td><td>Least</td><td>None</td><td>Cannot be used with uncached i/o</td></tr>
-</table>
-
-\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 <class T> using io_request = io_handle::io_request<T>;
- template <class T> using io_result = io_handle::io_result<T>;
-
-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_handle> 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_file_handle> 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<async_file_handle> 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_file_handle> 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_file_handle> 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<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<async_file_handle> 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<file_handle> 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<file_handle &&>(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<buffers_type> read;
- io_result<const_buffers_type> 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 <class U> void operator()(U *_ptr) const
- {
- bool must_deallocate_self = _ptr->must_deallocate_self;
- _ptr->~U();
- if(must_deallocate_self)
- {
- auto *ptr = reinterpret_cast<char *>(_ptr);
- ::free(ptr); // NOLINT
- }
- }
- };
-
-public:
- /*! Smart pointer to state of an i/o in progress. Destroying this before an i/o has completed
- is <b>blocking</b> 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 <class BuffersType, class IORoutine> result<io_state_ptr> LLFIO_HEADERS_ONLY_MEMFUNC_SPEC _begin_io(span<char> mem, operation_t operation, io_request<BuffersType> reqs, _erased_completion_handler &&completion, IORoutine &&ioroutine) noexcept;
- LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<io_state_ptr> _begin_io(span<char> mem, operation_t operation, io_request<const_buffers_type> 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<const_buffers_type> &&)`.
- 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 <class CompletionRoutine> //
- LLFIO_REQUIRES(true || detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &&>::value) //
- result<io_state_ptr> async_barrier(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, barrier_kind kind = barrier_kind::nowait_data_only, span<char> 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<void *>(_dest);
- using msvc_workaround = std::decay_t<decltype(*this)>;
- 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<CompletionRoutine>(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<io_request<const_buffers_type> &>(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<buffers_type> &&)`.
- 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 <class CompletionRoutine> //
- LLFIO_REQUIRES(true || detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<buffers_type> &&>::value) //
- result<io_state_ptr> async_read(io_request<buffers_type> reqs, CompletionRoutine &&completion, span<char> 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<void *>(_dest);
- using msvc_workaround = std::decay_t<decltype(*this)>;
- 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<CompletionRoutine>(completion)};
- return _begin_io(mem, operation_t::read, io_request<const_buffers_type>({reinterpret_cast<const_buffer_type *>(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<const_buffers_type> &&)`.
- 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 <class CompletionRoutine> //
- LLFIO_REQUIRES(true || detail::is_invocable_r<void, CompletionRoutine, async_file_handle *, io_result<const_buffers_type> &&>::value) //
- result<io_state_ptr> async_write(io_request<const_buffers_type> reqs, CompletionRoutine &&completion, span<char> 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<void *>(_dest);
- using msvc_workaround = std::decay_t<decltype(*this)>;
- 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<CompletionRoutine>(completion)};
- return _begin_io(mem, operation_t::write, reqs, std::move(ch));
- }
-
- using file_handle::read;
- LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
- using file_handle::write;
- LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
-
-#if defined(LLFIO_ENABLE_COROUTINES) || defined(DOXYGEN_IS_IN_THE_HOUSE)
-private:
- template <class BuffersType> class awaitable_state
- {
- friend class async_file_handle;
- optional<coroutine_handle<>> _suspended;
- optional<io_result<BuffersType>> _result;
-
- // Called on completion of the i/o
- void operator()(async_file_handle * /*unused*/, io_result<BuffersType> &&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 BuffersType> class awaitable
- {
- friend class async_file_handle;
- io_state_ptr _state;
- awaitable_state<BuffersType> *_astate;
-
- explicit awaitable(io_state_ptr state)
- : _state(std::move(state))
- , _astate(reinterpret_cast<awaitable_state<BuffersType> *>(_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<BuffersType> 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<awaitable<buffers_type>> co_read(io_request<buffers_type> reqs) noexcept
- {
- OUTCOME_TRY(r, async_read(reqs, awaitable_state<buffers_type>()));
- return awaitable<buffers_type>(std::move(r));
- }
- //! \overload
- LLFIO_MAKE_FREE_FUNCTION
- result<awaitable<buffers_type>> co_read(extent_type offset, std::initializer_list<buffer_type> lst) noexcept
- {
- buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
- memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
- io_request<buffers_type> 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<awaitable<const_buffers_type>> co_write(io_request<const_buffers_type> reqs) noexcept
- {
- OUTCOME_TRY(r, async_write(reqs, awaitable_state<const_buffers_type>()));
- return awaitable<const_buffers_type>(std::move(r));
- }
- //! \overload
- LLFIO_MAKE_FREE_FUNCTION
- result<awaitable<const_buffers_type>> co_write(extent_type offset, std::initializer_list<const_buffer_type> lst) noexcept
- {
- const_buffer_type *_reqs = reinterpret_cast<const_buffer_type *>(alloca(sizeof(const_buffer_type) * lst.size()));
- memcpy(_reqs, lst.begin(), sizeof(const_buffer_type) * lst.size());
- io_request<const_buffers_type> reqs(const_buffers_type(_reqs, lst.size()), offset);
- return co_write(reqs);
- }
-#endif
-};
-
-//! \brief Constructor for `async_file_handle`
-template <> struct construct<async_file_handle>
-{
- 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<async_file_handle> 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<decltype(o)>(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_handle> 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<decltype(service)>(service), std::forward<decltype(base)>(base), std::forward<decltype(_path)>(_path), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching),
- std::forward<decltype(flags)>(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_file_handle> 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<decltype(service)>(service), std::forward<decltype(dirpath)>(dirpath), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(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_file_handle> 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<decltype(service)>(service), std::forward<decltype(name)>(name), std::forward<decltype(_mode)>(_mode), std::forward<decltype(_creation)>(_creation), std::forward<decltype(_caching)>(_caching), std::forward<decltype(flags)>(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_file_handle> 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<decltype(service)>(service), std::forward<decltype(dir)>(dir), std::forward<decltype(_mode)>(_mode), std::forward<decltype(flags)>(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<buffers_type> &)`.
-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 <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
-{
- return self.async_read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(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<const_buffers_type> &)`.
-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 <class CompletionRoutine> inline result<async_file_handle::io_state_ptr> async_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, CompletionRoutine &&completion, span<char> mem = {}) noexcept
-{
- return self.async_write(std::forward<decltype(reqs)>(reqs), std::forward<decltype(completion)>(completion), std::forward<decltype(mem)>(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<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::io_request<async_file_handle::buffers_type> reqs) noexcept
-{
- return self.co_read(std::forward<decltype(reqs)>(reqs));
-}
-//! \overload
-inline result<async_file_handle::awaitable<async_file_handle::buffers_type>> co_read(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::buffer_type> lst) noexcept
-{
- return self.co_read(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(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<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs) noexcept
-{
- return self.co_write(std::forward<decltype(reqs)>(reqs));
-}
-//! \overload
-inline result<async_file_handle::awaitable<async_file_handle::const_buffers_type>> co_write(async_file_handle &self, async_file_handle::extent_type offset, std::initializer_list<async_file_handle::const_buffer_type> lst) noexcept
-{
- return self.co_write(std::forward<decltype(offset)>(offset), std::forward<decltype(lst)>(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 <class F, size_t callable_storage_bytes = 32 - sizeof(uintptr_t)> using function_ptr = QUICKCPPLIB_NAMESPACE::function_ptr::function_ptr<F, callable_storage_bytes>;
+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 F> class function_ptr;
- template <class R, class... Args> class function_ptr<R(Args...)>
- {
- 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 <class U> struct function_ptr_storage_impl : public function_ptr_storage
- {
- U c;
- template <class... Args2>
- constexpr explicit function_ptr_storage_impl(Args2 &&... args)
- : c(std::forward<Args2>(args)...)
- {
- }
- R operator()(Args &&... args) final { return c(std::move(args)...); }
- };
- function_ptr_storage *ptr;
- template <class U> struct emplace_t
- {
- };
- template <class U, class V> friend inline function_ptr<U> make_function_ptr(V &&f); // NOLINT
- template <class U>
- explicit function_ptr(std::nullptr_t, U &&f)
- : ptr(new function_ptr_storage_impl<typename std::decay<U>::type>(std::forward<U>(f)))
- {
- }
- template <class R_, class U, class... Args2> friend inline function_ptr<R_> emplace_function_ptr(Args2 &&... args); // NOLINT
- template <class U, class... Args2>
- explicit function_ptr(emplace_t<U> /*unused*/, Args2 &&... args)
- : ptr(new function_ptr_storage_impl<U>(std::forward<Args2>(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 <class R, class U> inline function_ptr<R> make_function_ptr(U &&f) { return function_ptr<R>(nullptr, std::forward<U>(f)); }
- template <class R, class U, class... Args> inline function_ptr<R> emplace_function_ptr(Args &&... args) { return function_ptr<R>(typename function_ptr<R>::template emplace_t<U>(), std::forward<Args>(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::buffers_type> fast_random_file_handle::read(io_request<buffers_type> reqs, deadline /* unused */) noexcept
+fast_random_file_handle::io_result<fast_random_file_handle::buffers_type> fast_random_file_handle::_do_read(io_request<buffers_type> 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 <http://www.nedproductions.biz/> (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 <http://www.nedproductions.biz/> (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 <fcntl.h>
-#include <unistd.h>
-#if LLFIO_USE_POSIX_AIO
-#include <aio.h>
-#endif
-
-LLFIO_V2_NAMESPACE_BEGIN
-
-async_file_handle::io_result<async_file_handle::const_buffers_type> async_file_handle::barrier(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- optional<io_result<const_buffers_type>> ret;
- OUTCOME_TRY(io_state, async_barrier(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &&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 <class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> 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<struct aiocb **>(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<int>(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<char *>(_mem), statelen};
- must_deallocate_self = true;
- }
- io_state_ptr _state(reinterpret_cast<state_type *>(mem.data()));
- new((state = reinterpret_cast<state_type *>(mem.data()))) state_type(this, operation, must_deallocate_self, items);
- state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast<uintptr_t>(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<void *>(const_cast<byte *>(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<void *>(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::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, io_request<const_buffers_type> 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::buffers_type> async_file_handle::read(async_file_handle::io_request<async_file_handle::buffers_type> reqs, deadline d) noexcept
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- optional<io_result<buffers_type>> ret;
- OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result<buffers_type> &&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::const_buffers_type> async_file_handle::write(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, deadline d) noexcept
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- optional<io_result<const_buffers_type>> ret;
- OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &&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 <poll.h>
#include <sys/uio.h> // for preadv etc
#include <unistd.h>
-#if LLFIO_USE_POSIX_AIO
-#include <aio.h>
-#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::buffers_type> io_handle::read(io_handle::io_request<io_handle::buffers_type> reqs, deadline d) noexcept
+io_handle::io_result<io_handle::buffers_type> io_handle::_do_read(io_handle::io_request<io_handle::buffers_type> reqs, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
if(d && !_v.is_nonblocking())
@@ -123,23 +122,26 @@ io_handle::io_result<io_handle::buffers_type> 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::buffers_type> io_handle::read(io_handle::io_requ
return {reqs.buffers};
}
-io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d) noexcept
+io_handle::io_result<io_handle::const_buffers_type> io_handle::_do_write(io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
if(d && !_v.is_nonblocking())
@@ -215,24 +217,33 @@ io_handle::io_result<io_handle::const_buffers_type> 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::const_buffers_type> io_handle::write(io_handle::
return {reqs.buffers};
}
-io_handle::io_result<io_handle::const_buffers_type> io_handle::barrier(io_handle::io_request<io_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
+io_handle::io_result<io_handle::const_buffers_type> io_handle::_do_barrier(io_handle::io_request<io_handle::const_buffers_type> 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 <http://www.nedproductions.biz/> (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 <pthread.h>
-#if LLFIO_USE_POSIX_AIO
-#include <aio.h>
-#include <sys/mman.h>
-#if LLFIO_COMPILE_KQUEUES
-#include <sys/event.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#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<bool> 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<std::chrono::nanoseconds>((began_steady + std::chrono::nanoseconds(d.nsecs)) - std::chrono::steady_clock::now());
- }
- else
- {
- ns = std::chrono::duration_cast<std::chrono::nanoseconds>(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<decltype(_posts_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<async_file_handle::_erased_io_state_type *>(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<async_file_handle::_erased_io_state_type *>(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<void(io_service *)> &&f)
-{
- {
- post_info pi(this, std::move(f));
- std::lock_guard<decltype(_posts_lock)> 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::const_buffers_type> map_handle::barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
+map_handle::io_result<map_handle::const_buffers_type> map_handle::_do_barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
byte *addr = _addr + reqs.offset;
@@ -632,7 +632,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe
return region;
}
-map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
+map_handle::io_result<map_handle::buffers_type> map_handle::_do_read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
byte *addr = _addr + reqs.offset;
@@ -653,7 +653,7 @@ map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buff
return reqs.buffers;
}
-map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_request<const_buffers_type> reqs, deadline d) noexcept
+map_handle::io_result<map_handle::const_buffers_type> map_handle::_do_write(io_request<const_buffers_type> 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<void> atomic_rewrite_quantum(storage_profile &sp, file_handle &srch) noexcept
{
- if(sp.atomic_rewrite_quantum.value != static_cast<io_service::extent_type>(-1))
+ if(sp.atomic_rewrite_quantum.value != static_cast<io_handle::extent_type>(-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<off_t>(-1);
size_t size = srch.requires_aligned_io() ?
@@ -451,7 +451,7 @@ namespace storage_profile
{
concurrency = 4;
}
- std::atomic<io_service::extent_type> atomic_rewrite_quantum(sp.atomic_rewrite_quantum.value);
+ std::atomic<io_handle::extent_type> atomic_rewrite_quantum(sp.atomic_rewrite_quantum.value);
std::atomic<bool> failed(false);
for(unsigned no = 0; no < concurrency; no++)
{
@@ -596,7 +596,7 @@ namespace storage_profile
{
concurrency = 4;
}
- std::atomic<io_service::extent_type> max_aligned_atomic_rewrite(sp.max_aligned_atomic_rewrite.value);
+ std::atomic<io_handle::extent_type> max_aligned_atomic_rewrite(sp.max_aligned_atomic_rewrite.value);
std::atomic<bool> failed(false);
for(unsigned no = 0; no < concurrency; no++)
{
@@ -685,7 +685,7 @@ namespace storage_profile
outcome<void> atomic_rewrite_offset_boundary(storage_profile &sp, file_handle &srch) noexcept
{
- if(sp.atomic_rewrite_offset_boundary.value != static_cast<io_service::extent_type>(-1))
+ if(sp.atomic_rewrite_offset_boundary.value != static_cast<io_handle::extent_type>(-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<size_t>(sp.max_aligned_atomic_rewrite.value);
auto maxsize = static_cast<size_t>(sp.max_aligned_atomic_rewrite.value);
if(size > 1024)
@@ -757,7 +757,7 @@ namespace storage_profile
{
concurrency = 4;
}
- std::atomic<io_service::extent_type> atomic_rewrite_offset_boundary(sp.atomic_rewrite_offset_boundary.value);
+ std::atomic<io_handle::extent_type> atomic_rewrite_offset_boundary(sp.atomic_rewrite_offset_boundary.value);
std::atomic<bool> 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 <http://www.nedproductions.biz/> (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::const_buffers_type> async_file_handle::barrier(async_file_handle::io_request<async_file_handle::const_buffers_type> 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 <class BuffersType, class IORoutine> result<async_file_handle::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, async_file_handle::io_request<BuffersType> 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<LPOVERLAPPED>(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<char *>(_mem), statelen};
- must_deallocate_self = true;
- }
- io_state_ptr _state(reinterpret_cast<state_type *>(mem.data()));
- new((state = reinterpret_cast<state_type *>(mem.data()))) state_type(this, operation, must_deallocate_self, items);
- state->completion = reinterpret_cast<_erased_completion_handler *>(reinterpret_cast<uintptr_t>(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<state_type *>(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<ULONG_PTR>(-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<HANDLE>(state);
- offset += out[n].size();
- ++state->items_to_go;
-#ifndef NDEBUG
- if(_v.requires_aligned_io())
- {
- assert((reinterpret_cast<uintptr_t>(out[n].data()) & 511) == 0);
- assert((out[n].size() & 511) == 0);
- }
-#endif
- if(!ioroutine(_v.h, const_cast<byte *>(out[n].data()), static_cast<DWORD>(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::io_state_ptr> async_file_handle::_begin_io(span<char> mem, async_file_handle::operation_t operation, io_request<const_buffers_type> 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::buffers_type> async_file_handle::read(async_file_handle::io_request<async_file_handle::buffers_type> reqs, deadline d) noexcept
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- optional<io_result<buffers_type>> ret;
- OUTCOME_TRY(io_state, async_read(reqs, [&ret](async_file_handle *, io_result<buffers_type> &&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::const_buffers_type> async_file_handle::write(async_file_handle::io_request<async_file_handle::const_buffers_type> reqs, deadline d) noexcept
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- optional<io_result<const_buffers_type>> ret;
- OUTCOME_TRY(io_state, async_write(reqs, [&ret](async_file_handle *, io_result<const_buffers_type> &&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_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<void> 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<void> 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<void> handle::close() noexcept
result<handle> handle::clone() const noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
- result<handle> ret(handle(native_handle_type(), _caching, _flags));
+ result<handle> 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<NtReadFile_t>(GetProcAddress(ntdllh, "NtReadFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtReadFileScatter == nullptr)
+ {
+ if((NtReadFileScatter = reinterpret_cast<NtReadFileScatter_t>(GetProcAddress(ntdllh, "NtReadFileScatter"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtWriteFile == nullptr)
+ {
+ if((NtWriteFile = reinterpret_cast<NtWriteFile_t>(GetProcAddress(ntdllh, "NtWriteFile"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtWriteFileGather == nullptr)
+ {
+ if((NtWriteFileGather = reinterpret_cast<NtWriteFileGather_t>(GetProcAddress(ntdllh, "NtWriteFileGather"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtCancelIoFileEx == nullptr)
+ {
+ if((NtCancelIoFileEx = reinterpret_cast<NtCancelIoFileEx_t>(GetProcAddress(ntdllh, "NtCancelIoFileEx"))) == nullptr)
+ {
+ abort();
+ }
+ }
if(NtDeleteFile == nullptr)
{
if((NtDeleteFile = reinterpret_cast<NtDeleteFile_t>(GetProcAddress(ntdllh, "NtDeleteFile"))) == nullptr)
@@ -844,6 +916,27 @@ namespace windows_nt_kernel
abort();
}
}
+ if(NtSetIoCompletion == nullptr)
+ {
+ if((NtSetIoCompletion = reinterpret_cast<NtSetIoCompletion_t>(GetProcAddress(ntdllh, "NtSetIoCompletion"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtRemoveIoCompletion == nullptr)
+ {
+ if((NtRemoveIoCompletion = reinterpret_cast<NtRemoveIoCompletion_t>(GetProcAddress(ntdllh, "NtRemoveIoCompletion"))) == nullptr)
+ {
+ abort();
+ }
+ }
+ if(NtRemoveIoCompletionEx == nullptr)
+ {
+ if((NtRemoveIoCompletionEx = reinterpret_cast<NtRemoveIoCompletionEx_t>(GetProcAddress(ntdllh, "NtRemoveIoCompletionEx"))) == nullptr)
+ {
+ abort();
+ }
+ }
if(NtLockFile == nullptr)
{
if((NtLockFile = reinterpret_cast<NtLockFile_t>(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<DWORD>(-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 <class BuffersType, class Syscall> inline io_handle::io_result<BuffersType> do_read_write(const native_handle_type &nativeh, Syscall &&syscall, io_handle::io_request<BuffersType> reqs, deadline d) noexcept
+template <class BuffersType> inline bool do_cancel(const native_handle_type &nativeh, span<typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK> ols, io_handle::io_request<BuffersType> reqs) noexcept
{
+ using namespace windows_nt_kernel;
+ using EIOSB = typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK;
+ bool did_cancel = false;
+ ols = span<EIOSB>(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 <bool blocking, class Syscall, class BuffersType>
+inline bool do_read_write(io_handle::io_result<BuffersType> &ret, size_t &scheduled, Syscall &&syscall, const native_handle_type &nativeh, windows_nt_kernel::PIO_APC_ROUTINE routine, detail::io_operation_connection *op, span<typename detail::io_operation_connection::_EXTENDED_IO_STATUS_BLOCK> ols,
+ io_handle::io_request<BuffersType> 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<OVERLAPPED, 64> _ols{};
- memset(_ols.data(), 0, reqs.buffers.size() * sizeof(OVERLAPPED));
- span<OVERLAPPED> ols(_ols.data(), reqs.buffers.size());
+ ols = span<EIOSB>(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<ULONG_PTR>(-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 <class BuffersType, class Syscall> inline io_handle::io_result<BuffersT
assert((reqs.offset & 511) == 0);
}
#endif
- ol.OffsetHigh = (reqs.offset >> 32) & 0xffffffff;
- ol.Offset = reqs.offset & 0xffffffff;
+ offset.QuadPart = reqs.offset;
}
#ifndef NDEBUG
if(nativeh.requires_aligned_io())
@@ -88,52 +119,96 @@ template <class BuffersType, class Syscall> inline io_handle::io_result<BuffersT
assert((req.size() & 511) == 0);
}
#endif
- if(!syscall(nativeh.h, req.data(), static_cast<DWORD>(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<DWORD>(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<void> {
+ 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<ULONG_PTR>(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<NTSTATUS>(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<NTSTATUS>(ols[n].Internal));
+ ret = {reqs.buffers.data(), n + 1};
}
- reqs.buffers[n] = {reqs.buffers[n].data(), ols[n].InternalHigh};
}
- return io_handle::io_result<BuffersType>(std::move(reqs.buffers));
+ return true;
}
-io_handle::io_result<io_handle::buffers_type> io_handle::read(io_handle::io_request<io_handle::buffers_type> reqs, deadline d) noexcept
+io_handle::io_result<io_handle::buffers_type> io_handle::_do_read(io_handle::io_request<io_handle::buffers_type> 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<EIOSB, 64> _ols{};
+ io_handle::io_result<io_handle::buffers_type> ret(reqs.buffers);
+ size_t scheduled = 0;
+ do_read_write<true>(ret, scheduled, NtReadFile, _v, nullptr, nullptr, {_ols.data(), _ols.size()}, reqs, d);
+ return ret;
}
-io_handle::io_result<io_handle::const_buffers_type> io_handle::write(io_handle::io_request<io_handle::const_buffers_type> reqs, deadline d) noexcept
+io_handle::io_result<io_handle::const_buffers_type> io_handle::_do_write(io_handle::io_request<io_handle::const_buffers_type> 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<EIOSB, 64> _ols{};
+ io_handle::io_result<io_handle::const_buffers_type> ret(reqs.buffers);
+ size_t scheduled = 0;
+ do_read_write<true>(ret, scheduled, NtWriteFile, _v, nullptr, nullptr, {_ols.data(), _ols.size()}, reqs, d);
+ return ret;
}
-io_handle::io_result<io_handle::const_buffers_type> io_handle::barrier(io_handle::io_request<io_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
+io_handle::io_result<io_handle::const_buffers_type> io_handle::_do_barrier(io_handle::io_request<io_handle::const_buffers_type> 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::const_buffers_type> 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<IO_STATUS_BLOCK *>(&ol);
*isb = make_iostatus();
@@ -159,10 +235,9 @@ io_handle::io_result<io_handle::const_buffers_type> 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 <http://www.nedproductions.biz/> (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<bool> 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<void(io_service *)> &&f)
-{
- LLFIO_LOG_FUNCTION_CALL(this);
- void *data = nullptr;
- {
- post_info pi(this, std::move(f));
- std::lock_guard<decltype(_posts_lock)> g(_posts_lock);
- _posts.push_back(std::move(pi));
- data = static_cast<void *>(&_posts.back());
- }
- // lambdas can't be __stdcall on winclang, so ...
- struct lambda
- {
- static void __stdcall _(ULONG_PTR data)
- {
- auto *pi = reinterpret_cast<post_info *>(data);
- pi->f(pi->service);
- pi->service->_post_done(pi);
- }
- };
- PAPCFUNC apcf = lambda::_;
- if(QueueUserAPC(apcf, _threadh, reinterpret_cast<ULONG_PTR>(data)) != 0u)
- {
- _work_enqueued();
- }
- else
- {
- auto *pi = static_cast<post_info *>(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::const_buffers_type> map_handle::barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
+map_handle::io_result<map_handle::const_buffers_type> map_handle::_do_barrier(map_handle::io_request<map_handle::const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
byte *addr = _addr + reqs.offset;
@@ -893,7 +893,7 @@ result<map_handle::buffer_type> map_handle::do_not_store(buffer_type region) noe
return region;
}
-map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
+map_handle::io_result<map_handle::buffers_type> map_handle::_do_read(io_request<buffers_type> reqs, deadline /*d*/) noexcept
{
LLFIO_LOG_FUNCTION_CALL(this);
byte *addr = _addr + reqs.offset;
@@ -914,7 +914,7 @@ map_handle::io_result<map_handle::buffers_type> map_handle::read(io_request<buff
return reqs.buffers;
}
-map_handle::io_result<map_handle::const_buffers_type> map_handle::write(io_request<const_buffers_type> reqs, deadline d) noexcept
+map_handle::io_result<map_handle::const_buffers_type> map_handle::_do_write(io_request<const_buffers_type> 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_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<pipe_handle> 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_handle::pipe(pipe_handle::path_view_type path, pipe_han
path_view::c_str<> zpath(path, true);
UNICODE_STRING _path{};
- _path.Buffer = const_cast<wchar_t *>(zpath.buffer);
- _path.MaximumLength = (_path.Length = static_cast<USHORT>(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<wchar_t *>(zpath.buffer);
+ _path.MaximumLength = (_path.Length = static_cast<USHORT>(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_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_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_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<std::pair<pipe_handle, pipe_handle>> 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<pipe_handle, pipe_handle> 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<pipe_handle, pipe_handle> 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::buffers_type> pipe_handle::read(pipe_handle::io_request<pipe_handle::buffers_type> reqs, deadline d) noexcept
+pipe_handle::io_result<pipe_handle::buffers_type> pipe_handle::_do_read(pipe_handle::io_request<pipe_handle::buffers_type> 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<ULONG_PTR>(-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::buffers_type> 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::buffers_type> 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::const_buffers_type> pipe_handle::write(pipe_handle::io_request<pipe_handle::const_buffers_type> reqs, deadline d) noexcept
+pipe_handle::io_result<pipe_handle::const_buffers_type> pipe_handle::_do_write(pipe_handle::io_request<pipe_handle::const_buffers_type> 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_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<const_buffers_type> _do_barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<buffers_type> _do_read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_write(io_request<const_buffers_type> 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<std::vector<file_handle::extent_pair>> extents() const noexcept override { return std::vector<file_handle::extent_pair>{{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<buffers_type> read(io_request<buffers_type> 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<const_buffers_type> write(io_request<const_buffers_type> 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<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<handle> 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<size_type> read(extent_type offset, std::initializer_list<buffer_type> lst, deadline d = deadline()) noexcept
- {
- buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
- memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
- io_request<buffers_type> 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<extent_type> 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<path_handle> 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<void> 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<void> 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<file_handle> 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<void> 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<handle> clone() const noexcept;
+ LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<handle> 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<unsigned>(_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 <http://www.nedproductions.biz/> (20 commits)
+(C) 2015-2019 Niall Douglas <http://www.nedproductions.biz/> (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<byte> 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<byte> 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<buffer_type>::value, "buffer_type is not a trivial type!");
- static_assert(std::is_trivial<const_buffer_type>::value, "const_buffer_type is not a trivial type!");
- static_assert(std::is_standard_layout<buffer_type>::value, "buffer_type is not a standard layout type!");
- static_assert(std::is_standard_layout<const_buffer_type>::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<buffer_type>;
- //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
- using const_buffers_type = span<const_buffer_type>;
-#ifndef NDEBUG
- // Is trivial in all ways, except default constructibility
- static_assert(std::is_trivially_copyable<buffers_type>::value, "buffers_type is not trivially copyable!");
- // static_assert(std::is_trivially_assignable<buffers_type, buffers_type>::value, "buffers_type is not trivially assignable!");
- // static_assert(std::is_trivially_destructible<buffers_type>::value, "buffers_type is not trivially destructible!");
- // static_assert(std::is_trivially_copy_constructible<buffers_type>::value, "buffers_type is not trivially copy constructible!");
- // static_assert(std::is_trivially_move_constructible<buffers_type>::value, "buffers_type is not trivially move constructible!");
- // static_assert(std::is_trivially_copy_assignable<buffers_type>::value, "buffers_type is not trivially copy assignable!");
- // static_assert(std::is_trivially_move_assignable<buffers_type>::value, "buffers_type is not trivially move assignable!");
- static_assert(std::is_standard_layout<buffers_type>::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 <class T> 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<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copyable!");
- // static_assert(std::is_trivially_assignable<io_request<buffers_type>, io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially assignable!");
- // static_assert(std::is_trivially_destructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially destructible!");
- // static_assert(std::is_trivially_copy_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy constructible!");
- // static_assert(std::is_trivially_move_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move constructible!");
- // static_assert(std::is_trivially_copy_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy assignable!");
- // static_assert(std::is_trivially_move_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move assignable!");
- static_assert(std::is_standard_layout<io_request<buffers_type>>::value, "io_request<buffers_type> is not a standard layout type!");
-#endif
- //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction.
- template <class T> struct io_result : public LLFIO_V2_NAMESPACE::result<T>
- {
- using Base = LLFIO_V2_NAMESPACE::result<T>;
- size_type _bytes_transferred{static_cast<size_type>(-1)};
-
-#if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug
- constexpr io_result()
- : Base()
- {
- }
- template <class... Args>
- constexpr io_result(Args &&... args)
- : Base(std::forward<Args>(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<size_type>(-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<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copyable!");
-// static_assert(std::is_trivially_assignable<io_result<buffers_type>, io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially assignable!");
-// static_assert(std::is_trivially_destructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially destructible!");
-// static_assert(std::is_trivially_copy_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy constructible!");
-// static_assert(std::is_trivially_move_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move constructible!");
-// static_assert(std::is_trivially_copy_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy assignable!");
-// static_assert(std::is_trivially_move_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move assignable!");
-//! \todo Why is io_result<buffers_type> not a standard layout type?
-// static_assert(std::is_standard_layout<result<buffers_type>>::value, "result<buffers_type> is not a standard layout type!");
-// static_assert(std::is_standard_layout<io_result<buffers_type>>::value, "io_result<buffers_type> 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 <class T> using io_request = io_multiplexer::io_request<T>;
+ template <class T> using io_result = io_multiplexer::io_result<T>;
+ 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<void> 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<void> 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<registered_buffer_type> _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<buffers_type> _do_read(io_request<buffers_type> reqs, deadline d) noexcept;
+ //! The virtualised implementation of `write()` used if no multiplexer has been set.
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_write(io_request<const_buffers_type> reqs, deadline d) noexcept;
+ //! The virtualised implementation of `barrier()` used if no multiplexer has been set.
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_barrier(io_request<const_buffers_type> 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<registered_buffer_type> 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<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept;
+ io_result<buffers_type> read(io_request<buffers_type> 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<registered_buffers_type> read(io_request<registered_buffers_type> 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<size_type> read(extent_type offset, std::initializer_list<buffer_type> lst, deadline d = deadline()) noexcept
+ {
+ buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
+ memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
+ io_request<buffers_type> 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<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept;
- //! \overload
+ io_result<const_buffers_type> write(io_request<const_buffers_type> 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<registered_const_buffers_type> read(io_request<registered_const_buffers_type> 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<size_type> write(extent_type offset, std::initializer_list<const_buffer_type> 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<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<registered_const_buffers_type> barrier(io_request<registered_const_buffers_type> reqs = io_request<registered_const_buffers_type>(), 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 <http://www.nedproductions.biz/> (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 <memory> // 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:
+
+<table>
+<tr><th>LLFIO i/o class<th>POSIX<th>Windows
+<tr><td><code>directory_handle</code><td>No effect<td>Creates `HANDLE` as `OVERLAPPED`
+<tr><td><code>file_handle</code><td>No effect<td>Creates `HANDLE` as `OVERLAPPED`
+<tr><td><code>map_handle</code><td>No effect<td>No effect
+<tr><td><code>mapped_file_handle</code><td>No effect<td>Creates `HANDLE` as `OVERLAPPED`, but i/o is to map not file
+<tr><td><code>pipe_handle</code><td>Creates file descriptor as non-blocking<td>Creates `HANDLE` as `OVERLAPPED`
+<tr><td><code>section_handle</code><td>No effect<td>Creates `HANDLE` as `OVERLAPPED`
+<tr><td><code>symlink_handle</code><td>No effect<td>Creates `HANDLE` as `OVERLAPPED`
+</table>
+
+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<int> _register_io_handle(io_handle *h) noexcept = 0;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> _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<byte> 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<byte> 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<buffer_type>;
+
+ //! 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<const byte> 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<byte> 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<byte> 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<buffer_type>::value, "buffer_type is not a trivial type!");
+ static_assert(std::is_trivial<const_buffer_type>::value, "const_buffer_type is not a trivial type!");
+ static_assert(std::is_standard_layout<buffer_type>::value, "buffer_type is not a standard layout type!");
+ static_assert(std::is_standard_layout<const_buffer_type>::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<const_buffer_type>;
+
+ //! The scatter buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ using buffers_type = span<buffer_type>;
+ //! The gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ using const_buffers_type = span<const_buffer_type>;
+#ifndef NDEBUG
+ // Is trivial in all ways, except default constructibility
+ static_assert(std::is_trivially_copyable<buffers_type>::value, "buffers_type is not trivially copyable!");
+ // static_assert(std::is_trivially_assignable<buffers_type, buffers_type>::value, "buffers_type is not trivially assignable!");
+ // static_assert(std::is_trivially_destructible<buffers_type>::value, "buffers_type is not trivially destructible!");
+ // static_assert(std::is_trivially_copy_constructible<buffers_type>::value, "buffers_type is not trivially copy constructible!");
+ // static_assert(std::is_trivially_move_constructible<buffers_type>::value, "buffers_type is not trivially move constructible!");
+ // static_assert(std::is_trivially_copy_assignable<buffers_type>::value, "buffers_type is not trivially copy assignable!");
+ // static_assert(std::is_trivially_move_assignable<buffers_type>::value, "buffers_type is not trivially move assignable!");
+ static_assert(std::is_standard_layout<buffers_type>::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<registered_buffer_type *>;
+ //! The registered gather buffers type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ using registered_const_buffers_type = span<registered_const_buffer_type *>;
+
+ //! The i/o request type used by this handle. Guaranteed to be `TrivialType` apart from construction, and `StandardLayoutType`.
+ template <class T> 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<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copyable!");
+ // static_assert(std::is_trivially_assignable<io_request<buffers_type>, io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially assignable!");
+ // static_assert(std::is_trivially_destructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially destructible!");
+ // static_assert(std::is_trivially_copy_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy constructible!");
+ // static_assert(std::is_trivially_move_constructible<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move constructible!");
+ // static_assert(std::is_trivially_copy_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially copy assignable!");
+ // static_assert(std::is_trivially_move_assignable<io_request<buffers_type>>::value, "io_request<buffers_type> is not trivially move assignable!");
+ static_assert(std::is_standard_layout<io_request<buffers_type>>::value, "io_request<buffers_type> is not a standard layout type!");
+#endif
+ //! The i/o result type used by this handle. Guaranteed to be `TrivialType` apart from construction.
+ template <class T> struct io_result : public LLFIO_V2_NAMESPACE::result<T>
+ {
+ using Base = LLFIO_V2_NAMESPACE::result<T>;
+ size_type _bytes_transferred{static_cast<size_type>(-1)};
+
+#if defined(_MSC_VER) && !defined(__clang__) // workaround MSVC parsing bug
+ constexpr io_result()
+ : Base()
+ {
+ }
+ template <class... Args>
+ constexpr io_result(Args &&... args)
+ : Base(std::forward<Args>(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<size_type>(-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<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copyable!");
+// static_assert(std::is_trivially_assignable<io_result<buffers_type>, io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially assignable!");
+// static_assert(std::is_trivially_destructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially destructible!");
+// static_assert(std::is_trivially_copy_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy constructible!");
+// static_assert(std::is_trivially_move_constructible<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move constructible!");
+// static_assert(std::is_trivially_copy_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially copy assignable!");
+// static_assert(std::is_trivially_move_assignable<io_result<buffers_type>>::value, "io_result<buffers_type> is not trivially move assignable!");
+//! \todo Why is io_result<buffers_type> not a standard layout type?
+// static_assert(std::is_standard_layout<result<buffers_type>>::value, "result<buffers_type> is not a standard layout type!");
+// static_assert(std::is_standard_layout<io_result<buffers_type>>::value, "io_result<buffers_type> 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<registered_buffer_type> 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<buffers_type> do_io_handle_read(io_handle *h, io_request<buffers_type> reqs, deadline d) noexcept = 0;
+ //! Implements `io_handle::read()`
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<registered_buffers_type> do_io_handle_read(io_handle *h, io_request<registered_buffers_type> reqs, deadline d) noexcept = 0;
+ //! Implements `io_handle::write()`
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> do_io_handle_write(io_handle *h, io_request<const_buffers_type> reqs, deadline d) noexcept = 0;
+ //! Implements `io_handle::write()`
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<registered_const_buffers_type> do_io_handle_write(io_handle *h, io_request<registered_const_buffers_type> reqs, deadline d) noexcept = 0;
+ //! Implements `io_handle::barrier()`
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> do_io_handle_barrier(io_handle *h, io_request<const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept = 0;
+ //! Implements `io_handle::barrier()`
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<registered_const_buffers_type> do_io_handle_barrier(io_handle *h, io_request<registered_const_buffers_type> reqs, barrier_kind kind, deadline d) noexcept = 0;
+};
+//! A unique ptr to an i/o multiplexer implementation.
+using io_multiplexer_ptr = std::unique_ptr<io_multiplexer>;
+
+//! \brief Choose the best available i/o multiplexer implementation for this platform.
+// LLFIO_HEADERS_ONLY_FUNC_SPEC result<io_multiplexer_ptr> multiplexer_best_available(size_t threads) noexcept;
+#if defined(__linux__) || DOXYGEN_IS_IN_THE_HOUSE
+// LLFIO_HEADERS_ONLY_FUNC_SPEC result<io_multiplexer_ptr> multiplexer_linux_epoll(size_t threads) noexcept;
+// LLFIO_HEADERS_ONLY_FUNC_SPEC result<io_multiplexer_ptr> multiplexer_linux_io_uring() noexcept;
+#endif
+#if(defined(__FreeBSD__) || defined(__APPLE__)) || DOXYGEN_IS_IN_THE_HOUSE
+// LLFIO_HEADERS_ONLY_FUNC_SPEC result<io_multiplexer_ptr> multiplexer_bsd_kqueue(size_t threads) noexcept;
+#endif
+#if defined(_WIN32) || DOXYGEN_IS_IN_THE_HOUSE
+// LLFIO_HEADERS_ONLY_FUNC_SPEC result<io_multiplexer_ptr> 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 <http://www.nedproductions.biz/> (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 <cassert>
-#include <deque>
-#include <mutex>
-
-#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 <csignal>
-// 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 <class T> using io_request = io_handle::io_request<T>;
- //! The i/o result type used by this i/o service
- template <class T> using io_result = io_handle::io_result<T>;
-
-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<void(io_service *)> f;
- post_info(io_service *s, detail::function_ptr<void(io_service *)> _f)
- : service(s)
- , f(std::move(_f))
- {
- }
- };
- std::deque<post_info> _posts;
- using shared_size_type = std::atomic<size_type>;
- shared_size_type _work_queued;
-#if LLFIO_USE_POSIX_AIO
- bool _use_kqueues;
-#if LLFIO_COMPILE_KQUEUES
- int _kqueueh;
-#endif
- std::vector<struct aiocb *> _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<decltype(_posts_lock)> 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<bool> _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<bool> run_until(deadline d) noexcept;
- //! \overload
- result<bool> run() noexcept { return run_until(deadline()); }
-
-private:
- LLFIO_HEADERS_ONLY_VIRTUAL_SPEC void _post(detail::function_ptr<void(io_service *)> &&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 <class U> void post(U &&f) { _post(detail::make_function_ptr<void(io_service *)>(std::forward<U>(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<void> 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<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<const_buffers_type> _do_barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> _do_read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_write(io_request<const_buffers_type> 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<buffers_type> read(io_request<buffers_type> 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<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
using io_handle::write;
};
@@ -776,6 +777,84 @@ template <class T> constexpr inline span<T> in_place_attach(map_handle &mh) noex
return span<T>{reinterpret_cast<T *>(mh.address()), mh.length() / sizeof(T)};
}
+namespace detail
+{
+ inline result<io_handle::registered_buffer_type> 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<registered_buffer_type_indirect>(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::registered_buffer_type> 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<const_buffers_type> _do_barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), 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<buffers_type> _do_read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override { return _mh.read(reqs, d); }
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override
+ {
+ if(!!(_sh.section_flags() & section_handle::flag::write_via_syscall))
+ {
+ const auto batch = max_buffers();
+ io_request<const_buffers_type> 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<void> close() noexcept override;
LLFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override;
- LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), barrier_kind kind = barrier_kind::nowait_data_only, deadline d = deadline()) noexcept override { return _mh.barrier(reqs, kind, d); }
result<mapped_file_handle> 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<void> 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<buffers_type> read(io_request<buffers_type> 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<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override
- {
- if(!!(_sh.section_flags() & section_handle::flag::write_via_syscall))
- {
- const auto batch = max_buffers();
- io_request<const_buffers_type> 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 <http://www.nedproductions.biz/> (20 commits)
+(C) 2017-2020 Niall Douglas <http://www.nedproductions.biz/> (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<path_handle> 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_handle> 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_handle> 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<pipe_handle> 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<pipe_handle> 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<path_handle> 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<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
- LLFIO_MAKE_FREE_FUNCTION
- LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override;
-#endif
- //! Convenience initialiser list based overload for `read()`
- LLFIO_MAKE_FREE_FUNCTION
- io_result<size_type> read(extent_type offset, std::initializer_list<buffer_type> lst, deadline d = deadline()) noexcept
- {
- buffer_type *_reqs = reinterpret_cast<buffer_type *>(alloca(sizeof(buffer_type) * lst.size()));
- memcpy(_reqs, lst.begin(), sizeof(buffer_type) * lst.size());
- io_request<buffers_type> 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<buffers_type> _do_read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override;
+ LLFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> _do_write(io_request<const_buffers_type> 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 <class T> using atomic_lazy = OUTCOME_V2_NAMESPACE::experimental::await
template <class T> using eager = OUTCOME_V2_NAMESPACE::experimental::awaitables::eager<T>;
template <class T> using lazy = OUTCOME_V2_NAMESPACE::experimental::awaitables::lazy<T>;
template <class T = void> using coroutine_handle = OUTCOME_V2_NAMESPACE::awaitables::coroutine_handle<T>;
+template <class... Args> using coroutine_traits = OUTCOME_V2_NAMESPACE::awaitables::coroutine_traits<Args...>;
+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 <class T> using atomic_lazy = OUTCOME_V2_NAMESPACE::awaitables::atomic_
template <class T> using eager = OUTCOME_V2_NAMESPACE::awaitables::eager<T>;
template <class T> using lazy = OUTCOME_V2_NAMESPACE::awaitables::lazy<T>;
template <class T = void> using coroutine_handle = OUTCOME_V2_NAMESPACE::awaitables::coroutine_handle<T>;
+template <class... Args> using coroutine_traits = OUTCOME_V2_NAMESPACE::awaitables::coroutine_traits<Args...>;
+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>, "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 <class T> constexpr T default_value() { return T{}; }
- template <> constexpr storage_types map_to_storage_type<io_service::extent_type>() { return storage_types::extent_type; }
- template <> constexpr io_service::extent_type default_value<io_service::extent_type>() { return static_cast<io_service::extent_type>(-1); }
+ template <> constexpr storage_types map_to_storage_type<io_handle::extent_type>() { return storage_types::extent_type; }
+ template <> constexpr io_handle::extent_type default_value<io_handle::extent_type>() { return static_cast<io_handle::extent_type>(-1); }
template <> constexpr storage_types map_to_storage_type<unsigned int>() { return storage_types::unsigned_int; }
template <> constexpr unsigned int default_value<unsigned int>() { return static_cast<unsigned int>(-1); }
// template<> constexpr storage_types map_to_storage_type<unsigned long long>() { return storage_types::unsigned_long_long; }
@@ -148,7 +148,7 @@ namespace storage_profile
switch(type)
{
case storage_types::extent_type:
- return f(*reinterpret_cast<const item<io_service::extent_type> *>(static_cast<const item_base *>(this)));
+ return f(*reinterpret_cast<const item<io_handle::extent_type> *>(static_cast<const item_base *>(this)));
case storage_types::unsigned_int:
return f(*reinterpret_cast<const item<unsigned int> *>(static_cast<const item_base *>(this)));
case storage_types::unsigned_long_long:
@@ -309,21 +309,21 @@ namespace storage_profile
// Storage characteristics
item<std::string> device_name = {"storage:device:name", &storage::device}; // e.g. WDC WD30EFRX-68EUZN0
item<unsigned> device_min_io_size = {"storage:device:min_io_size", &storage::device}; // e.g. 4096
- item<io_service::extent_type> device_size = {"storage:device:size", &storage::device};
+ item<io_handle::extent_type> device_size = {"storage:device:size", &storage::device};
// Filing system characteristics
item<std::string> fs_name = {"storage:fs:name", &storage::fs};
item<std::string> fs_config = {"storage:fs:config", &storage::fs}; // POSIX mount options, ZFS pool properties etc
// item<std::string> fs_ffeatures = { "storage:fs:features" }; // Standardised features???
- item<io_service::extent_type> fs_size = {"storage:fs:size", &storage::fs};
+ item<io_handle::extent_type> fs_size = {"storage:fs:size", &storage::fs};
item<float> fs_in_use = {"storage:fs:in_use", &storage::fs};
// Test results on this filing system, storage and system
- item<io_service::extent_type> 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<io_service::extent_type> max_aligned_atomic_rewrite = {"concurrency:max_aligned_atomic_rewrite", concurrency::atomic_rewrite_quantum,
+ item<io_handle::extent_type> 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<io_handle::extent_type> 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<io_service::extent_type> 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<io_handle::extent_type> 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 "