From 5ac44ecb0053dd899418ecb172c896e7064b4d49 Mon Sep 17 00:00:00 2001 From: mandreyel Date: Mon, 22 Oct 2018 13:24:32 +0200 Subject: [PATCH 01/14] Create invalid_handle value and update unmapped shared_mmap handle methods to return it --- include/mio/detail/basic_mmap.hpp | 6 ++++-- include/mio/mmap.hpp | 4 ++++ include/mio/shared_mmap.hpp | 15 ++++++++++++--- test/test.cpp | 6 ++++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/mio/detail/basic_mmap.hpp b/include/mio/detail/basic_mmap.hpp index 4582626..50a12ad 100644 --- a/include/mio/detail/basic_mmap.hpp +++ b/include/mio/detail/basic_mmap.hpp @@ -42,11 +42,13 @@ namespace detail { enum { map_entire_file = 0 }; #ifdef _WIN32 - using file_handle_type = HANDLE; +using file_handle_type = HANDLE; #else - using file_handle_type = int; +using file_handle_type = int; #endif +constexpr static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; + template struct basic_mmap { using value_type = ByteT; diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 419509e..46c95d3 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -32,6 +32,10 @@ namespace mio { // `map`, in which case a memory mapping of the entire file is created. using detail::map_entire_file; +// This value represents an invalid file handle type. This can be used to +// determine whether `basic_mmap::file_handle` is valid, for example. +using detail::invalid_handle; + template< access_mode AccessMode, typename ByteT diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index 64f6a1b..7dcf74b 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -120,8 +120,15 @@ public: * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ - handle_type file_handle() const noexcept { return pimpl_->file_handle(); } - handle_type mapping_handle() const noexcept { return pimpl_->mapping_handle(); } + handle_type file_handle() const noexcept + { + return pimpl_ ? pimpl_->file_handle() : invalid_handle; + } + + handle_type mapping_handle() const noexcept + { + return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; + } /** Returns whether a valid memory mapping has been created. */ bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } @@ -142,7 +149,9 @@ public: size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } size_type mapped_length() const noexcept - { return pimpl_ ? pimpl_->mapped_length() : 0; } + { + return pimpl_ ? pimpl_->mapped_length() : 0; + } /** * Returns the offset, relative to the file's start, at which the mapping was diff --git a/test/test.cpp b/test/test.cpp index aacae81..c9ae00b 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -89,8 +89,7 @@ int main() CHECK_INVALID_MMAP(m); // Invalid handle? - m = mio::make_mmap_source( - INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error); + m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error); CHECK_INVALID_MMAP(m); // Invalid offset? @@ -102,6 +101,9 @@ int main() // Make sure custom types compile. mio::ummap_source _1; mio::shared_ummap_source _2; + // Make sure shared_mmap mapping compiles as all testing was done on + // normal mmaps. + mio::shared_mmap_source _3(path, 0, mio::map_entire_file); } std::printf("all tests passed!\n"); From 98d0d875b574e1e49f510d762155a74c3881e421 Mon Sep 17 00:00:00 2001 From: mandreyel Date: Mon, 22 Oct 2018 14:43:36 +0200 Subject: [PATCH 02/14] Refactor code to remove basic_mmap impl indirection --- include/mio/detail/CMakeLists.txt | 3 +- include/mio/detail/basic_mmap.hpp | 166 ------------ .../mio/detail/{basic_mmap.ipp => mmap.ipp} | 223 ++++++++-------- include/mio/mmap.hpp | 244 +++++++++++------- 4 files changed, 270 insertions(+), 366 deletions(-) delete mode 100644 include/mio/detail/basic_mmap.hpp rename include/mio/detail/{basic_mmap.ipp => mmap.ipp} (62%) diff --git a/include/mio/detail/CMakeLists.txt b/include/mio/detail/CMakeLists.txt index 5711e21..fde72b3 100644 --- a/include/mio/detail/CMakeLists.txt +++ b/include/mio/detail/CMakeLists.txt @@ -5,7 +5,6 @@ if(NOT subproject) target_sources(mio INTERFACE $) endif() diff --git a/include/mio/detail/basic_mmap.hpp b/include/mio/detail/basic_mmap.hpp deleted file mode 100644 index 50a12ad..0000000 --- a/include/mio/detail/basic_mmap.hpp +++ /dev/null @@ -1,166 +0,0 @@ -/* Copyright 2017 https://github.com/mandreyel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included in all copies - * or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef MIO_BASIC_MMAP_HEADER -#define MIO_BASIC_MMAP_HEADER - -#include "mio/page.hpp" - -#include -#include -#include - -#ifdef _WIN32 -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif // WIN32_LEAN_AND_MEAN -# include -#else // ifdef _WIN32 -# define INVALID_HANDLE_VALUE -1 -#endif // ifdef _WIN32 - -namespace mio { -namespace detail { - -enum { map_entire_file = 0 }; - -#ifdef _WIN32 -using file_handle_type = HANDLE; -#else -using file_handle_type = int; -#endif - -constexpr static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; - -template struct basic_mmap -{ - using value_type = ByteT; - using size_type = int64_t; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - using difference_type = std::ptrdiff_t; - using iterator = pointer; - using const_iterator = const_pointer; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - using iterator_category = std::random_access_iterator_tag; - using handle_type = file_handle_type; - - static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); - -private: - // Points to the first requested byte, and not to the actual start of the mapping. - pointer data_ = nullptr; - - // Length, in bytes, requested by user, which may not be the length of the full - // mapping, and the entire length of the full mapping. - size_type length_ = 0; - size_type mapped_length_ = 0; - - // Letting user map a file using both an existing file handle and a path introcudes - // On POSIX, we only need a file handle to create a mapping, while on Windows - // systems the file handle is necessary to retrieve a file mapping handle, but any - // subsequent operations on the mapped region must be done through the latter. - handle_type file_handle_ = INVALID_HANDLE_VALUE; -#ifdef _WIN32 - handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; -#endif - - // some complexity in that we must not close the file handle if user provided it, - // but we must close it if we obtained it using the provided path. For this reason, - // this flag is used to determine when to close file_handle_. - bool is_handle_internal_; - -public: - basic_mmap() = default; - basic_mmap(const basic_mmap&) = delete; - basic_mmap& operator=(const basic_mmap&) = delete; - basic_mmap(basic_mmap&&); - basic_mmap& operator=(basic_mmap&&); - ~basic_mmap(); - - handle_type file_handle() const noexcept { return file_handle_; } - handle_type mapping_handle() const noexcept; - - bool is_open() const noexcept { return file_handle_ != INVALID_HANDLE_VALUE; } - bool is_mapped() const noexcept; - bool empty() const noexcept { return length() == 0; } - - size_type offset() const noexcept { return mapped_length_ - length_; } - size_type length() const noexcept { return length_; } - size_type mapped_length() const noexcept { return mapped_length_; } - - pointer data() noexcept { return data_; } - const_pointer data() const noexcept { return data_; } - - iterator begin() noexcept { return data(); } - const_iterator begin() const noexcept { return data(); } - const_iterator cbegin() const noexcept { return data(); } - - iterator end() noexcept { return begin() + length(); } - const_iterator end() const noexcept { return begin() + length(); } - const_iterator cend() const noexcept { return begin() + length(); } - - reverse_iterator rbegin() { return reverse_iterator(end()); } - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } - - reverse_iterator rend() { return reverse_iterator(begin()); } - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } - - reference operator[](const size_type i) noexcept { return data_[i]; } - const_reference operator[](const size_type i) const noexcept { return data_[i]; } - - template - void map(String& path, size_type offset, size_type length, - access_mode mode, std::error_code& error); - void map(handle_type handle, size_type offset, size_type length, - access_mode mode, std::error_code& error); - void unmap(); - void sync(std::error_code& error); - - void swap(basic_mmap& other); - -private: - pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - offset(); } -}; - -template -bool operator==(const basic_mmap& a, const basic_mmap& b); -template -bool operator!=(const basic_mmap& a, const basic_mmap& b); -template -bool operator<(const basic_mmap& a, const basic_mmap& b); -template -bool operator<=(const basic_mmap& a, const basic_mmap& b); -template -bool operator>(const basic_mmap& a, const basic_mmap& b); -template -bool operator>=(const basic_mmap& a, const basic_mmap& b); - -} // namespace detail -} // namespace mio - -#include "mio/detail/basic_mmap.ipp" - -#endif // MIO_BASIC_MMAP_HEADER diff --git a/include/mio/detail/basic_mmap.ipp b/include/mio/detail/mmap.ipp similarity index 62% rename from include/mio/detail/basic_mmap.ipp rename to include/mio/detail/mmap.ipp index 08e4bd4..bd3cb74 100644 --- a/include/mio/detail/basic_mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -21,12 +21,11 @@ #ifndef MIO_BASIC_MMAP_IMPL #define MIO_BASIC_MMAP_IMPL -#include "mio/detail/basic_mmap.hpp" -#include "mio/detail/string_util.hpp" +#include "mio/mmap.hpp" #include "mio/page.hpp" +#include "mio/detail/string_util.hpp" #include -#include #ifndef _WIN32 # include @@ -39,17 +38,23 @@ namespace mio { namespace detail { #ifdef _WIN32 +/** Returns the 4 upper bytes of a 8-byte integer. */ inline DWORD int64_high(int64_t n) noexcept { return n >> 32; } +/** Returns the 4 lower bytes of a 8-byte integer. */ inline DWORD int64_low(int64_t n) noexcept { return n & 0xffffffff; } #endif +/** + * Returns the last platform specific system error (errno on POSIX and + * GetLastError on Win) as a `std::error_code`. + */ inline std::error_code last_error() noexcept { std::error_code error; @@ -63,29 +68,29 @@ inline std::error_code last_error() noexcept template file_handle_type open_file(const String& path, - const access_mode mode, std::error_code& error) + const access_mode mode, std::error_code& error) { error.clear(); if(detail::empty(path)) { error = std::make_error_code(std::errc::invalid_argument); - return INVALID_HANDLE_VALUE; + return invalid_handle; } #ifdef _WIN32 const auto handle = ::CreateFileA(c_str(path), - mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - 0); -#else + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +#else // POSIX const auto handle = ::open(c_str(path), - mode == access_mode::read ? O_RDONLY : O_RDWR); + mode == access_mode::read ? O_RDONLY : O_RDWR); #endif - if(handle == INVALID_HANDLE_VALUE) + if(handle == invalid_handle) { - error = last_error(); + error = detail::last_error(); } return handle; } @@ -97,15 +102,15 @@ inline int64_t query_file_size(file_handle_type handle, std::error_code& error) LARGE_INTEGER file_size; if(::GetFileSizeEx(handle, &file_size) == 0) { - error = last_error(); + error = detail::last_error(); return 0; } return static_cast(file_size.QuadPart); -#else +#else // POSIX struct stat sbuf; if(::fstat(handle, &sbuf) == -1) { - error = last_error(); + error = detail::last_error(); return 0; } return sbuf.st_size; @@ -130,39 +135,39 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t #ifdef _WIN32 const int64_t max_file_size = offset + length; const auto file_mapping_handle = ::CreateFileMapping( - file_handle, - 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, - int64_high(max_file_size), - int64_low(max_file_size), - 0); - if(file_mapping_handle == INVALID_HANDLE_VALUE) + file_handle, + 0, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + int64_high(max_file_size), + int64_low(max_file_size), + 0); + if(file_mapping_handle == invalid_handle) { - error = last_error(); + error = detail::last_error(); return {}; } char* mapping_start = static_cast(::MapViewOfFile( - file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, - int64_high(aligned_offset), - int64_low(aligned_offset), - length_to_map)); + file_mapping_handle, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + int64_high(aligned_offset), + int64_low(aligned_offset), + length_to_map)); if(mapping_start == nullptr) { - error = last_error(); + error = detail::last_error(); return {}; } -#else +#else // POSIX char* mapping_start = static_cast(::mmap( - 0, // Don't give hint as to where to map. - length_to_map, - mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, - file_handle, - aligned_offset)); + 0, // Don't give hint as to where to map. + length_to_map, + mode == access_mode::read ? PROT_READ : PROT_WRITE, + MAP_SHARED, + file_handle, + aligned_offset)); if(mapping_start == MAP_FAILED) { - error = last_error(); + error = detail::last_error(); return {}; } #endif @@ -176,16 +181,18 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t return ctx; } +} // namespace detail + // -- basic_mmap -- -template -basic_mmap::~basic_mmap() +template +basic_mmap::~basic_mmap() { unmap(); } -template -basic_mmap::basic_mmap(basic_mmap&& other) +template +basic_mmap::basic_mmap(basic_mmap&& other) : data_(std::move(other.data_)) , length_(std::move(other.length_)) , mapped_length_(std::move(other.mapped_length_)) @@ -197,14 +204,15 @@ basic_mmap::basic_mmap(basic_mmap&& other) { other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; - other.file_handle_ = INVALID_HANDLE_VALUE; + other.file_handle_ = invalid_handle; #ifdef _WIN32 - other.file_mapping_handle_ = INVALID_HANDLE_VALUE; + other.file_mapping_handle_ = invalid_handle; #endif } -template -basic_mmap& basic_mmap::operator=(basic_mmap&& other) +template +basic_mmap& +basic_mmap::operator=(basic_mmap&& other) { if(this != &other) { @@ -219,21 +227,23 @@ basic_mmap& basic_mmap::operator=(basic_mmap&& other) #endif is_handle_internal_ = std::move(other.is_handle_internal_); - // The moved from basic_mmap's fields need to be reset, because otherwise other's - // destructor will unmap the same mapping that was just moved into this. + // The moved from basic_mmap's fields need to be reset, because + // otherwise other's destructor will unmap the same mapping that was + // just moved into this. other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; - other.file_handle_ = INVALID_HANDLE_VALUE; + other.file_handle_ = invalid_handle; #ifdef _WIN32 - other.file_mapping_handle_ = INVALID_HANDLE_VALUE; + other.file_mapping_handle_ = invalid_handle; #endif other.is_handle_internal_ = false; } return *this; } -template -typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept +template +typename basic_mmap::handle_type +basic_mmap::mapping_handle() const noexcept { #ifdef _WIN32 return file_mapping_handle_; @@ -242,10 +252,10 @@ typename basic_mmap::handle_type basic_mmap::mapping_handle() cons #endif } -template +template template -void basic_mmap::map(String& path, size_type offset, - size_type length, access_mode mode, std::error_code& error) +void basic_mmap::map(const String& path, const size_type offset, + const size_type length, std::error_code& error) { error.clear(); if(detail::empty(path)) @@ -253,9 +263,13 @@ void basic_mmap::map(String& path, size_type offset, error = std::make_error_code(std::errc::invalid_argument); return; } - const auto handle = open_file(path, mode, error); - if(error) { return; } - map(handle, offset, length, mode, error); + const auto handle = detail::open_file(path, AccessMode, error); + if(error) + { + return; + } + + map(handle, offset, length, error); // This MUST be after the call to map, as that sets this to true. if(!error) { @@ -263,31 +277,32 @@ void basic_mmap::map(String& path, size_type offset, } } -template -void basic_mmap::map(handle_type handle, size_type offset, - size_type length, access_mode mode, std::error_code& error) +template +void basic_mmap::map(const handle_type handle, const size_type offset, + const size_type length, std::error_code& error) { error.clear(); - if(handle == INVALID_HANDLE_VALUE) + if(handle == invalid_handle) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } - const auto file_size = query_file_size(handle, error); - if(error) { return; } - - if(length <= map_entire_file) + const auto file_size = detail::query_file_size(handle, error); + if(error) { - length = file_size; + return; } - else if(offset + length > file_size) + + if(offset + length > file_size) { error = std::make_error_code(std::errc::invalid_argument); return; } - const mmap_context ctx = memory_map(handle, offset, length, mode, error); + const auto ctx = detail::memory_map(handle, offset, + length == map_entire_file ? file_size : length, + AccessMode, error); if(!error) { // We must unmap the previous mapping that may have existed prior to this call. @@ -307,8 +322,9 @@ void basic_mmap::map(handle_type handle, size_type offset, } } -template -void basic_mmap::sync(std::error_code& error) +template +template +void basic_mmap::sync(std::error_code& error) { error.clear(); if(!is_open()) @@ -322,24 +338,24 @@ void basic_mmap::sync(std::error_code& error) #ifdef _WIN32 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0) -#else +#else // POSIX if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) #endif { - error = last_error(); + error = detail::last_error(); return; } } #ifdef _WIN32 if(::FlushFileBuffers(file_handle_) == 0) { - error = last_error(); + error = detail::last_error(); } #endif } -template -void basic_mmap::unmap() +template +void basic_mmap::unmap() { if(!is_open()) { return; } // TODO do we care about errors here? @@ -349,7 +365,7 @@ void basic_mmap::unmap() ::UnmapViewOfFile(get_mapping_start()); ::CloseHandle(file_mapping_handle_); } -#else +#else // POSIX if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } #endif @@ -360,7 +376,7 @@ void basic_mmap::unmap() { #ifdef _WIN32 ::CloseHandle(file_handle_); -#else +#else // POSIX ::close(file_handle_); #endif } @@ -368,24 +384,24 @@ void basic_mmap::unmap() // Reset fields to their default values. data_ = nullptr; length_ = mapped_length_ = 0; - file_handle_ = INVALID_HANDLE_VALUE; + file_handle_ = invalid_handle; #ifdef _WIN32 - file_mapping_handle_ = INVALID_HANDLE_VALUE; + file_mapping_handle_ = invalid_handle; #endif } -template -bool basic_mmap::is_mapped() const noexcept +template +bool basic_mmap::is_mapped() const noexcept { #ifdef _WIN32 - return file_mapping_handle_ != INVALID_HANDLE_VALUE; -#else + return file_mapping_handle_ != invalid_handle; +#else // POSIX return is_open(); #endif } -template -void basic_mmap::swap(basic_mmap& other) +template +void basic_mmap::swap(basic_mmap& other) { if(this != &other) { @@ -401,46 +417,51 @@ void basic_mmap::swap(basic_mmap& other) } } -template -bool operator==(const basic_mmap& a, const basic_mmap& b) +template +bool operator==(const basic_mmap& a, + const basic_mmap& b) { return a.data() == b.data() && a.size() == b.size(); } -template -bool operator!=(const basic_mmap& a, const basic_mmap& b) +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b) { return !(a == b); } -template -bool operator<(const basic_mmap& a, const basic_mmap& b) +template +bool operator<(const basic_mmap& a, + const basic_mmap& b) { if(a.data() == b.data()) { return a.size() < b.size(); } return a.data() < b.data(); } -template -bool operator<=(const basic_mmap& a, const basic_mmap& b) +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b) { return !(a > b); } -template -bool operator>(const basic_mmap& a, const basic_mmap& b) +template +bool operator>(const basic_mmap& a, + const basic_mmap& b) { if(a.data() == b.data()) { return a.size() > b.size(); } return a.data() > b.data(); } -template -bool operator>=(const basic_mmap& a, const basic_mmap& b) +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b) { return !(a < b); } -} // namespace detail } // namespace mio #endif // MIO_BASIC_MMAP_IMPL diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 46c95d3..158dcb4 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -21,48 +21,85 @@ #ifndef MIO_MMAP_HEADER #define MIO_MMAP_HEADER -#include "detail/basic_mmap.hpp" -#include "page.hpp" +#include "mio/page.hpp" +#include +#include #include +#include + +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif // WIN32_LEAN_AND_MEAN +# include +#else // ifdef _WIN32 +# define INVALID_HANDLE_VALUE -1 +#endif // ifdef _WIN32 namespace mio { // This value may be provided as the `length` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. -using detail::map_entire_file; +enum { map_entire_file = 0 }; + +#ifdef _WIN32 +using file_handle_type = HANDLE; +#else +using file_handle_type = int; +#endif // This value represents an invalid file handle type. This can be used to // determine whether `basic_mmap::file_handle` is valid, for example. -using detail::invalid_handle; +constexpr static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; -template< - access_mode AccessMode, - typename ByteT -> class basic_mmap +template +struct basic_mmap { - using impl_type = detail::basic_mmap; - impl_type impl_; + using value_type = ByteT; + using size_type = int64_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; + + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); + +private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; + + // Length, in bytes, requested by user, which may not be the length of the full + // mapping, and the entire length of the full mapping. + size_type length_ = 0; + size_type mapped_length_ = 0; + + // Letting user map a file using both an existing file handle and a path introcudes + // On POSIX, we only need a file handle to create a mapping, while on Windows + // systems the file handle is necessary to retrieve a file mapping handle, but any + // subsequent operations on the mapped region must be done through the latter. + handle_type file_handle_ = INVALID_HANDLE_VALUE; +#ifdef _WIN32 + handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; +#endif + + // some complexity in that we must not close the file handle if user provided it, + // but we must close it if we obtained it using the provided path. For this reason, + // this flag is used to determine when to close file_handle_. + bool is_handle_internal_; public: - using value_type = typename impl_type::value_type; - using size_type = typename impl_type::size_type; - using reference = typename impl_type::reference; - using const_reference = typename impl_type::const_reference; - using pointer = typename impl_type::pointer; - using const_pointer = typename impl_type::const_pointer; - using difference_type = typename impl_type::difference_type; - using iterator = typename impl_type::iterator; - using const_iterator = typename impl_type::const_iterator; - using reverse_iterator = typename impl_type::reverse_iterator; - using const_reverse_iterator = typename impl_type::const_reverse_iterator; - using iterator_category = typename impl_type::iterator_category; - using handle_type = typename impl_type::handle_type; - /** - * The default constructed mmap object is in a non-mapped state, that is, any - * operation that attempts to access nonexistent underlying data will result in - * undefined behaviour/segmentation faults. + * The default constructed mmap object is in a non-mapped state, that is, + * any operation that attempts to access nonexistent underlying data will + * result in undefined behaviour/segmentation faults. */ basic_mmap() = default; @@ -90,32 +127,37 @@ public: } /** - * This class has single-ownership semantics, so transferring ownership may only be - * accomplished by moving the object. + * `basic_mmap` has single-ownership semantics, so transferring ownership + * may only be accomplished by moving the object. */ - basic_mmap(basic_mmap&&) = default; - basic_mmap& operator=(basic_mmap&&) = default; + basic_mmap(const basic_mmap&) = delete; + basic_mmap(basic_mmap&&); + basic_mmap& operator=(const basic_mmap&) = delete; + basic_mmap& operator=(basic_mmap&&); /** The destructor invokes unmap. */ - ~basic_mmap() = default; + ~basic_mmap(); /** * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ - handle_type file_handle() const noexcept { return impl_.file_handle(); } - handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); } + handle_type file_handle() const noexcept { return file_handle_; } + handle_type mapping_handle() const noexcept; /** Returns whether a valid memory mapping has been created. */ - bool is_open() const noexcept { return impl_.is_open(); } + bool is_open() const noexcept { return file_handle_ != invalid_handle; } /** * Returns true if no mapping was established, that is, conceptually the * same as though the length that was mapped was 0. This function is * provided so that this class has Container semantics. */ - bool empty() const noexcept { return impl_.empty(); } + bool empty() const noexcept { return length() == 0; } + + /** Returns true if a mapping was established. */ + bool is_mapped() const noexcept; /** * `size` and `length` both return the logical length, i.e. the number of bytes @@ -123,15 +165,15 @@ public: * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ - size_type size() const noexcept { return impl_.length(); } - size_type length() const noexcept { return impl_.length(); } - size_type mapped_length() const noexcept { return impl_.mapped_length(); } + size_type size() const noexcept { return length(); } + size_type length() const noexcept { return length_; } + size_type mapped_length() const noexcept { return mapped_length_; } /** * Returns the offset, relative to the file's start, at which the mapping was * requested to be created. */ - size_type offset() const noexcept { return impl_.offset(); } + size_type offset() const noexcept { return mapped_length_ - length_; } /** * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping @@ -140,8 +182,8 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > pointer data() noexcept { return impl_.data(); } - const_pointer data() const noexcept { return impl_.data(); } + > pointer data() noexcept { return data_; } + const_pointer data() const noexcept { return data_; } /** * Returns an iterator to the first requested byte, if a valid memory mapping @@ -150,9 +192,9 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > iterator begin() noexcept { return impl_.begin(); } - const_iterator begin() const noexcept { return impl_.begin(); } - const_iterator cbegin() const noexcept { return impl_.cbegin(); } + > iterator begin() noexcept { return data(); } + const_iterator begin() const noexcept { return data(); } + const_iterator cbegin() const noexcept { return data(); } /** * Returns an iterator one past the last requested byte, if a valid memory mapping @@ -161,9 +203,9 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > iterator end() noexcept { return impl_.end(); } - const_iterator end() const noexcept { return impl_.end(); } - const_iterator cend() const noexcept { return impl_.cend(); } + > iterator end() noexcept { return data() + length(); } + const_iterator end() const noexcept { return data() + length(); } + const_iterator cend() const noexcept { return data() + length(); } /** * Returns a reverse iterator to the last memory mapped byte, if a valid @@ -173,9 +215,11 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > reverse_iterator rbegin() noexcept { return impl_.rbegin(); } - const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); } - const_reverse_iterator crbegin() const noexcept { return impl_.crbegin(); } + > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const noexcept + { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const noexcept + { return const_reverse_iterator(end()); } /** * Returns a reverse iterator past the first mapped byte, if a valid memory @@ -184,17 +228,19 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > reverse_iterator rend() noexcept { return impl_.rend(); } - const_reverse_iterator rend() const noexcept { return impl_.rend(); } - const_reverse_iterator crend() const noexcept { return impl_.crend(); } + > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + const_reverse_iterator rend() const noexcept + { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const noexcept + { return const_reverse_iterator(begin()); } /** * Returns a reference to the `i`th byte from the first requested byte (as returned * by `data`). If this is invoked when no valid memory mapping has been created * prior to this call, undefined behaviour ensues. */ - reference operator[](const size_type i) noexcept { return impl_[i]; } - const_reference operator[](const size_type i) const noexcept { return impl_[i]; } + reference operator[](const size_type i) noexcept { return data_[i]; } + const_reference operator[](const size_type i) const noexcept { return data_[i]; } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the @@ -218,15 +264,12 @@ public: */ template void map(const String& path, const size_type offset, - const size_type length, std::error_code& error) - { - impl_.map(path, offset, length, AccessMode, error); - } + const size_type length, std::error_code& error); /** - * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the - * reason is reported via `error` and the object remains in a state as if this - * function hadn't been called. + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the @@ -243,10 +286,7 @@ public: * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error) - { - impl_.map(handle, offset, length, AccessMode, error); - } + const size_type length, std::error_code& error); /** * If a valid memory mapping has been created prior to this call, this call @@ -257,52 +297,60 @@ public: * mapping was created using a file path. If, on the other hand, an existing * file handle was used to create the mapping, the file handle is not closed. */ - void unmap() { impl_.unmap(); } + void unmap(); - void swap(basic_mmap& other) { impl_.swap(other.impl_); } + void swap(basic_mmap& other); /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ template< access_mode A = AccessMode, typename = typename std::enable_if::type - > void sync(std::error_code& error) { impl_.sync(error); } + > void sync(std::error_code& error); /** * All operators compare the address of the first byte and size of the two mapped * regions. */ - friend bool operator==(const basic_mmap& a, const basic_mmap& b) +private: + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer get_mapping_start() noexcept { - return a.impl_ == b.impl_; + return !data() ? nullptr : data() - offset(); } - friend bool operator!=(const basic_mmap& a, const basic_mmap& b) + const_pointer get_mapping_start() const noexcept { - return !(a == b); - } - - friend bool operator<(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ < b.impl_; - } - - friend bool operator<=(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ <= b.impl_; - } - - friend bool operator>(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ > b.impl_; - } - - friend bool operator>=(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ >= b.impl_; + return !data() ? nullptr : data() - offset(); } }; +template +bool operator==(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b); + /** * This is the basis for all read-only mmap objects and should be preferred over * directly using `basic_mmap`. @@ -332,7 +380,7 @@ template< typename MMap, typename MappingToken > MMap make_mmap(const MappingToken& token, - int64_t offset, int64_t length, std::error_code& error) + int64_t offset, int64_t length, std::error_code& error) { MMap mmap; mmap.map(token, offset, length, error); @@ -348,7 +396,7 @@ template< */ template mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, - mmap_source::size_type length, std::error_code& error) + mmap_source::size_type length, std::error_code& error) { return make_mmap(token, offset, length, error); } @@ -362,11 +410,13 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o */ template mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, - mmap_sink::size_type length, std::error_code& error) + mmap_sink::size_type length, std::error_code& error) { return make_mmap(token, offset, length, error); } } // namespace mio +#include "detail/mmap.ipp" + #endif // MIO_MMAP_HEADER From 865d7f35cedf4541001b124a74740e2c0041b6fd Mon Sep 17 00:00:00 2001 From: mandreyel Date: Mon, 22 Oct 2018 14:57:47 +0200 Subject: [PATCH 03/14] Update basic_mmap destructor sync behavior --- include/mio/detail/mmap.ipp | 5 +++-- include/mio/mmap.hpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index bd3cb74..1618ef1 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -188,6 +188,7 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t template basic_mmap::~basic_mmap() { + conditional_sync(); unmap(); } @@ -278,8 +279,8 @@ void basic_mmap::map(const String& path, const size_type offs } template -void basic_mmap::map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error) +void basic_mmap::map(const handle_type handle, + const size_type offset, const size_type length, std::error_code& error) { error.clear(); if(handle == invalid_handle) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 158dcb4..855fefa 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -325,6 +325,17 @@ private: { return !data() ? nullptr : data() - offset(); } + + /** + * The destructor syncs changes to disk if `AccessMode` is `write`, but not + * if it's `read`, but since the destructor cannot be templated, we need to + * do SFINAE in a dedicated function, where one syncs and the other is a noop. + */ + template::type> + void conditional_sync() { sync(); } + template + typename std::enable_if::type conditional_sync() {} }; template From 1157537c98ec75967acb77ac1db93150ee1a1d5a Mon Sep 17 00:00:00 2001 From: mandreyel Date: Mon, 22 Oct 2018 18:42:56 +0200 Subject: [PATCH 04/14] Update docs, example.cpp and README.md --- README.md | 9 +++++---- include/mio/mmap.hpp | 5 ++++- include/mio/shared_mmap.hpp | 5 +++++ test/example.cpp | 9 +++++---- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c3287a0..217ab7f 100644 --- a/README.md +++ b/README.md @@ -113,14 +113,15 @@ int main() const int answer_index = rw_mmap.size() / 2; rw_mmap[answer_index] = 42; - // Don't forget to flush changes to disk, which is NOT done by the destructor for - // more explicit control of this potentially expensive operation. + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. rw_mmap.sync(error); if (error) { return handle_error(error); } // We can then remove the mapping, after which rw_mmap will be in a default - // constructed state, i.e. this has the same effect as if the destructor had been - // invoked. + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. rw_mmap.unmap(); // Now create the same mapping, but in read-only mode. diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 855fefa..91b2f63 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -135,7 +135,10 @@ public: basic_mmap& operator=(const basic_mmap&) = delete; basic_mmap& operator=(basic_mmap&&); - /** The destructor invokes unmap. */ + /** + * If this is a read-write mapping, the destructor invokes sync. Regardless + * of the access mode, unmap is invoked as a final step. + */ ~basic_mmap(); /** diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index 7dcf74b..ba7e1e0 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -110,6 +110,11 @@ public: if(error) { throw error; } } + /** + * If this is a read-write mapping and the last reference to the mapping, + * the destructor invokes sync. Regardless of the access mode, unmap is + * invoked as a final step. + */ ~basic_shared_mmap() = default; /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ diff --git a/test/example.cpp b/test/example.cpp index efeb67a..2f08b1b 100644 --- a/test/example.cpp +++ b/test/example.cpp @@ -49,14 +49,15 @@ int main() const int answer_index = rw_mmap.size() / 2; rw_mmap[answer_index] = 42; - // Don't forget to flush changes to disk, which is NOT done by the destructor for - // more explicit control of this potentially expensive operation. + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. rw_mmap.sync(error); if (error) { return handle_error(error); } // We can then remove the mapping, after which rw_mmap will be in a default - // constructed state, i.e. this has the same effect as if the destructor had been - // invoked. + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. rw_mmap.unmap(); // Now create the same mapping, but in read-only mode. From 99447c57cf002512a6a918634c2e7ccdefd4e86a Mon Sep 17 00:00:00 2001 From: James Darpinian Date: Wed, 24 Oct 2018 03:02:02 -0700 Subject: [PATCH 05/14] MSVC can't handle constexpr INVALID_HANDLE_VALUE Causes error C2131: expression did not evaluate to a constant note: failure was caused by unevaluable pointer value --- include/mio/mmap.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 91b2f63..cfd15de 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -51,7 +51,7 @@ using file_handle_type = int; // This value represents an invalid file handle type. This can be used to // determine whether `basic_mmap::file_handle` is valid, for example. -constexpr static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; +const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; template struct basic_mmap From 0b3261071ef7f5dc1bfedbd52142883654a61e8b Mon Sep 17 00:00:00 2001 From: James Darpinian Date: Wed, 24 Oct 2018 03:09:54 -0700 Subject: [PATCH 06/14] Map entire file by default. --- include/mio/mmap.hpp | 38 +++++++++++++++++++++++++++++++++++-- include/mio/shared_mmap.hpp | 38 +++++++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index cfd15de..286e117 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -108,7 +108,7 @@ public: * establishing the mapping is thrown. */ template - basic_mmap(const String& path, const size_type offset, const size_type length) + basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); @@ -119,7 +119,7 @@ public: * The same as invoking the `map` function, except any error that may occur while * establishing the mapping is thrown. */ - basic_mmap(const handle_type handle, const size_type offset, const size_type length) + basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); @@ -269,6 +269,24 @@ public: void map(const String& path, const size_type offset, const size_type length, std::error_code& error); + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map(path, 0, map_entire_file, error); + } + /** * Establishes a memory mapping with AccessMode. If the mapping is * unsuccesful, the reason is reported via `error` and the object remains in @@ -291,6 +309,22 @@ public: void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error); + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map(handle, 0, map_entire_file, error); + } + /** * If a valid memory mapping has been created prior to this call, this call * instructs the kernel to unmap the memory region and disassociate this object diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index ba7e1e0..0ca5c14 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -92,7 +92,7 @@ public: * establishing the mapping is thrown. */ template - basic_shared_mmap(const String& path, const size_type offset, const size_type length) + basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); @@ -103,7 +103,7 @@ public: * The same as invoking the `map` function, except any error that may occur while * establishing the mapping is thrown. */ - basic_shared_mmap(const handle_type handle, const size_type offset, const size_type length) + basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); @@ -251,6 +251,24 @@ public: map_impl(path, offset, length, error); } + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map_impl(path, 0, map_entire_file, error); + } + /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this @@ -276,6 +294,22 @@ public: map_impl(handle, offset, length, error); } + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `handle`, which must be a valid file handle, which is used to memory map the + * requested region. Upon failure, `error` is set to indicate the reason and the + * object remains in an unmapped state. + * + * The entire file is mapped. + */ + void map(const handle_type handle, std::error_code& error) + { + map_impl(handle, 0, map_entire_file, error); + } + /** * If a valid memory mapping has been created prior to this call, this call * instructs the kernel to unmap the memory region and disassociate this object From 99db68866cc0aad331766ce3b02e62e33e97d019 Mon Sep 17 00:00:00 2001 From: mandreyel Date: Thu, 25 Oct 2018 09:15:15 +0200 Subject: [PATCH 07/14] Update README.md and example.cpp --- README.md | 46 ++++++++++++++++++++++++++++++---------------- test/example.cpp | 37 +++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 217ab7f..157629e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ There are three ways to map a file into memory: ```c++ mio::mmap_source mmap(path, offset, size_to_map); ``` +or you can omit the `offset` and `size_to_map` arguments, in which case the +entire file is mapped: +```c++ +mio::mmap_source mmap(path); +``` - Using the factory function: ```c++ @@ -37,6 +42,10 @@ std::error_code error; mio::mmap_source mmap; mmap.map(path, offset, size_to_map, error); ``` +or: +```c++ +mmap.map(path, error); +``` Moreover, in each case, you can provide either some string type for the file's path, or you can use an existing, valid file handle. ```c++ @@ -69,19 +78,8 @@ types for functions where character strings are expected (e.g. path parameters). #include #include -int handle_error(const std::error_code& error) -{ - const auto& errmsg = error.message(); - std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); -} - -void allocate_file(const std::string& path, const int size) -{ - std::ofstream file(path); - std::string s(size, '0'); - file << s; -} +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); int main() { @@ -124,14 +122,30 @@ int main() // effect as if the destructor had been invoked. rw_mmap.unmap(); - // Now create the same mapping, but in read-only mode. - mio::mmap_source ro_mmap = mio::make_mmap_source( - path, 0, mio::map_entire_file, error); + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); if (error) { return handle_error(error); } const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} ``` `mio::basic_mmap` is move-only, but if multiple copies to the same mapping are needed, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`. diff --git a/test/example.cpp b/test/example.cpp index 2f08b1b..841a57f 100644 --- a/test/example.cpp +++ b/test/example.cpp @@ -5,19 +5,8 @@ #include #include -int handle_error(const std::error_code& error) -{ - const auto& errmsg = error.message(); - std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); -} - -void allocate_file(const std::string& path, const int size) -{ - std::ofstream file(path); - std::string s(size, '0'); - file << s; -} +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); int main() { @@ -60,11 +49,27 @@ int main() // effect as if the destructor had been invoked. rw_mmap.unmap(); - // Now create the same mapping, but in read-only mode. - mio::mmap_source ro_mmap = mio::make_mmap_source( - path, 0, mio::map_entire_file, error); + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); if (error) { return handle_error(error); } const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} From 2e048e5a25f3ffb153cba25e423bf83213302afb Mon Sep 17 00:00:00 2001 From: mandreyel Date: Thu, 25 Oct 2018 09:22:46 +0200 Subject: [PATCH 08/14] Add entire file mapping overload to factory methods --- README.md | 4 ++++ include/mio/mmap.hpp | 17 ++++++++++++++++- test/test.cpp | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 157629e..901c8e1 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ mio::mmap_source mmap(path); std::error_code error; mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error); ``` +or: +```c++ +mio::mmap_source mmap = mio::make_mmap_source(path, error); +``` - Using the `map` member function: ```c++ diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 286e117..8e5d7c0 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -423,7 +423,10 @@ using ummap_source = basic_mmap_source; using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; -/** Convenience factory method that constructs a mapping for any `basic_mmap` type. */ +/** + * Convenience factory method that constructs a mapping for any `basic_mmap` or + * `basic_mmap` type. + */ template< typename MMap, typename MappingToken @@ -449,6 +452,12 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o return make_mmap(token, offset, length, error); } +template +mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) +{ + return make_mmap_source(token, 0, map_entire_file, error); +} + /** * Convenience factory method. * @@ -463,6 +472,12 @@ mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, return make_mmap(token, offset, length, error); } +template +mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_sink(token, 0, map_entire_file, error); +} + } // namespace mio #include "detail/mmap.ipp" diff --git a/test/test.cpp b/test/test.cpp index c9ae00b..36d350d 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -104,6 +104,8 @@ int main() // Make sure shared_mmap mapping compiles as all testing was done on // normal mmaps. mio::shared_mmap_source _3(path, 0, mio::map_entire_file); + auto _4 = mio::make_mmap_source(path, error); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file, error); } std::printf("all tests passed!\n"); From 56aaf4e71f01cecab2e50b721029f63a9db3200b Mon Sep 17 00:00:00 2001 From: AlanAtWork Date: Thu, 25 Oct 2018 10:40:22 -0500 Subject: [PATCH 09/14] Throw std::system_error --- include/mio/mmap.hpp | 4 ++-- include/mio/shared_mmap.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 8e5d7c0..513eae8 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -112,7 +112,7 @@ public: { std::error_code error; map(path, offset, length, error); - if(error) { throw error; } + if(error) { throw std::system_error(error); } } /** @@ -123,7 +123,7 @@ public: { std::error_code error; map(handle, offset, length, error); - if(error) { throw error; } + if(error) { throw std::system_error(error); } } /** diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index 0ca5c14..b8f8799 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -96,7 +96,7 @@ public: { std::error_code error; map(path, offset, length, error); - if(error) { throw error; } + if(error) { throw std::system_error(error); } } /** @@ -107,7 +107,7 @@ public: { std::error_code error; map(handle, offset, length, error); - if(error) { throw error; } + if(error) { throw std::system_error(error); } } /** From d33a0d567f5be01d0f0275fc71e5f13da58871fd Mon Sep 17 00:00:00 2001 From: mandreyel Date: Fri, 26 Oct 2018 09:00:27 +0200 Subject: [PATCH 10/14] Update docs to specify exception type --- README.md | 2 +- include/mio/mmap.hpp | 10 ++++++---- include/mio/shared_mmap.hpp | 10 ++++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 901c8e1..b0abcf2 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ NOTE: the file must exist before creating a mapping. There are three ways to map a file into memory: -- Using the constructor, which throws on failure: +- Using the constructor, which throws a `std::system_error` on failure: ```c++ mio::mmap_source mmap(path, offset, size_to_map); ``` diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 513eae8..ed4af12 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -104,8 +104,9 @@ public: basic_mmap() = default; /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ template basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) @@ -116,8 +117,9 @@ public: } /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index b8f8799..6ce187f 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -88,8 +88,9 @@ public: } /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ template basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) @@ -100,8 +101,9 @@ public: } /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { From 0da3002815ed985746ba28332890a97687e40a51 Mon Sep 17 00:00:00 2001 From: mandreyel Date: Wed, 7 Nov 2018 11:08:34 +0100 Subject: [PATCH 11/14] Fix missing error code in conditional_sync --- include/mio/detail/mmap.ipp | 2 +- include/mio/mmap.hpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index 1618ef1..a813fc3 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -334,7 +334,7 @@ void basic_mmap::sync(std::error_code& error) return; } - if(data() != nullptr) + if(data()) { #ifdef _WIN32 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index ed4af12..fd18d2b 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -372,7 +372,14 @@ private: */ template::type> - void conditional_sync() { sync(); } + void conditional_sync() + { + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); + } + template typename std::enable_if::type conditional_sync() {} }; From 261acc6bc690a77d6cae4f5d4c837f9a2634862c Mon Sep 17 00:00:00 2001 From: mandreyel Date: Wed, 7 Nov 2018 11:40:37 +0100 Subject: [PATCH 12/14] Follow up attempt to fix MSVC SFINAE build error --- include/mio/detail/mmap.ipp | 19 +++++++++++++++++++ include/mio/mmap.hpp | 15 ++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index a813fc3..a068891 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -418,6 +418,25 @@ void basic_mmap::swap(basic_mmap& other) } } +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // noop +} + template bool operator==(const basic_mmap& a, const basic_mmap& b) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index fd18d2b..2d18cd9 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -370,18 +370,11 @@ private: * if it's `read`, but since the destructor cannot be templated, we need to * do SFINAE in a dedicated function, where one syncs and the other is a noop. */ - template::type> - void conditional_sync() - { - // This is invoked from the destructor, so not much we can do about - // failures here. - std::error_code ec; - sync(ec); - } - template - typename std::enable_if::type conditional_sync() {} + typename std::enable_if::type + conditional_sync(); + template + typename std::enable_if::type conditional_sync(); }; template From bd144378954c1e95b712fe3f956168e7397c01e7 Mon Sep 17 00:00:00 2001 From: mandreyel Date: Wed, 7 Nov 2018 12:08:41 +0100 Subject: [PATCH 13/14] Second follow up attempt to fix MSVC SFINAE build error --- include/mio/detail/mmap.ipp | 3 +-- include/mio/mmap.hpp | 14 +++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index a068891..2045cd1 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -324,8 +324,7 @@ void basic_mmap::map(const handle_type handle, } template -template -void basic_mmap::sync(std::error_code& error) +void basic_mmap::sync_impl(std::error_code& error) { error.clear(); if(!is_open()) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 2d18cd9..694d9f4 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -344,7 +344,10 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > void sync(std::error_code& error); + > void sync(std::error_code& error) + { + sync_impl(error); + } /** * All operators compare the address of the first byte and size of the two mapped @@ -375,6 +378,15 @@ private: conditional_sync(); template typename std::enable_if::type conditional_sync(); + + /** + * Due to MSVC's fragile SFINAE support (see + * https://github.com/mandreyel/mio/issues/300), we need to have `sync` + * defined inline so that `conditional_sync` sees the definition. To not + * clutter the API, the implementation is extracted into this non-SFINAE + * private member function. + */ + void sync_impl(std::error_code& error); }; template From 45e5fcde9736a36011b74684f8f605581707ae3a Mon Sep 17 00:00:00 2001 From: mandreyel Date: Wed, 7 Nov 2018 12:31:59 +0100 Subject: [PATCH 14/14] Third follow up attempt to fix MSVC SFINAE build error --- include/mio/detail/mmap.ipp | 4 +++- include/mio/mmap.hpp | 19 +++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index 2045cd1..be6ef92 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -324,7 +324,9 @@ void basic_mmap::map(const handle_type handle, } template -void basic_mmap::sync_impl(std::error_code& error) +template +typename std::enable_if::type +basic_mmap::sync(std::error_code& error) { error.clear(); if(!is_open()) diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 694d9f4..2e69e63 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -341,13 +341,9 @@ public: void swap(basic_mmap& other); /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > void sync(std::error_code& error) - { - sync_impl(error); - } + template + typename std::enable_if::type + sync(std::error_code& error); /** * All operators compare the address of the first byte and size of the two mapped @@ -378,15 +374,6 @@ private: conditional_sync(); template typename std::enable_if::type conditional_sync(); - - /** - * Due to MSVC's fragile SFINAE support (see - * https://github.com/mandreyel/mio/issues/300), we need to have `sync` - * defined inline so that `conditional_sync` sees the definition. To not - * clutter the API, the implementation is extracted into this non-SFINAE - * private member function. - */ - void sync_impl(std::error_code& error); }; template