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

github.com/windirstat/llfio.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/headers.cmake3
-rw-r--r--include/afio/revision.hpp6
-rw-r--r--include/afio/v2.0/algorithm/mapped_view.hpp12
-rw-r--r--include/afio/v2.0/async_file_handle.hpp19
-rw-r--r--include/afio/v2.0/detail/impl/posix/map_handle.ipp32
-rw-r--r--include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp29
-rw-r--r--include/afio/v2.0/detail/impl/windows/map_handle.ipp23
-rw-r--r--include/afio/v2.0/detail/impl/windows/mapped_file_handle.ipp29
-rw-r--r--include/afio/v2.0/file_handle.hpp12
-rw-r--r--include/afio/v2.0/map_handle.hpp17
-rw-r--r--include/afio/v2.0/mapped_file_handle.hpp325
-rw-r--r--include/afio/v2.0/utils.hpp4
-rw-r--r--programs/key-value-store/include/key_value_store.hpp87
-rw-r--r--programs/key-value-store/main.cpp21
-rw-r--r--release_notes.md34
15 files changed, 601 insertions, 52 deletions
diff --git a/cmake/headers.cmake b/cmake/headers.cmake
index df7b25e8..163f49dc 100644
--- a/cmake/headers.cmake
+++ b/cmake/headers.cmake
@@ -26,6 +26,7 @@ set(afio_HEADERS
"include/afio/v2.0/io_handle.hpp"
"include/afio/v2.0/io_service.hpp"
"include/afio/v2.0/map_handle.hpp"
+ "include/afio/v2.0/mapped_file_handle.hpp"
"include/afio/v2.0/native_handle_type.hpp"
"include/afio/v2.0/path_handle.hpp"
"include/afio/v2.0/path_view.hpp"
@@ -44,6 +45,7 @@ set(afio_HEADERS
"include/afio/v2.0/detail/impl/posix/io_handle.ipp"
"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_handle.ipp"
"include/afio/v2.0/detail/impl/posix/stat.ipp"
"include/afio/v2.0/detail/impl/posix/statfs.ipp"
@@ -59,6 +61,7 @@ set(afio_HEADERS
"include/afio/v2.0/detail/impl/windows/io_handle.ipp"
"include/afio/v2.0/detail/impl/windows/io_service.ipp"
"include/afio/v2.0/detail/impl/windows/map_handle.ipp"
+ "include/afio/v2.0/detail/impl/windows/mapped_file_handle.ipp"
"include/afio/v2.0/detail/impl/windows/path_handle.ipp"
"include/afio/v2.0/detail/impl/windows/path_view.ipp"
"include/afio/v2.0/detail/impl/windows/stat.ipp"
diff --git a/include/afio/revision.hpp b/include/afio/revision.hpp
index ee44c856..74e81dac 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 59b3c2416850d6b4c7887f695fc4cfbd2064b3e9
-#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-04 19:37:42 +00:00"
-#define AFIO_PREVIOUS_COMMIT_UNIQUE 59b3c241
+#define AFIO_PREVIOUS_COMMIT_REF fec616c9c6f53b77b53b8852c51bbe5af11b82b8
+#define AFIO_PREVIOUS_COMMIT_DATE "2017-09-05 18:05:20 +00:00"
+#define AFIO_PREVIOUS_COMMIT_UNIQUE fec616c9
diff --git a/include/afio/v2.0/algorithm/mapped_view.hpp b/include/afio/v2.0/algorithm/mapped_view.hpp
index a2e13f66..6847c3d0 100644
--- a/include/afio/v2.0/algorithm/mapped_view.hpp
+++ b/include/afio/v2.0/algorithm/mapped_view.hpp
@@ -25,7 +25,7 @@ Distributed under the Boost Software License, Version 1.0.
#ifndef AFIO_MAPPED_VIEW_HPP
#define AFIO_MAPPED_VIEW_HPP
-#include "../map_handle.hpp"
+#include "../mapped_file_handle.hpp"
#include "../utils.hpp"
//! \file mapped_view.hpp Provides typed view of mapped section.
@@ -95,6 +95,16 @@ namespace algorithm
byteoffset, sh, (length == (size_type) -1) ? 0 : length * sizeof(T), _flag))
{
}
+ /*! Construct a mapped view of the given mapped file handle.
+
+ \param mfh The mapped file handle to use as the data source for creating the map.
+ \param length The number of items to map, use -1 to mean the length of the section handle divided by `sizeof(T)`.
+ \param byteoffset The byte offset into the mapped file handle, this does not need to be a multiple of the page size.
+ */
+ mapped_view(mapped_file_handle &sh, size_type length = (size_type) -1, extent_type byteoffset = 0)
+ : span<T>(sh.address() + byteoffset, (length == (size_type) -1) ? (sh.length().value() / sizeof(T)) : length)
+ {
+ }
};
} // namespace
diff --git a/include/afio/v2.0/async_file_handle.hpp b/include/afio/v2.0/async_file_handle.hpp
index ee9b6146..28a5c91b 100644
--- a/include/afio/v2.0/async_file_handle.hpp
+++ b/include/afio/v2.0/async_file_handle.hpp
@@ -96,6 +96,13 @@ public:
/*! Create an async file handle opening access to a file on path
using the given io_service.
+ \param service The `io_service` to use.
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param _path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
@@ -318,9 +325,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::all, 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::all, 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
@@ -349,7 +358,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::temporary, 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::temporary, 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));
}
@@ -367,7 +377,8 @@ inline result<async_file_handle> async_temp_inode(io_service &service, async_fil
{
return async_file_handle::async_temp_inode(std::forward<decltype(service)>(service), std::forward<decltype(dirpath)>(dirpath), 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/detail/impl/posix/map_handle.ipp b/include/afio/v2.0/detail/impl/posix/map_handle.ipp
index 37142f7a..dbc365a3 100644
--- a/include/afio/v2.0/detail/impl/posix/map_handle.ipp
+++ b/include/afio/v2.0/detail/impl/posix/map_handle.ipp
@@ -31,15 +31,27 @@ AFIO_V2_NAMESPACE_BEGIN
result<section_handle> section_handle::section(file_handle &backing, extent_type maximum_size, flag _flag) noexcept
{
+ extent_type length = 0;
+ if(backing.is_valid())
+ {
+ OUTCOME_TRY(_length, backing.length());
+ length = _length;
+ if(maximum_size > length)
+ {
+ // For compatibility with Windows, disallow sections larger than the file
+ return std::errc::value_too_large;
+ }
+ }
if(!maximum_size)
{
if(backing.is_valid())
{
- OUTCOME_TRY(length, backing.length());
maximum_size = length;
}
else
+ {
return std::errc::invalid_argument;
+ }
}
if(!backing.is_valid())
maximum_size = utils::round_up_to_page_size(maximum_size);
@@ -51,7 +63,21 @@ result<section_handle> section_handle::section(file_handle &backing, extent_type
result<section_handle::extent_type> section_handle::truncate(extent_type newsize) noexcept
{
- newsize = utils::round_up_to_page_size(newsize);
+ extent_type length = 0;
+ if(_backing)
+ {
+ OUTCOME_TRY(_length, _backing->length());
+ length = _length;
+ if(newsize > length)
+ {
+ // For compatibility with Windows, disallow sections larger than the file
+ return std::errc::value_too_large;
+ }
+ }
+ else
+ {
+ newsize = utils::round_up_to_page_size(newsize);
+ }
// There are no section handles on POSIX, so do nothing
_length = newsize;
return newsize;
@@ -132,7 +158,7 @@ map_handle::io_result<map_handle::const_buffers_type> map_handle::barrier(map_ha
static inline result<void *> do_mmap(native_handle_type &nativeh, void *ataddr, section_handle *section, map_handle::size_type &bytes, map_handle::extent_type offset, section_handle::flag _flag) noexcept
{
bool have_backing = section ? (section->backing() != nullptr) : false;
- int prot = 0, flags = have_backing ? MAP_SHARED : MAP_PRIVATE | MAP_ANONYMOUS;
+ int prot = 0, flags = have_backing ? MAP_SHARED : (MAP_PRIVATE | MAP_ANONYMOUS);
void *addr = nullptr;
if(_flag == section_handle::flag::none)
{
diff --git a/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp b/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp
new file mode 100644
index 00000000..0780785a
--- /dev/null
+++ b/include/afio/v2.0/detail/impl/posix/mapped_file_handle.ipp
@@ -0,0 +1,29 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 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 "../../../mapped_file_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+AFIO_V2_NAMESPACE_END
diff --git a/include/afio/v2.0/detail/impl/windows/map_handle.ipp b/include/afio/v2.0/detail/impl/windows/map_handle.ipp
index b3dd2c8b..0e76d6e7 100644
--- a/include/afio/v2.0/detail/impl/windows/map_handle.ipp
+++ b/include/afio/v2.0/detail/impl/windows/map_handle.ipp
@@ -81,7 +81,6 @@ result<section_handle> section_handle::section(file_handle &backing, extent_type
{
// In the case where there is a backing file, asking for read perms or no perms
// means "don't auto-expand the file to the nearest 4Kb multiple"
- attribs = SEC_RESERVE;
}
if(_flag & flag::executable)
attribs = SEC_IMAGE;
@@ -109,7 +108,18 @@ result<section_handle::extent_type> section_handle::truncate(extent_type newsize
{
windows_nt_kernel::init();
using namespace windows_nt_kernel;
- newsize = utils::round_up_to_page_size(newsize);
+ if(!newsize)
+ {
+ if(_backing)
+ {
+ OUTCOME_TRY(length, _backing->length());
+ newsize = length;
+ }
+ else
+ return std::errc::invalid_argument;
+ }
+ if(!_backing)
+ newsize = utils::round_up_to_page_size(newsize);
LARGE_INTEGER _maximum_size;
_maximum_size.QuadPart = newsize;
NTSTATUS ntstat = NtExtendSection(_v.h, &_maximum_size);
@@ -265,7 +275,7 @@ result<map_handle> map_handle::map(section_handle &section, size_type bytes, ext
}
result<map_handle> ret{map_handle(&section)};
native_handle_type &nativeh = ret.value()._v;
- ULONG allocation = 0, prot = 0;
+ ULONG allocation = 0, prot = PAGE_NOACCESS;
PVOID addr = 0;
size_t commitsize = bytes;
LARGE_INTEGER _offset;
@@ -273,13 +283,10 @@ result<map_handle> map_handle::map(section_handle &section, size_type bytes, ext
SIZE_T _bytes = bytes;
if((_flag & section_handle::flag::nocommit) || (_flag == section_handle::flag::none))
{
- // Perhaps this is only valid from kernel mode? Either way, any attempt to use MEM_RESERVE caused an invalid parameter error.
- // Weirdly, setting commitsize to 0 seems to do a reserve as we wanted
- // allocation = MEM_RESERVE;
+ allocation = MEM_RESERVE;
commitsize = 0;
- prot = PAGE_NOACCESS;
}
- else if(_flag & section_handle::flag::cow)
+ if(_flag & section_handle::flag::cow)
{
prot = PAGE_WRITECOPY;
nativeh.behaviour |= native_handle_type::disposition::seekable | native_handle_type::disposition::readable | native_handle_type::disposition::writable;
diff --git a/include/afio/v2.0/detail/impl/windows/mapped_file_handle.ipp b/include/afio/v2.0/detail/impl/windows/mapped_file_handle.ipp
new file mode 100644
index 00000000..0780785a
--- /dev/null
+++ b/include/afio/v2.0/detail/impl/windows/mapped_file_handle.ipp
@@ -0,0 +1,29 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 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 "../../../mapped_file_handle.hpp"
+#include "import.hpp"
+
+AFIO_V2_NAMESPACE_BEGIN
+AFIO_V2_NAMESPACE_END
diff --git a/include/afio/v2.0/file_handle.hpp b/include/afio/v2.0/file_handle.hpp
index 6f04c832..b289dc7f 100644
--- a/include/afio/v2.0/file_handle.hpp
+++ b/include/afio/v2.0/file_handle.hpp
@@ -117,6 +117,12 @@ public:
}
/*! Create a file handle opening access to a file on path
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param _path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
\errors Any of the values POSIX open() or CreateFile() can return.
*/
@@ -266,7 +272,8 @@ inline void swap(file_handle &self, file_handle &o) noexcept
\errors Any of the values POSIX open() or CreateFile() can return.
*/
-inline result<file_handle> file(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) noexcept
+inline result<file_handle> file(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) noexcept
{
return file_handle::file(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));
}
@@ -297,7 +304,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<file_handle> temp_file(file_handle::path_view_type name = file_handle::path_view_type(), file_handle::mode _mode = file_handle::mode::write, file_handle::creation _creation = file_handle::creation::if_needed, file_handle::caching _caching = file_handle::caching::temporary, file_handle::flag flags = file_handle::flag::unlink_on_close) noexcept
+inline result<file_handle> temp_file(file_handle::path_view_type name = file_handle::path_view_type(), file_handle::mode _mode = file_handle::mode::write, file_handle::creation _creation = file_handle::creation::if_needed, file_handle::caching _caching = file_handle::caching::temporary,
+ file_handle::flag flags = file_handle::flag::unlink_on_close) noexcept
{
return file_handle::temp_file(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));
}
diff --git a/include/afio/v2.0/map_handle.hpp b/include/afio/v2.0/map_handle.hpp
index 9690293f..d8cbba0d 100644
--- a/include/afio/v2.0/map_handle.hpp
+++ b/include/afio/v2.0/map_handle.hpp
@@ -67,7 +67,7 @@ public:
QUICKCPPLIB_BITFIELD_END(flag);
protected:
- io_handle *_backing;
+ file_handle *_backing;
extent_type _length;
flag _flag;
@@ -80,7 +80,7 @@ public:
{
}
//! Construct a section handle using the given native handle type for the section and the given i/o handle for the backing storage
- explicit constexpr section_handle(native_handle_type sectionh, io_handle *backing, extent_type maximum_size, flag __flag)
+ explicit constexpr section_handle(native_handle_type sectionh, file_handle *backing, extent_type maximum_size, flag __flag)
: handle(sectionh, handle::caching::all)
, _backing(backing)
, _length(maximum_size)
@@ -112,7 +112,8 @@ public:
/*! \brief Create a memory section.
\param backing The handle to use as backing storage. An invalid handle means to use the system page file as the backing storage.
- \param maximum_size The maximum size this section can ever be. Zero means to use backing.length().
+ \param maximum_size The maximum size this section can ever be. Zero means to use `backing.length()`. This cannot exceed the size
+ of any backing file used.
\param _flag How to create the section.
\errors Any of the values POSIX dup() or NtCreateSection() can return.
@@ -141,11 +142,13 @@ public:
extent_type length() const noexcept { return _length; }
/*! Resize the current maximum permitted extent of the memory section to the given extent.
+ \param newsize The new size of the memory section. Specify zero to use `backing.length()`.
+ This cannot exceed the size of any backing file used.
\errors Any of the values NtExtendSection() can return. On POSIX this is a no op.
*/
AFIO_MAKE_FREE_FUNCTION
- AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> truncate(extent_type newsize) noexcept;
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> truncate(extent_type newsize = 0) noexcept;
};
inline std::ostream &operator<<(std::ostream &s, const section_handle::flag &v)
{
@@ -183,6 +186,8 @@ inline std::ostream &operator<<(std::ostream &s, const section_handle::flag &v)
\note The native handle returned by this map handle is always that of the backing storage, but closing this handle
does not close that of the backing storage, nor does releasing this handle release that of the backing storage.
Locking byte ranges of this handle is therefore equal to locking byte ranges in the original backing storage.
+
+\sa `mapped_file_handle`, `algorithm::mapped_view`
*/
class AFIO_DECL map_handle : public io_handle
{
@@ -227,7 +232,7 @@ public:
, _flag(section_handle::flag::none)
{
}
- AFIO_HEADERS_ONLY_MEMFUNC_SPEC ~map_handle();
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~map_handle();
//! Implicit move construction of map_handle permitted
constexpr map_handle(map_handle &&o) noexcept : io_handle(std::move(o)), _section(o._section), _addr(o._addr), _offset(o._offset), _length(o._length), _flag(o._flag)
{
@@ -466,7 +471,7 @@ inline map_handle::io_result<map_handle::buffers_type> read(map_handle &self, ma
{
return self.read(std::forward<decltype(reqs)>(reqs), std::forward<decltype(d)>(d));
}
-/*! \brief Write data to the mapped view.
+/*! \brief Write data to the mapped view. Note this will never extend past the current length of the mapped file.
\return The buffers written, which will never be the buffers input because they will point at where the data was copied into the mapped view.
The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
diff --git a/include/afio/v2.0/mapped_file_handle.hpp b/include/afio/v2.0/mapped_file_handle.hpp
new file mode 100644
index 00000000..68aa021e
--- /dev/null
+++ b/include/afio/v2.0/mapped_file_handle.hpp
@@ -0,0 +1,325 @@
+/* An mapped handle to a file
+(C) 2017 Niall Douglas <http://www.nedproductions.biz/> (11 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 "map_handle.hpp"
+
+//! \file mapped_file_handle.hpp Provides mapped_file_handle
+
+#ifndef AFIO_MAPPED_FILE_HANDLE_H
+#define AFIO_MAPPED_FILE_HANDLE_H
+
+AFIO_V2_NAMESPACE_EXPORT_BEGIN
+
+/*! \class mapped_file_handle
+\brief A memory mapped regular file or device
+
+All the major OSs on all the major 64 bit CPU architectures now offer at least 127 Tb of address
+spaces to user mode processes. This makes feasible mapping multi-Tb files directly into
+memory, and thus avoiding the syscall overhead involved when reading and writing. This
+becames **especially** important with nextgen storage devices capable of Direct Access
+Storage (DAX) like Optane from 2018 onwards, performance via syscalls will always be
+but a fraction of speaking directly to the storage device via directly mapped memory.
+
+As an example of the gains, on Microsoft Windows to read or write 1Kb using the standard
+syscalls takes about fifteen times longer than the exact same i/o via mapped memory. On Linux,
+OS X or FreeBSD the gain is considerably lower, a 1Kb i/o might only be 50% slower via syscalls
+than memory maps. However for lots of say 64 byte i/o, the gain of memory maps over
+syscalls is unsurpassable.
+
+This class combines a `file_handle` with a `section_handle` and a `map_handle` to
+implement a fully memory mapped `file_handle`. The whole file is always mapped entirely
+into memory, including any appends to the file, and i/o is performed directly with the map.
+Reads always return the original mapped data, and do not fill any buffers passed in.
+For obvious reasons the utility of this class on 32-bit systems is limited,
+but can be useful when used with smaller files.
+
+Note that zero lengthed files cannot be memory mapped. On first write will the map be
+created, until then `address()` will return a null pointer. Similarly, calling `truncate(0)`
+will destroy the maps which can be useful to know as Microsoft Windows will not permit
+shrinking of a file with open maps in any process on it, thus every process must call
+`truncate(0)` and sink the error about the shrink failing until when the final process to
+attempt the truncation succeeds.
+
+For better performance when handling files which are growing, there is a concept of
+"address space reservation" via `reserve()` and `capacity()`. The implementation asks the
+kernel to set out a contiguous region of pages matching that reservation, and to map the
+file into the beginning of the reservation. The remainder of the pages are inaccessible
+and will generate a segfault.
+
+`length()` reports the length of the mapped file, NOT the underlying file. For better
+performance, and to avoid kernel bugs, we do not automatically track the length of the
+underlying file, so reads and writes always terminate at the mapped file length and do
+not auto-extend the file. If you wish to extend the file, you must call `truncate(bytes)`
+which is of course racy with respect to other things extending the file. When you
+know that another process has extended the file and you wish to map the newly appended
+data, you can call `truncate()` with no parameters. This will read the current length
+of the underlying file, and map the new data into your process up until the reservation
+is full. It is then up to you to detect that the reservation has been exhausted, and to
+reserve a new reservation which will change the value returned by `address()`. This
+entirely manual system is a bit tedious and cumbersome to use, but as mapping files
+is an expensive operation given TLB shootdown, the only place where we change mappings
+silently is on the first write to an empty mapped file.
+
+\warning You must be cautious when the file is being extended by third parties which are
+not using this `mapped_file_handle` to write the new data. With unified page cache kernels,
+mixing mapped and normal i/o is generally safe except at the end of a file where race
+conditions and outright kernel bugs tend to abound. To avoid these, **make sure you truncate to new
+length before appending data**, or else solely and exclusively use a dedicated handle
+configured to atomic append only to do the appends.
+*/
+class AFIO_DECL mapped_file_handle : public file_handle
+{
+
+public:
+ using dev_t = file_handle::dev_t;
+ using ino_t = file_handle::ino_t;
+ using path_view_type = file_handle::path_view_type;
+ using path_type = io_handle::path_type;
+ using extent_type = io_handle::extent_type;
+ using size_type = io_handle::size_type;
+ using mode = io_handle::mode;
+ using creation = io_handle::creation;
+ using caching = io_handle::caching;
+ using flag = io_handle::flag;
+ using buffer_type = io_handle::buffer_type;
+ using const_buffer_type = io_handle::const_buffer_type;
+ using buffers_type = io_handle::buffers_type;
+ using const_buffers_type = io_handle::const_buffers_type;
+ template <class T> using io_request = io_handle::io_request<T>;
+ template <class T> using io_result = io_handle::io_result<T>;
+
+protected:
+ section_handle _sh;
+ map_handle _mh;
+
+public:
+ //! Default constructor
+ mapped_file_handle() = default;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC ~mapped_file_handle();
+
+ //! Construct a handle from a supplied native handle
+ constexpr mapped_file_handle(native_handle_type h, dev_t devid, ino_t inode, caching caching = caching::none, flag flags = flag::none)
+ : file_handle(std::move(h), devid, inode, std::move(caching), std::move(flags))
+ {
+ }
+ //! Implicit move construction of mapped_file_handle permitted
+ mapped_file_handle(mapped_file_handle &&o) noexcept = default;
+ //! Explicit conversion from file_handle permitted
+ explicit constexpr mapped_file_handle(file_handle &&o) noexcept : file_handle(std::move(o)) {}
+ //! Move assignment of mapped_file_handle permitted
+ mapped_file_handle &operator=(mapped_file_handle &&o) noexcept
+ {
+ this->~mapped_file_handle();
+ new(this) mapped_file_handle(std::move(o));
+ return *this;
+ }
+ //! Swap with another instance
+ AFIO_MAKE_FREE_FUNCTION
+ void swap(mapped_file_handle &o) noexcept
+ {
+ mapped_file_handle temp(std::move(*this));
+ *this = std::move(o);
+ o = std::move(temp);
+ }
+
+ /*! Create a memory mapped file handle opening access to a file on path.
+ \param reservation The number of bytes to reserve for later expansion when mapping. Zero means reserve only the current file length.
+ \param base Handle to a base location on the filing system. Pass `{}` to indicate that path will be absolute.
+ \param _path The path relative to base to open.
+ \param _mode How to open the file.
+ \param _creation How to create the file.
+ \param _caching How to ask the kernel to cache the file.
+ \param flags Any additional custom behaviours.
+
+ Note that if the file is currently zero sized, no mapping occurs now, but
+ later when `truncate()` or `write()` is called.
+
+ \errors Any of the values which the constructors for `file_handle`, `section_handle` and `map_handle` can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<mapped_file_handle> mapped_file(size_type reservation, const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept;
+ //! \overload
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_file(const path_handle &base, path_view_type _path, mode _mode = mode::read, creation _creation = creation::open_existing, caching _caching = caching::all, flag flags = flag::none) noexcept { return mapped_file(0, base, _path, _mode, _creation, _caching, flags); }
+
+ /*! Create an mapped file handle creating a randomly named file on a path.
+ The file is opened exclusively with `creation::only_if_not_exist` so it
+ will never collide with nor overwrite any existing file. Note also
+ that caching defaults to temporary which hints to the OS to only
+ flush changes to physical storage as lately as possible.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_random_file(size_type reservation, const path_handle &dirpath, mode _mode = mode::write, caching _caching = caching::temporary, flag flags = flag::none) noexcept
+ {
+ try
+ {
+ for(;;)
+ {
+ auto randomname = utils::random_string(32);
+ randomname.append(".random");
+ result<mapped_file_handle> ret = mapped_file(reservation, dirpath, randomname, _mode, creation::only_if_not_exist, _caching, flags);
+ if(ret || (!ret && ret.error() != std::errc::file_exists))
+ return ret;
+ }
+ }
+ catch(...)
+ {
+ return error_from_exception();
+ }
+ }
+ /*! Create a mapped file handle creating the named file on some path which
+ the OS declares to be suitable for temporary files. Most OSs are
+ very lazy about flushing changes made to these temporary files.
+ Note the default flags are to have the newly created file deleted
+ on first handle close.
+ Note also that an empty name is equivalent to calling
+ `mapped_random_file(temporary_files_directory())` and the creation
+ parameter is ignored.
+
+ \note If the temporary file you are creating is not going to have its
+ path sent to another process for usage, this is the WRONG function
+ to use. Use `temp_inode()` instead, it is far more secure.
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static inline result<mapped_file_handle> mapped_temp_file(size_type reservation, path_view_type name = path_view_type(), mode _mode = mode::write, creation _creation = creation::if_needed, caching _caching = caching::temporary, flag flags = flag::unlink_on_close) noexcept
+ {
+ OUTCOME_TRY(tempdirh, path_handle::path(temporary_files_directory()));
+ return name.empty() ? mapped_random_file(reservation, tempdirh, _mode, _caching, flags) : mapped_file(reservation, tempdirh, name, _mode, _creation, _caching, flags);
+ }
+ /*! \em Securely create a mapped file handle creating a temporary anonymous inode in
+ the filesystem referred to by \em dirpath. The inode created has
+ no name nor accessible path on the filing system and ceases to
+ exist as soon as the last handle is closed, making it ideal for use as
+ a temporary file where other processes do not need to have access
+ to its contents via some path on the filing system (a classic use case
+ is for backing shared memory maps).
+
+ \errors Any of the values POSIX open() or CreateFile() can return.
+ */
+ AFIO_MAKE_FREE_FUNCTION
+ static AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<mapped_file_handle> mapped_temp_inode(path_view_type dirpath = temporary_files_directory(), mode _mode = mode::write, flag flags = flag::none) noexcept
+ {
+ // Open it overlapped, otherwise no difference.
+ OUTCOME_TRY(v, file_handle::temp_inode(std::move(dirpath), std::move(_mode), flags));
+ mapped_file_handle ret(std::move(v));
+ return std::move(ret);
+ }
+
+ //! The memory section this handle is using
+ const section_handle &section() const noexcept { return _sh; }
+ //! The memory section this handle is using
+ section_handle &section() noexcept { return _sh; }
+
+ //! The map this handle is using
+ const map_handle &map() const noexcept { return _mh; }
+ //! The map this handle is using
+ map_handle &map() noexcept { return _mh; }
+
+ //! The address in memory where this mapped file resides
+ char *address() const noexcept { return _mh.address(); }
+
+ //! The length of the underlying file
+ result<extent_type> underlying_file_length() const noexcept { return file_handle::length(); }
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<void> close() noexcept override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC native_handle_type release() noexcept override;
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<const_buffers_type> barrier(io_request<const_buffers_type> reqs = io_request<const_buffers_type>(), bool wait_for_device = false, bool and_metadata = false, deadline d = deadline()) noexcept override
+ {
+ return _mh.barrier(std::move(reqs), wait_for_device, and_metadata, std::move(d));
+ }
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<file_handle> clone() const noexcept override;
+ //! Return the current maximum permitted extent of the file.
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> length() const noexcept override { return _mh.length(); }
+
+ /*! \brief Resize the current maximum permitted extent of the mapped file to the given extent, avoiding any
+ new allocation of physical storage where supported.
+
+ Note that on extents based filing systems
+ this will succeed even if there is insufficient free space on the storage medium. Only when
+ pages are written to will the lack of sufficient free space be realised, resulting in an
+ operating system specific exception.
+
+ \note On Microsoft Windows you cannot shrink a file below any section handle's extent in any
+ process in the system. We do, of course, shrink the internally held section handle correctly
+ before truncating the underlying file. However you will need to coordinate with any other
+ processes to shrink their section handles first. This is partly why `section()` is exposed.
+
+ \return The bytes actually truncated to.
+ \param newsize The bytes to truncate the file to. Zero causes the maps to be closed before
+ truncation.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> truncate(extent_type newsize) noexcept override;
+ /*! \brief Resize the mapping to match that of the underlying file, returning the size of the underlying file.
+
+ If the internal section and map handle are invalid, they are restored unless the underlying file is zero length.
+ */
+ AFIO_HEADERS_ONLY_MEMFUNC_SPEC result<extent_type> truncate() noexcept;
+
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC result<extent_type> zero(extent_type offset, extent_type bytes, deadline /*unused*/ = deadline()) noexcept override
+ {
+ OUTCOME_TRYV(_mh.zero_memory({_mh.address() + offset, bytes}));
+ return bytes;
+ }
+
+ /*! \brief Read data from the mapped file.
+
+ \note Because this implementation never copies memory, you can pass in buffers with a null address.
+
+ \return The buffers read, which will never be the buffers input because they will point into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors None, though the various signals and structured exception throws common to using memory maps may occur.
+ \mallocs None.
+ */
+ AFIO_HEADERS_ONLY_VIRTUAL_SPEC io_result<buffers_type> read(io_request<buffers_type> reqs, deadline d = deadline()) noexcept override { return _mh.read(std::move(reqs), std::move(d)); }
+ /*! \brief Write data to the mapped file. Note this will never extend past the current length of the mapped file.
+
+ \return The buffers written, which will never be the buffers input because they will point at where the data was copied into the mapped view.
+ The size of each scatter-gather buffer is updated with the number of bytes of that buffer transferred.
+ \param reqs A scatter-gather and offset request.
+ \param d Ignored.
+ \errors None, though the various signals and structured exception throws common to using memory maps may occur.
+ \mallocs None.
+ */
+ 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)); }
+};
+
+AFIO_V2_NAMESPACE_END
+
+#if AFIO_HEADERS_ONLY == 1 && !defined(DOXYGEN_SHOULD_SKIP_THIS)
+#define AFIO_INCLUDED_BY_HEADER 1
+#ifdef _WIN32
+#include "detail/impl/windows/mapped_file_handle.ipp"
+#else
+#include "detail/impl/posix/mapped_file_handle.ipp"
+#endif
+#undef AFIO_INCLUDED_BY_HEADER
+#endif
+
+#endif
diff --git a/include/afio/v2.0/utils.hpp b/include/afio/v2.0/utils.hpp
index 088cd925..75e241b0 100644
--- a/include/afio/v2.0/utils.hpp
+++ b/include/afio/v2.0/utils.hpp
@@ -51,7 +51,7 @@ namespace utils
template <class T> inline T round_down_to_page_size(T i) noexcept
{
const size_t pagesize = page_size();
- i = i & ~(pagesize - 1);
+ i = (T)((uintptr_t) i & ~(pagesize - 1));
return i;
}
/*! \brief Round a value to its next highest page size multiple
@@ -59,7 +59,7 @@ namespace utils
template <class T> inline T round_up_to_page_size(T i) noexcept
{
const size_t pagesize = page_size();
- i = (i + pagesize - 1) & ~(pagesize - 1);
+ i = (T)(((uintptr_t) i + pagesize - 1) & ~(pagesize - 1));
return i;
}
/*! \brief Round a pair of a pointer and a size_t to their nearest page size multiples. The pointer will be rounded
diff --git a/programs/key-value-store/include/key_value_store.hpp b/programs/key-value-store/include/key_value_store.hpp
index a4283b05..0150bab3 100644
--- a/programs/key-value-store/include/key_value_store.hpp
+++ b/programs/key-value-store/include/key_value_store.hpp
@@ -161,18 +161,22 @@ namespace key_value_store
friend class transaction;
afio::file_handle _indexfile;
afio::file_handle _mysmallfile; // append only
+ afio::map_handle _mysmallfilemapped;
afio::file_handle::extent_guard _indexfileguard, _smallfileguard;
size_t _mysmallfileidx{(size_t) -1};
struct
{
std::vector<afio::file_handle> read;
+ std::vector<afio::section_handle> section;
+ std::vector<afio::map_handle> map;
} _smallfiles;
optional<index::open_hash_index> _index;
index::index *_indexheader{nullptr};
std::mutex _commitlock;
bool _use_mmaps_for_commit{false};
+ size_t _mmap_over_extension{0};
- static constexpr afio::file_handle::extent_type _indexinuseoffset = (afio::file_handle::extent_type) -1;
+ static constexpr afio::file_handle::extent_type _indexinuseoffset = INT64_MAX;
static constexpr uint64_t _goodmagic = 0x3130564b4f494641; // "AFIOKV01"
static constexpr uint64_t _badmagic = 0x3130564b44414544; // "DEADKV01"
@@ -221,6 +225,10 @@ namespace key_value_store
}
if(!claimed)
{
+#ifndef _WIN32
+ // We really need this to only have read only perms, otherwise any mmaps will extend the file ludicrously
+ fh = afio::file_handle::file(dir, name, afio::file_handle::mode::read, afio::file_handle::creation::open_existing, afio::file_handle::caching::all, afio::file_handle::flag::disable_prefetching);
+#endif
_smallfiles.read.push_back(std::move(fh).value());
}
continue;
@@ -228,9 +236,10 @@ namespace key_value_store
else if(mode == afio::file_handle::mode::write && !_mysmallfile.is_valid())
{
// Going to need a new smallfile
- fh = afio::file_handle::file(dir, name, smallfilemode, afio::file_handle::creation::only_if_not_exist, caching);
+ fh = afio::file_handle::file(dir, name, afio::file_handle::mode::write, afio::file_handle::creation::only_if_not_exist, caching);
if(fh)
{
+ fh.value().truncate(64).value();
goto retry;
}
continue;
@@ -360,7 +369,7 @@ namespace key_value_store
Normally, this implementation atomic-appends small objects to the smallfile via gather write.
This is simple, safe, and with reasonable performance most of the time.
- However in certain circumstances e.g. with synchronous i/o enabled, atomic-append forces lots of
+ However in certain circumstances e.g. with synchronous i/o enabled, atomic-append can cause lots of
read-modify-write cycles on the storage device. This can become unusably slow if the values you
write are small, or your OS doesn't implement gather writes for file i/o (e.g. Windows). For
these circumstances, one can instead use a memory map of the end of the smallfile to append
@@ -388,6 +397,37 @@ namespace key_value_store
}
_use_mmaps_for_commit = v;
}
+ /*! \brief Sets whether to use mmaps for fetches.
+
+ Requires lots of virtual address space as the entire
+ of all the small files is mapped into memory with additional `overextension`. Also requires a kernel
+ page cache implementation which correctly updates appends to the smallfile into the mapped view
+ without `msync(MS_INVALIDATE)`.
+ */
+ void use_mmaps_for_fetch(size_t overextension = 1024ULL * 1024 * 1024)
+ {
+ if(_mmap_over_extension != 0)
+ return;
+ _smallfiles.section.reserve(_smallfiles.read.size());
+ _smallfiles.map.reserve(_smallfiles.read.size());
+ for(size_t n = 0; n < _smallfiles.read.size(); n++)
+ {
+ auto currentlength = _smallfiles.read[n].length().value();
+ _smallfiles.section.push_back(afio::section_handle::section(_smallfiles.read[n], currentlength,
+#ifdef _WIN32
+ // Yes this is confusing. But for some reason, Windows won't permit overextended views on read only sections.
+ // And somehow or other, Windows permits read/write sections on read only files. Which makes zero sense.
+ afio::section_handle::flag::readwrite
+#else
+ afio::section_handle::flag::read
+#endif
+ )
+ .value());
+ // The nocommit allows us to reserve all the address space now, and to fill in mapped data later as the file extends
+ _smallfiles.map.push_back(afio::map_handle::map(_smallfiles.section.back(), currentlength + overextension, 0, afio::section_handle::flag::nocommit | afio::section_handle::flag::read).value());
+ }
+ _mmap_over_extension = overextension;
+ }
//! Retrieve when keys were last updated by setting the second to the latest transaction counter.
//! Note that counter will be `(uint64_t)-1` for any unknown keys. Never throws exceptions.
@@ -418,7 +458,7 @@ namespace key_value_store
//! When this value was last modified
uint64_t transaction_counter;
- keyvalue_info(keyvalue_info &&o) noexcept : key(std::move(o.key)), value(std::move(o.value)), transaction_counter(std::move(o.transaction_counter)), _value_buffer(std::move(o._value_buffer)), _value_view(std::move(o._value_view)) { o._value_buffer = nullptr; }
+ keyvalue_info(keyvalue_info &&o) noexcept : key(std::move(o.key)), value(std::move(o.value)), transaction_counter(std::move(o.transaction_counter)), _value_buffer(std::move(o._value_buffer)) { o._value_buffer = nullptr; }
keyvalue_info &operator=(keyvalue_info &&o) noexcept
{
this->~keyvalue_info();
@@ -447,15 +487,14 @@ namespace key_value_store
, transaction_counter((uint64_t) -1)
{
}
- keyvalue_info(key_type _key, span<char> buffer, uint64_t tc)
+ keyvalue_info(key_type _key, span<char> buffer, bool free_on_destruct, uint64_t tc)
: key(_key)
, value(buffer)
, transaction_counter(tc)
- , _value_buffer(buffer.data())
+ , _value_buffer(free_on_destruct ? buffer.data() : nullptr)
{
}
char *_value_buffer{nullptr};
- afio::algorithm::mapped_view<const char> _value_view;
};
//! Retrieve the latest value for a key. May throw `corrupted_store`
keyvalue_info find(key_type key, size_t revision = 0)
@@ -480,17 +519,37 @@ namespace key_value_store
return keyvalue_info(key);
}
size_t length = item.length, smallfilelength = _pad_length(length);
- char *buffer = (char *) malloc(smallfilelength);
- if(!buffer)
- {
- throw std::bad_alloc();
- }
if(item.value_identifier >= _smallfiles.read.size())
{
// TODO: Open newly created smallfiles
abort();
}
- _smallfiles.read[item.value_identifier].read(item.value_offset * 64 - smallfilelength, buffer, smallfilelength).value();
+ char *buffer;
+ bool free_on_destruct = _smallfiles.map.empty() || !_smallfiles.map[item.value_identifier].is_valid();
+ if(!free_on_destruct)
+ {
+ if(item.value_offset * 64 > _smallfiles.section[item.value_identifier].length())
+ {
+ auto oldsize = _smallfiles.section[item.value_identifier].length();
+ auto newsize = _smallfiles.read[item.value_identifier].length().value();
+ // Resize the memory section to the current size of the file
+ _smallfiles.section[item.value_identifier].truncate(newsize).value();
+ // Commit the newly mapped pages
+ afio::map_handle::buffer_type bt{_smallfiles.map[item.value_identifier].address() + oldsize, newsize - oldsize};
+ bt.data = afio::utils::round_up_to_page_size(bt.data);
+ _smallfiles.map[item.value_identifier].commit(bt, afio::section_handle::flag::read).value();
+ }
+ buffer = _smallfiles.map[item.value_identifier].address() + item.value_offset * 64 - smallfilelength;
+ }
+ else
+ {
+ buffer = (char *) malloc(smallfilelength);
+ if(!buffer)
+ {
+ throw std::bad_alloc();
+ }
+ _smallfiles.read[item.value_identifier].read(item.value_offset * 64 - smallfilelength, buffer, smallfilelength).value();
+ }
index::value_tail *vt = reinterpret_cast<index::value_tail *>(buffer + smallfilelength - sizeof(index::value_tail));
if(_indexheader->contents_hashed || _indexheader->key_is_hash_of_value)
{
@@ -519,7 +578,7 @@ namespace key_value_store
_indexheader->magic = _badmagic;
throw corrupted_store();
}
- return keyvalue_info(key, span<char>(buffer, length), item.transaction_counter);
+ return keyvalue_info(key, span<char>(buffer, length), free_on_destruct, item.transaction_counter);
}
}
};
diff --git a/programs/key-value-store/main.cpp b/programs/key-value-store/main.cpp
index a6349453..26eec4bc 100644
--- a/programs/key-value-store/main.cpp
+++ b/programs/key-value-store/main.cpp
@@ -227,6 +227,7 @@ int main()
}
{
key_value_store::basic_key_value_store store("teststore", 2000000);
+ store.use_mmaps_for_fetch();
benchmark(store, "no integrity, no durability, commit appends");
}
{
@@ -260,6 +261,26 @@ int main()
AFIO_V2_NAMESPACE::filesystem::remove_all("teststore", ec);
}
{
+ key_value_store::basic_key_value_store store("teststore", 2000000);
+ store.use_mmaps_for_commit(true);
+ store.use_mmaps_for_fetch();
+ benchmark(store, "no integrity, no durability, commit mmaps, fetch mmaps");
+ }
+ {
+ std::error_code ec;
+ AFIO_V2_NAMESPACE::filesystem::remove_all("teststore", ec);
+ }
+ {
+ key_value_store::basic_key_value_store store("teststore", 2000000, true);
+ store.use_mmaps_for_commit(true);
+ store.use_mmaps_for_fetch();
+ benchmark(store, "integrity, no durability, commit mmaps, fetch mmaps");
+ }
+ {
+ std::error_code ec;
+ AFIO_V2_NAMESPACE::filesystem::remove_all("teststore", ec);
+ }
+ {
key_value_store::basic_key_value_store store("teststore", 2000000, true, AFIO_V2_NAMESPACE::file_handle::mode::write, AFIO_V2_NAMESPACE::file_handle::caching::reads);
store.use_mmaps_for_commit(true);
benchmark(store, "integrity, durability, commit mmaps");
diff --git a/release_notes.md b/release_notes.md
index 6bd2c6d0..4e6577d0 100644
--- a/release_notes.md
+++ b/release_notes.md
@@ -9,7 +9,12 @@
Herein lies my proposed zero whole machine memory copy async file i/o and filesystem
library for Boost and the C++ standard, intended for storage devices with ~1 microsecond
-4Kb transfer latencies.
+4Kb transfer latencies and those supporting Storage Class Memory (SCM)/Direct Access
+Storage (DAX). Its i/o overhead, including syscall overhead, has been benchmarked to
+100 nanoseconds on Linux which corresponds to a theoretical maximum of 10M IOPS @ QD1,
+approx 40Gb/sec per thread. It has particularly strong support for writing portable
+filesystem algorithms which work well with directly mapped non-volatile storage such
+as Intel Optane.
It is a complete rewrite after a Boost peer review in August 2015. Its github
source code repository lives at https://github.com/ned14/boost.afio.
@@ -34,22 +39,33 @@ Manufacturer claimed 4Kb transfer latencies for the physical hardware:
- &lt; 99% spinning rust hard drive latency: Windows **187,231us** FreeBSD **9,836us** Linux **26,468us**
- &lt; 99% SATA flash drive latency: Windows **290us** Linux **158us**
- &lt; 99% NVMe drive latency: Windows **37us** FreeBSD **70us** Linux **30us**
-
-Worst case AFIO read overhead benchmarked by author: **0.19us** (5.3M IOPS @ QD1, approx 20Gb/sec)
</td>
<td valign="top" width="33%">
75% read 25% write QD4 4Kb direct transfer latencies for the software with AFIO:
- &lt; 99% spinning rust hard drive latency: Windows **48,185us** FreeBSD **61,834us** Linux **104,507us**
- &lt; 99% SATA flash drive latency: Windows **1,812us** Linux **1,416us**
- &lt; 99% NVMe drive latency: Windows **50us** FreeBSD **143us** Linux **40us**
-
-Worst case AFIO write overhead benchmarked by author: **0.18us** (5.6M IOPS @ QD1, approx 21Gb/sec)
</td>
</tr>
</table>
\note Note that this code is of late alpha quality. It's quite reliable on Windows and Linux, but be careful when using it!
+Example of use:
+\code
+namespace afio = AFIO_V2_NAMESPACE;
+// Make me a 1 trillion element sparsely allocated integer array!
+afio::mapped_file_handle mfh = afio::mapped_temp_inode().value();
+// On an extents based filing system, doesn't actually allocate any physical storage
+// but does map approximately 4Tb of all bits zero data into memory
+mfh.truncate(1000000000000ULL*sizeof(int));
+// Create a typed view of the one trillion integers
+afio::algorithm::mapped_view<int> one_trillion_int_array(mfh);
+// Write and read as you see fit, if you exceed physical RAM it'll be paged to disk
+one_trillion_int_array[0] = 5;
+one_trillion_int_array[999999999999ULL] = 6;
+\endcode
+
These compilers and OS are regularly tested:
- GCC 7.0 (Linux 4,x x64)
- clang 4.0 (Linux 4.x x64)
@@ -88,7 +104,7 @@ ctest -C Release -R afio_sl
| ✔ | ✔ | Universal native handle/fd abstraction instead of `void *`.
| ✔ | ✔ | Perfectly/Ideally low memory (de)allocation per op (usually none).
| ✔ | ✔ | noexcept API throughout returning error_code for failure instead of throwing exceptions.
-| ✔ | ✔ | AFIO v1 handle type split into hierarchy of types:<ol><li>handle - provides open, close, get path, clone, set/unset append only, change caching, characteristics<li>path_handle - a race free anchor to a subset of the filesystem<li>io_handle - adds synchronous scatter-gather i/o, byte range locking<li>file_handle - adds open/create file, get and set maximum extent<li>async_file_handle - adds asynchronous scatter-gather i/o</ol>
+| ✔ | ✔ | AFIO v1 handle type split into hierarchy of types:<ol><li>handle - provides open, close, get path, clone, set/unset append only, change caching, characteristics<li>fs_handle - handles with an inode number<li>path_handle - a race free anchor to a subset of the filesystem<li>directory_handle - enumerates the filesystem<li>io_handle - adds synchronous scatter-gather i/o, byte range locking<li>file_handle - adds open/create file, get and set maximum extent<li>async_file_handle - adds asynchronous scatter-gather i/o<li>mapped_file_handle - adds low latency memory mapped scatter-gather i/o</ol>
| ✔ | ✔ | Cancelable i/o (made possible thanks to dropping XP support).
| ✔ | ✔ | All shared_ptr usage removed as all use of multiple threads removed.
| ✔ | ✔ | Use of std::vector to transport scatter-gather sequences replaced with C++ 20 `span<>` borrowed views.
@@ -137,19 +153,19 @@ Todo:
| ✔ | ✔ | ✔ | Byte range shared/exclusive locking.
| ✔ | ✔ | ✔ | `shared_fs_mutex` shared/exclusive entities locking based on byte ranges
| ✔ | ✔ | ✔ | `shared_fs_mutex` shared/exclusive entities locking based on atomic append
-| | ✔ | ✔ | Memory mapped files and virtual memory management (`section_handle` and `map_handle`)
+| | ✔ | ✔ | Memory mapped files and virtual memory management (`section_handle`, `map_handle` and `mapped_file_handle`)
| ✔ | ✔ | ✔ | `shared_fs_mutex` shared/exclusive entities locking based on memory maps
| ✔ | ✔ | ✔ | Universal portable UTF-8 path views.
| | ✔ | ✔ | "Hole punching" and hole enumeration ported over from AFIO v1.
| | ✔ | ✔ | Directory handles and very fast directory enumeration ported over from AFIO v1.
| ✔ | ✔ | ✔ | `shared_fs_mutex` shared/exclusive entities locking based on safe byte ranges
+| | ✔ | ✔ | Set random or sequential i/o (prefetch).
Todo to reach feature parity with AFIO v1:
| NEW in v2 | Windows | POSIX | |
| --------- | --------| ----- | --- |
| | | | Hard links and symlinks.
-| | | | Set random or sequential i/o (prefetch).
| | | | BSD and OS X kqueues optimised `io_service`
Todo thereafter:
@@ -158,7 +174,7 @@ Todo thereafter:
| --------- | --------| ----- | --- |
| ✔ | | | Extended attributes support.
| ✔ | | | Coroutines TS integration for `async_file_handle`. Use example: https://gist.github.com/anonymous/a67ba4695c223a905ff108ed8b9a342f
-| ✔ | | | Linux KAIO support for native async `O_DIRECT` i/o
+| ✔ | | | Linux KAIO support for native non-blocking `O_DIRECT` i/o
| ✔ | | | Reliable directory hierarchy deletion algorithm.
| ✔ | | | Reliable directory hierarchy copy algorithm.
| ✔ | | | Reliable directory hierarchy update (two and three way) algorithm.