diff options
-rw-r--r-- | Readme.md | 6 | ||||
-rw-r--r-- | cmake/headers.cmake | 3 | ||||
-rw-r--r-- | include/afio/revision.hpp | 6 | ||||
-rw-r--r-- | include/afio/v2.0/afio.hpp | 1 | ||||
-rw-r--r-- | include/afio/v2.0/algorithm/cached_parent_handle_adapter.hpp | 177 | ||||
-rw-r--r-- | include/afio/v2.0/async_file_handle.hpp | 26 | ||||
-rw-r--r-- | include/afio/v2.0/config.hpp | 6 | ||||
-rw-r--r-- | include/afio/v2.0/detail/impl/cached_parent_handle_adapter.ipp | 128 | ||||
-rw-r--r-- | include/afio/v2.0/directory_handle.hpp | 12 | ||||
-rw-r--r-- | include/afio/v2.0/file_handle.hpp | 12 | ||||
-rw-r--r-- | include/afio/v2.0/fs_handle.hpp | 11 | ||||
-rw-r--r-- | include/afio/v2.0/handle.hpp | 21 | ||||
-rw-r--r-- | include/afio/v2.0/map_handle.hpp | 19 | ||||
-rw-r--r-- | include/afio/v2.0/mapped_file_handle.hpp | 14 | ||||
-rw-r--r-- | include/afio/v2.0/path_handle.hpp | 8 | ||||
-rw-r--r-- | test/tests/current_path.cpp | 149 |
16 files changed, 517 insertions, 82 deletions
@@ -6,12 +6,6 @@ Tarballs of source and prebuilt binaries for Linux x64 and Windows x64: http://m ### Immediate todos in order of priority: -- [ ] Make some system for registering static constructors - - Then make the random_X and temp_X implementations common - - Then add a sibling_X() -- [ ] Make a templated file handle adapter which keeps a `shared_ptr<directory_handle>` -for the parent of the inode, thus making race free operations much quicker and -reliable but at the cost of construction time. - [ ] `atomic_append` isn't actually being tested in shared_fs_mutex - [ ] Implement a non-toy ACID key-value BLOB store and send it to Boost for peer review. - [ ] All time based kernel tests need to use soak test based API and auto adjust to diff --git a/cmake/headers.cmake b/cmake/headers.cmake index 45094bec..9679c71f 100644 --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -7,6 +7,7 @@ set(afio_HEADERS "include/afio/ntkernel-error-category/include/ntkernel_category.hpp" "include/afio/revision.hpp" "include/afio/v2.0/afio.hpp" + "include/afio/v2.0/algorithm/cached_parent_handle_adapter.hpp" "include/afio/v2.0/algorithm/mapped_view.hpp" "include/afio/v2.0/algorithm/section_allocator.hpp" "include/afio/v2.0/algorithm/shared_fs_mutex/atomic_append.hpp" @@ -38,6 +39,7 @@ set(afio_HEADERS "include/afio/version.hpp" "include/afio/ntkernel-error-category/include/detail/ntkernel-table.ipp" "include/afio/ntkernel-error-category/include/detail/ntkernel_category_impl.ipp" + "include/afio/v2.0/detail/impl/cached_parent_handle_adapter.ipp" "include/afio/v2.0/detail/impl/path_discovery.ipp" "include/afio/v2.0/detail/impl/posix/async_file_handle.ipp" "include/afio/v2.0/detail/impl/posix/directory_handle.ipp" @@ -48,6 +50,7 @@ set(afio_HEADERS "include/afio/v2.0/detail/impl/posix/io_service.ipp" "include/afio/v2.0/detail/impl/posix/map_handle.ipp" "include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp" + "include/afio/v2.0/detail/impl/posix/path_discovery.ipp" "include/afio/v2.0/detail/impl/posix/path_handle.ipp" "include/afio/v2.0/detail/impl/posix/stat.ipp" "include/afio/v2.0/detail/impl/posix/statfs.ipp" diff --git a/include/afio/revision.hpp b/include/afio/revision.hpp index df19d14a..ab33f1b5 100644 --- a/include/afio/revision.hpp +++ b/include/afio/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 AFIO_PREVIOUS_COMMIT_REF f6ce8d2618caedd8bd5e5637ff63dfa83f46180a -#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-15 02:15:16 +00:00" -#define AFIO_PREVIOUS_COMMIT_UNIQUE f6ce8d26 +#define AFIO_PREVIOUS_COMMIT_REF a8fd33814a0e858621940d3f91a82457b73e0a53 +#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-17 01:28:52 +00:00" +#define AFIO_PREVIOUS_COMMIT_UNIQUE a8fd3381 diff --git a/include/afio/v2.0/afio.hpp b/include/afio/v2.0/afio.hpp index ca96d685..82ede786 100644 --- a/include/afio/v2.0/afio.hpp +++ b/include/afio/v2.0/afio.hpp @@ -63,6 +63,7 @@ import AFIO_MODULE_NAME; #include "statfs.hpp" #include "storage_profile.hpp" +#include "algorithm/cached_parent_handle_adapter.hpp" #include "algorithm/mapped_view.hpp" #include "algorithm/section_allocator.hpp" #include "algorithm/shared_fs_mutex/atomic_append.hpp" diff --git a/include/afio/v2.0/algorithm/cached_parent_handle_adapter.hpp b/include/afio/v2.0/algorithm/cached_parent_handle_adapter.hpp new file mode 100644 index 00000000..ea9ffb39 --- /dev/null +++ b/include/afio/v2.0/algorithm/cached_parent_handle_adapter.hpp @@ -0,0 +1,177 @@ +/* A parent handle caching adapter +(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits) +File Created: Sept 2017 + + +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 AFIO_CACHED_PARENT_HANDLE_ADAPTER_HPP +#define AFIO_CACHED_PARENT_HANDLE_ADAPTER_HPP + +#include "../directory_handle.hpp" + +//! \file cached_parent_handle_adapter.hpp Adapts any `fs_handle` to cache its parent directory handle +AFIO_V2_NAMESPACE_BEGIN + +namespace algorithm +{ + namespace detail + { + struct AFIO_DECL cached_path_handle : public std::enable_shared_from_this<cached_path_handle> + { + directory_handle h; + filesystem::path _lastpath; + cached_path_handle(directory_handle &&_h) + : h(std::move(_h)) + { + } + AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<filesystem::path> current_path(const filesystem::path &append) noexcept; + }; + using cached_path_handle_ptr = std::shared_ptr<cached_path_handle>; + // Passed the base and path of the adapted handle being created, returns a handle to the containing directory and the leafname + AFIO_HEADERS_ONLY_FUNC_SPEC std::pair<cached_path_handle_ptr, filesystem::path> get_cached_path_handle(const path_handle &base, path_view path); + } + + /*! \brief Adapts any `construct()`-able implementation to cache its parent directory handle in a process wide cache. + + For some use cases where one is calling `parent_path_handle()` or code which calls that function very frequently + e.g. calling `relink()` or `unlink()` a lot on many files with the same parent directory, having to constantly + fetch the current path, open the parent directory and verify inodes becomes unhelpfully inefficient. This + adapter keeps a process-wide hash table of directory handles shared between all instances of this adapter, + thus making calling `parent_path_handle()` almost zero cost. + + This adapter is of especial use on platforms which do not reliably implement per-fd path tracking for regular + files (Apple MacOS, FreeBSD) as `current_path()` is reimplemented to use the current path of the shared parent + directory instead. One loses race freedom within the contained directory, but that is the case on POSIX anyway. + + This adapter is also of use on platforms which do not implement path tracking for open handles at all (e.g. + Linux without `/proc` mounted) as the process-wide cache of directory handles retains the path of the directory + handle at the time of creation. Third party changes to the part of the filesystem you are working upon will + result in the inability to do race free unlinking etc, but if no third party changes are encountered it ought + to work well. + */ + template <class T> AFIO_REQUIRES(sizeof(construct<T>) > 0) class AFIO_DECL cached_parent_handle_adapter : public T + { + static_assert(sizeof(construct<T>) > 0, "Type T must be registered with the construct<T> framework so cached_parent_handle_adapter<T> knows how to construct it"); + + public: + //! The handle type being adapted + using adapted_handle_type = T; + using path_type = typename T::path_type; + + protected: + detail::cached_path_handle_ptr _sph; + filesystem::path _leafname; + + public: + cached_parent_handle_adapter() = default; + cached_parent_handle_adapter(const cached_parent_handle_adapter &) = default; + cached_parent_handle_adapter(cached_parent_handle_adapter &&) = default; + cached_parent_handle_adapter &operator=(const cached_parent_handle_adapter &) = default; + cached_parent_handle_adapter &operator=(cached_parent_handle_adapter &&) = default; + cached_parent_handle_adapter(adapted_handle_type &&o, const path_handle &base, path_view path) + : adapted_handle_type(std::move(o)) + { + auto r = detail::get_cached_path_handle(base, path); + _sph = std::move(r.first); + _leafname = std::move(r.second); + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~cached_parent_handle_adapter() + { + if(this->_v) + { + (void) cached_parent_handle_adapter::close(); + } + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_type> current_path() const noexcept override + { + AFIO_LOG_FUNCTION_CALL(this); + return _sph->current_path(_leafname); + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override + { + AFIO_LOG_FUNCTION_CALL(this); + OUTCOME_TRYV(adapted_handle_type::close()); + _sph.reset(); + _leafname.clear(); + return success(); + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override + { + AFIO_LOG_FUNCTION_CALL(this); + _sph.reset(); + _leafname.clear(); + return adapted_handle_type::release(); + } + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_handle> parent_path_handle(deadline /* unused */ = std::chrono::seconds(30)) const noexcept override + { + AFIO_LOG_FUNCTION_CALL(this); + OUTCOME_TRY(ret, _sph->h.clone()); + return path_handle(std::move(ret)); + } + }; + /*! \brief Constructs a `T` adapted into a parent handle caching implementation. + + This function works via the `construct<T>()` free function framework for which your `handle` + implementation must have registered its construction details. + */ + template <class T, class... Args> inline result<cached_parent_handle_adapter<T>> cache_parent(Args &&... args) noexcept + { + construct<T> constructor{std::forward<Args>(args)...}; + OUTCOME_TRY(h, constructor()); + try + { + return cached_parent_handle_adapter<T>(std::move(h), constructor.base, constructor._path); + } + catch(...) + { + return error_from_exception(); + } + } + +} // namespace + +//! \brief Constructor for `algorithm::::cached_parent_handle_adapter<T>` +template <class T> struct construct<algorithm::cached_parent_handle_adapter<T>> +{ + construct<T> args; + result<algorithm::cached_parent_handle_adapter<T>> operator()() const noexcept + { + OUTCOME_TRY(h, args()); + try + { + return algorithm::cached_parent_handle_adapter<T>(std::move(h), args.base, args._path); + } + catch(...) + { + return error_from_exception(); + } + } +}; + +AFIO_V2_NAMESPACE_END + +#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS) +#define AFIO_INCLUDED_BY_HEADER 1 +#include "../detail/impl/cached_parent_handle_adapter.ipp" +#undef AFIO_INCLUDED_BY_HEADER +#endif + +#endif diff --git a/include/afio/v2.0/async_file_handle.hpp b/include/afio/v2.0/async_file_handle.hpp index 24bef6dc..5d5dd56f 100644 --- a/include/afio/v2.0/async_file_handle.hpp +++ b/include/afio/v2.0/async_file_handle.hpp @@ -426,6 +426,20 @@ public: #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 @@ -444,9 +458,11 @@ using the given io_service. \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 +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)); + 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 @@ -473,7 +489,8 @@ 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_close) noexcept +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_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)); } @@ -491,7 +508,8 @@ inline result<async_file_handle> async_temp_inode(io_service &service, const pat { 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)); } -inline async_file_handle::io_result<async_file_handle::const_buffers_type> barrier(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs = async_file_handle::io_request<async_file_handle::const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept +inline async_file_handle::io_result<async_file_handle::const_buffers_type> barrier(async_file_handle &self, async_file_handle::io_request<async_file_handle::const_buffers_type> reqs = async_file_handle::io_request<async_file_handle::const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, + deadline d = deadline()) noexcept { return self.barrier(std::forward<decltype(reqs)>(reqs), std::forward<decltype(wait_for_device)>(wait_for_device), std::forward<decltype(and_metadata)>(and_metadata), std::forward<decltype(d)>(d)); } diff --git a/include/afio/v2.0/config.hpp b/include/afio/v2.0/config.hpp index 617b93c4..b1e7430b 100644 --- a/include/afio/v2.0/config.hpp +++ b/include/afio/v2.0/config.hpp @@ -217,6 +217,12 @@ AFIO_V2_NAMESPACE_END #else #error No <filesystem> implementation found #endif +AFIO_V2_NAMESPACE_BEGIN +struct path_hasher +{ + size_t operator()(const filesystem::path &p) const { return std::hash<filesystem::path::string_type>()(p.native()); } +}; +AFIO_V2_NAMESPACE_END // Configure AFIO_DECL diff --git a/include/afio/v2.0/detail/impl/cached_parent_handle_adapter.ipp b/include/afio/v2.0/detail/impl/cached_parent_handle_adapter.ipp new file mode 100644 index 00000000..478d7485 --- /dev/null +++ b/include/afio/v2.0/detail/impl/cached_parent_handle_adapter.ipp @@ -0,0 +1,128 @@ +/* A parent handle caching adapter +(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (12 commits) +File Created: Sept 2017 + + +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 "../../algorithm/cached_parent_handle_adapter.hpp" + +#include <mutex> +#include <unordered_map> + +AFIO_V2_NAMESPACE_BEGIN + +namespace algorithm +{ + namespace detail + { + struct cached_path_handle_map_ + { + std::mutex lock; + size_t gc_count{0}; + std::unordered_map<filesystem::path, std::weak_ptr<cached_path_handle>, path_hasher> by_path; + }; + inline cached_path_handle_map_ &cached_path_handle_map() + { + static cached_path_handle_map_ map; + return map; + } + AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<filesystem::path> cached_path_handle::current_path(const filesystem::path &append) noexcept + { + try + { + auto ret = h.current_path(); + auto &map = cached_path_handle_map(); + std::lock_guard<std::mutex> g(map.lock); + if(!ret) + { + std::string msg("cached_path_handle::current_path() failed to retrieve current path of cached handle due to "); + msg.append(ret.error().message()); + AFIO_LOG_WARN(nullptr, msg.c_str()); + } + else if(!ret.value().empty() && ret.value() != _lastpath) + { + map.by_path.erase(_lastpath); + _lastpath = std::move(ret).value(); + map.by_path.emplace(_lastpath, shared_from_this()); + } + return _lastpath / append; + } + catch(...) + { + return error_from_exception(); + } + } + AFIO_HEADERS_ONLY_FUNC_SPEC std::pair<cached_path_handle_ptr, filesystem::path> get_cached_path_handle(const path_handle &base, path_view path) + { + path_view leaf(path.filename()); + path.remove_filename(); + filesystem::path dirpath; + if(base.is_valid()) + dirpath = base.current_path().value() / path.path(); + else + { + dirpath = path.path(); + if(dirpath.empty()) + { + dirpath = filesystem::current_path(); + path = dirpath; + } + } + auto &map = cached_path_handle_map(); + std::lock_guard<std::mutex> g(map.lock); + auto it = map.by_path.find(dirpath); + if(it != map.by_path.end()) + { + cached_path_handle_ptr ret = it->second.lock(); + if(ret) + return {ret, leaf.path()}; + } + cached_path_handle_ptr ret = std::make_shared<cached_path_handle>(directory_handle::directory(base, path).value()); + ret->_lastpath = ret->h.current_path().value(); + if(it != map.by_path.end()) + { + it->second = ret; + } + else + { + map.by_path.emplace(ret->_lastpath, ret); + } + if(map.gc_count++ >= 1024) + { + for(auto it = map.by_path.begin(); it != map.by_path.end();) + { + if(it->second.expired()) + { + it = map.by_path.erase(it); + } + else + { + ++it; + } + } + map.gc_count = 0; + } + return {ret, leaf.path()}; + } + } +} + +AFIO_V2_NAMESPACE_END diff --git a/include/afio/v2.0/directory_handle.hpp b/include/afio/v2.0/directory_handle.hpp index 65d4789b..4dadec2f 100644 --- a/include/afio/v2.0/directory_handle.hpp +++ b/include/afio/v2.0/directory_handle.hpp @@ -263,6 +263,18 @@ inline std::ostream &operator<<(std::ostream &s, const directory_handle::enumera return s << "afio::directory_handle::enumerate_info"; } +//! \brief Constructor for `directory_handle` +template <> struct construct<directory_handle> +{ + const path_handle &base; + directory_handle::path_view_type _path; + directory_handle::mode _mode = directory_handle::mode::read; + directory_handle::creation _creation = directory_handle::creation::open_existing; + directory_handle::caching _caching = directory_handle::caching::all; + directory_handle::flag flags = directory_handle::flag::none; + result<directory_handle> operator()() const noexcept { return directory_handle::directory(base, _path, _mode, _creation, _caching, flags); } +}; + // BEGIN make_free_functions.py //! Swap with another instance inline void swap(directory_handle &self, directory_handle &o) noexcept diff --git a/include/afio/v2.0/file_handle.hpp b/include/afio/v2.0/file_handle.hpp index 9c207c20..75e9d6c7 100644 --- a/include/afio/v2.0/file_handle.hpp +++ b/include/afio/v2.0/file_handle.hpp @@ -264,6 +264,18 @@ public: AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> zero(extent_type offset, extent_type bytes, deadline d = deadline()) noexcept; }; +//! \brief Constructor for `file_handle` +template <> struct construct<file_handle> +{ + const path_handle &base; + file_handle::path_view_type _path; + file_handle::mode _mode = file_handle::mode::read; + file_handle::creation _creation = file_handle::creation::open_existing; + file_handle::caching _caching = file_handle::caching::all; + file_handle::flag flags = file_handle::flag::none; + result<file_handle> operator()() const noexcept { return file_handle::file(base, _path, _mode, _creation, _caching, flags); } +}; + // BEGIN make_free_functions.py //! Swap with another instance inline void swap(file_handle &self, file_handle &o) noexcept diff --git a/include/afio/v2.0/fs_handle.hpp b/include/afio/v2.0/fs_handle.hpp index 986131cf..a0ea94d7 100644 --- a/include/afio/v2.0/fs_handle.hpp +++ b/include/afio/v2.0/fs_handle.hpp @@ -41,6 +41,8 @@ AFIO_V2_NAMESPACE_EXPORT_BEGIN /*! \class fs_handle \brief A handle to something with a device and inode number. + +\sa `algorithm::cached_parent_handle_adapter<T>` */ class AFIO_DECL fs_handle { @@ -117,8 +119,11 @@ public: success until the deadline given. \mallocs Calls `current_path()` and thus is both expensive and calls malloc many times. + + \sa `algorithm::cached_parent_handle_adapter<T>` which overrides this with a zero cost + implementation, thus making unlinking and relinking very considerably quicker. */ - result<path_handle> parent_path_handle(deadline d = std::chrono::seconds(30)) const noexcept; + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_handle> parent_path_handle(deadline d = std::chrono::seconds(30)) const noexcept; /*! 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 @@ -141,9 +146,10 @@ public: \param d The deadline by which the matching of the containing directory to the open handle's inode must succeed, else `std::errc::timed_out` will be returned. \mallocs Except on platforms with race free syscalls for renaming open handles (Windows), calls - `current_path()` and thus is both expensive and calls malloc many times. + `current_path()` via `parent_path_handle()` and thus is both expensive and calls malloc many times. */ AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> relink(const path_handle &base, path_view_type newpath, bool atomic_replace = true, deadline d = std::chrono::seconds(30)) noexcept; /*! Unlinks the current path of this open handle, causing its entry to immediately disappear from the filing system. @@ -169,6 +175,7 @@ public: `current_path()` if `flag::disable_safety_unlinks` is not set. */ AFIO_MAKE_FREE_FUNCTION + AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> unlink(deadline d = std::chrono::seconds(30)) noexcept; }; diff --git a/include/afio/v2.0/handle.hpp b/include/afio/v2.0/handle.hpp index 62e93456..6418f186 100644 --- a/include/afio/v2.0/handle.hpp +++ b/include/afio/v2.0/handle.hpp @@ -237,6 +237,12 @@ public: instead! \mallocs At least one malloc for the `path_type`, likely several more. + \sa `algorithm::cached_parent_handle_adapter<T>` which overrides this with an + implementation based on retrieving the current path of a cached handle to the parent + directory. On platforms with instability or failure to retrieve the correct current path + for regular files, the cached parent handle adapter works around the problem by + taking advantage of directory inodes not having the same instability problems on any + platform. */ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<path_type> current_path() const noexcept; //! Immediately close the native handle type managed by this handle @@ -379,6 +385,21 @@ inline std::ostream &operator<<(std::ostream &s, const handle::flag &v) return s << "afio::handle::flag::" << temp; } +/*! \brief Metaprogramming shim for constructing any `handle` subclass. + +Each `handle` implementation provides one or more static member functions used to construct it. +Each of these has a descriptive, unique name so it can be used as a free function which is +convenient and intuitive for human programmers. + +This design pattern is however inconvenient for generic code which needs a single way +of constructing some arbitrary unknown `handle` implementation. This shim function +provides that. +*/ +template <class T> struct construct +{ + result<T> operator()() const noexcept { static_assert(!std::is_same<T, T>::value, "construct<T>() was not specialised for the type T supplied"); } +}; + // Intercept when Outcome creates an errored result and log it to our log template <class T, class R> inline void hook_result_construction(OUTCOME_V2_NAMESPACE::in_place_type_t<T>, result<R> *res) noexcept { diff --git a/include/afio/v2.0/map_handle.hpp b/include/afio/v2.0/map_handle.hpp index d0016278..0cc10916 100644 --- a/include/afio/v2.0/map_handle.hpp +++ b/include/afio/v2.0/map_handle.hpp @@ -196,6 +196,16 @@ inline std::ostream &operator<<(std::ostream &s, const section_handle::flag &v) return s << "afio::section_handle::flag::" << temp; } +//! \brief Constructor for `section_handle` +template <> struct construct<section_handle> +{ + file_handle &backing; + section_handle::extent_type maximum_size = 0; + section_handle::flag _flag = section_handle::flag::read | section_handle::flag::write; + result<section_handle> operator()() const noexcept { return section_handle::section(backing, maximum_size, _flag); } +}; + + /*! \class map_handle \brief A handle to a memory mapped region of memory. @@ -386,6 +396,15 @@ public: using io_handle::write; }; +//! \brief Constructor for `map_handle` +template <> struct construct<map_handle> +{ + section_handle §ion; + map_handle::size_type bytes = 0; + map_handle::extent_type offset = 0; + section_handle::flag _flag = section_handle::flag::readwrite; + result<map_handle> operator()() const noexcept { return map_handle::map(section, bytes, offset, _flag); } +}; // BEGIN make_free_functions.py //! Swap with another instance diff --git a/include/afio/v2.0/mapped_file_handle.hpp b/include/afio/v2.0/mapped_file_handle.hpp index b7f96e56..49fb304a 100644 --- a/include/afio/v2.0/mapped_file_handle.hpp +++ b/include/afio/v2.0/mapped_file_handle.hpp @@ -433,6 +433,20 @@ public: AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> write(io_request<const_buffers_type> reqs, deadline d = deadline()) noexcept override { return _mh.write(std::move(reqs), std::move(d)); } }; +//! \brief Constructor for `mapped_file_handle` +template <> struct construct<mapped_file_handle> +{ + mapped_file_handle::size_type reservation; + const path_handle &base; + mapped_file_handle::path_view_type _path; + mapped_file_handle::mode _mode = mapped_file_handle::mode::read; + mapped_file_handle::creation _creation = mapped_file_handle::creation::open_existing; + mapped_file_handle::caching _caching = mapped_file_handle::caching::all; + mapped_file_handle::flag flags = mapped_file_handle::flag::none; + result<mapped_file_handle> operator()() const noexcept { return mapped_file_handle::mapped_file(reservation, base, _path, _mode, _creation, _caching, flags); } +}; + + // BEGIN make_free_functions.py //! Swap with another instance inline void swap(mapped_file_handle &self, mapped_file_handle &o) noexcept diff --git a/include/afio/v2.0/path_handle.hpp b/include/afio/v2.0/path_handle.hpp index 5c7b1375..c1b85e33 100644 --- a/include/afio/v2.0/path_handle.hpp +++ b/include/afio/v2.0/path_handle.hpp @@ -86,6 +86,14 @@ public: static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<path_handle> path(path_view_type _path) noexcept { return path(path_handle(), _path); } }; +//! \brief Constructor for `path_handle` +template <> struct construct<path_handle> +{ + const path_handle &base; + path_handle::path_view_type _path; + result<path_handle> operator()() const noexcept { return path_handle::path(base, _path); } +}; + // BEGIN make_free_functions.py /*! Create a path handle opening access to some location on the filing system. Some operating systems provide a particularly lightweight method of doing this diff --git a/test/tests/current_path.cpp b/test/tests/current_path.cpp index 29c98c16..c5aa9ec7 100644 --- a/test/tests/current_path.cpp +++ b/test/tests/current_path.cpp @@ -25,83 +25,98 @@ Distributed under the Boost Software License, Version 1.0. #include "../../include/afio/afio.hpp" #include "kerneltest/include/kerneltest.hpp" -static inline void TestHandleCurrentPath() +template <class FileHandleType, class DirectoryHandleType> static inline void TestHandleCurrentPath() { namespace afio = AFIO_V2_NAMESPACE; - afio::file_handle h1 = afio::file_handle::file({}, "tempfile", afio::file_handle::mode::write, afio::file_handle::creation::if_needed, afio::file_handle::caching::temporary, afio::file_handle::flag::unlink_on_close).value(); - afio::directory_handle h2 = afio::directory_handle::directory({}, "tempdir", afio::file_handle::mode::write, afio::file_handle::creation::if_needed, afio::file_handle::caching::temporary, afio::file_handle::flag::unlink_on_close).value(); - - { - auto h1path=h1.current_path(); - BOOST_CHECK(h1path); - if(!h1path) - { - std::cerr << "Getting the current path of a file FAILED due to " << h1path.error().message() << std::endl; - } - else if(h1path.value().empty()) - { - BOOST_CHECK(!h1path.value().empty()); - std::cerr << "Getting the current path of a file FAILED due to the returned path being empty" << std::endl; - } - else - { - std::cout << "The path of the file is " << h1path.value() << std::endl; - } - - auto h2path=h2.current_path(); - BOOST_CHECK(h2path); - if(!h2path) { - std::cerr << "Getting the current path of a directory FAILED due to " << h2path.error().message() << std::endl; + std::error_code ec; + afio::filesystem::remove_all("tempfile", ec); + afio::filesystem::remove_all("tempfile2", ec); + afio::filesystem::remove_all("tempdir", ec); + afio::filesystem::remove_all("tempdir2", ec); } - else if(h2path.value().empty()) - { - BOOST_CHECK(!h2path.value().empty()); - std::cerr << "Getting the current path of a directory FAILED due to the returned path being empty" << std::endl; - } - else +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmissing-braces" +#endif + FileHandleType h1 = afio::construct<FileHandleType>{afio::path_handle(), "tempfile", afio::file_handle::mode::write, afio::file_handle::creation::if_needed, afio::file_handle::caching::temporary, afio::file_handle::flag::unlink_on_close}().value(); // NOLINT + DirectoryHandleType h2 = afio::construct<DirectoryHandleType>{afio::path_handle(), "tempdir", afio::file_handle::mode::write, afio::file_handle::creation::if_needed, afio::file_handle::caching::all, afio::file_handle::flag::unlink_on_close}().value(); // NOLINT +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + { - std::cout << "The path of the directory is " << h2path.value() << std::endl; - } + auto h1path = h1.current_path(); + BOOST_CHECK(h1path); + if(!h1path) + { + std::cerr << "Getting the current path of a file FAILED due to " << h1path.error().message() << std::endl; + } + else if(h1path.value().empty()) + { + BOOST_CHECK(!h1path.value().empty()); + std::cerr << "Getting the current path of a file FAILED due to the returned path being empty" << std::endl; + } + else + { + std::cout << "The path of the file is " << h1path.value() << std::endl; + } + + auto h2path = h2.current_path(); + BOOST_CHECK(h2path); + if(!h2path) + { + std::cerr << "Getting the current path of a directory FAILED due to " << h2path.error().message() << std::endl; + } + else if(h2path.value().empty()) + { + BOOST_CHECK(!h2path.value().empty()); + std::cerr << "Getting the current path of a directory FAILED due to the returned path being empty" << std::endl; + } + else + { + std::cout << "The path of the directory is " << h2path.value() << std::endl; + } } - + h1.relink({}, "tempfile2").value(); h2.relink({}, "tempdir2").value(); - { - auto h1path=h1.current_path(); - BOOST_CHECK(h1path); - if(!h1path) { - std::cerr << "Getting the current path of a file FAILED due to " << h1path.error().message() << std::endl; - } - else if(h1path.value().empty()) - { - BOOST_CHECK(!h1path.value().empty()); - std::cerr << "Getting the current path of a file FAILED due to the returned path being empty" << std::endl; - } - else - { - std::cout << "The path of the file is " << h1path.value() << std::endl; - } - - auto h2path=h2.current_path(); - BOOST_CHECK(h2path); - if(!h2path) - { - std::cerr << "Getting the current path of a directory FAILED due to " << h2path.error().message() << std::endl; - } - else if(h2path.value().empty()) - { - BOOST_CHECK(!h2path.value().empty()); - std::cerr << "Getting the current path of a directory FAILED due to the returned path being empty" << std::endl; - } - else - { - std::cout << "The path of the directory is " << h2path.value() << std::endl; - } + auto h1path = h1.current_path(); + BOOST_CHECK(h1path); + if(!h1path) + { + std::cerr << "Getting the current path of a file FAILED due to " << h1path.error().message() << std::endl; + } + else if(h1path.value().empty()) + { + BOOST_CHECK(!h1path.value().empty()); + std::cerr << "Getting the current path of a file FAILED due to the returned path being empty" << std::endl; + } + else + { + std::cout << "The path of the file is " << h1path.value() << std::endl; + } + + auto h2path = h2.current_path(); + BOOST_CHECK(h2path); + if(!h2path) + { + std::cerr << "Getting the current path of a directory FAILED due to " << h2path.error().message() << std::endl; + } + else if(h2path.value().empty()) + { + BOOST_CHECK(!h2path.value().empty()); + std::cerr << "Getting the current path of a directory FAILED due to the returned path being empty" << std::endl; + } + else + { + std::cout << "The path of the directory is " << h2path.value() << std::endl; + } } - } -KERNELTEST_TEST_KERNEL(integration, afio, current_path, handle, "Tests that afio::handle::current_path() works as expected", TestHandleCurrentPath()) +KERNELTEST_TEST_KERNEL(integration, afio, current_path, handle, "Tests that afio::handle::current_path() works as expected", TestHandleCurrentPath<AFIO_V2_NAMESPACE::file_handle, AFIO_V2_NAMESPACE::directory_handle>()) +KERNELTEST_TEST_KERNEL(integration, afio, current_path, cached_parent_handle_adapter, "Tests that afio::cached_parent_handle_adapter::current_path() works as expected", + TestHandleCurrentPath<AFIO_V2_NAMESPACE::algorithm::cached_parent_handle_adapter<AFIO_V2_NAMESPACE::file_handle>, AFIO_V2_NAMESPACE::algorithm::cached_parent_handle_adapter<AFIO_V2_NAMESPACE::directory_handle>>()) |