mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 22:15:23 +00:00
[trac3614] removed no longer used --with-shared-memory and dependencies
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
847. [build] fdupont
|
||||||
|
Removed unused configuration option --with-shared-memory
|
||||||
|
and associated files and variables.
|
||||||
|
(Trac #3614, git ...)
|
||||||
|
|
||||||
846. [bug] fdupont
|
846. [bug] fdupont
|
||||||
Fixed subdir-objects warnings from recent versions of autotools,
|
Fixed subdir-objects warnings from recent versions of autotools,
|
||||||
e.g., on Apple OSX.
|
e.g., on Apple OSX.
|
||||||
|
25
configure.ac
25
configure.ac
@@ -1041,31 +1041,6 @@ if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGP
|
|||||||
AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
|
AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
|
||||||
fi
|
fi
|
||||||
|
|
||||||
use_shared_memory=yes
|
|
||||||
AC_ARG_WITH(shared-memory,
|
|
||||||
AC_HELP_STRING([--with-shared-memory],
|
|
||||||
[Build with Boost shared memory support; for large scale authoritative DNS servers]),
|
|
||||||
[use_shared_memory=$withval])
|
|
||||||
if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes" -a "$want_dns" = "yes"; then
|
|
||||||
AC_MSG_ERROR([Boost shared memory does not compile on this system. If you don't need it (most normal users won't) build without it by rerunning this script with --without-shared-memory; using a different compiler or a different version of Boost may also help.])
|
|
||||||
fi
|
|
||||||
AM_CONDITIONAL([USE_SHARED_MEMORY], [test x$use_shared_memory = xyes])
|
|
||||||
if test "x$use_shared_memory" = "xyes"; then
|
|
||||||
AC_DEFINE(USE_SHARED_MEMORY, 1, [Define to 1 if shared memory support is enabled])
|
|
||||||
fi
|
|
||||||
AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
|
|
||||||
|
|
||||||
if test "$BOOST_OFFSET_PTR_OLD" = "yes" -a "$use_shared_memory" = "yes" -a "$want_dns" = "yes"; then
|
|
||||||
AC_MSG_ERROR([You're trying to compile against boost older than 1.48 with
|
|
||||||
shared memory. Older versions of boost have a bug which causes segfaults in
|
|
||||||
offset_ptr implementation when compiled by GCC with optimisations enabled.
|
|
||||||
See ticket no. 3025 for details.
|
|
||||||
|
|
||||||
Either update boost to newer version or use --without-shared-memory.
|
|
||||||
Note that most users likely don't need shared memory support.
|
|
||||||
])
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Add some default CPP flags needed for Boost, identified by the AX macro.
|
# Add some default CPP flags needed for Boost, identified by the AX macro.
|
||||||
CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
|
CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
|
||||||
|
|
||||||
|
@@ -23,20 +23,9 @@ dnl BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
|
|||||||
dnl error; otherwise set to "no"
|
dnl error; otherwise set to "no"
|
||||||
dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
|
dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
|
||||||
dnl build error; otherwise set to "no"
|
dnl build error; otherwise set to "no"
|
||||||
dnl BOOST_MAPPED_FILE_WOULDFAIL set to "yes" if managed_mapped_file would
|
|
||||||
dnl cause build failure; otherwise set to "no"
|
|
||||||
dnl BOOST_MAPPED_FILE_CXXFLAG set to the compiler flag that would need to
|
|
||||||
dnl compile managed_mapped_file (can be empty).
|
|
||||||
dnl It is of no use if "WOULDFAIL" is yes.
|
|
||||||
dnl BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
|
dnl BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
|
||||||
dnl cause build error; otherwise set to "no"
|
dnl cause build error; otherwise set to "no"
|
||||||
|
|
||||||
dnl BOOST_OFFSET_PTR_OLD set to "yes" if the version of boost is older than
|
|
||||||
dnl 1.48. Older versions of boost have a bug which
|
|
||||||
dnl causes segfaults in offset_ptr implementation when
|
|
||||||
dnl compiled by GCC with optimisations enabled.
|
|
||||||
dnl See ticket no. 3025 for details.
|
|
||||||
|
|
||||||
AC_DEFUN([AX_BOOST_FOR_KEA], [
|
AC_DEFUN([AX_BOOST_FOR_KEA], [
|
||||||
AC_LANG_SAVE
|
AC_LANG_SAVE
|
||||||
AC_LANG([C++])
|
AC_LANG([C++])
|
||||||
@@ -119,56 +108,9 @@ if test "X$GXX" = "Xyes"; then
|
|||||||
BOOST_NUMERIC_CAST_WOULDFAIL=yes])
|
BOOST_NUMERIC_CAST_WOULDFAIL=yes])
|
||||||
|
|
||||||
CXXFLAGS="$CXXFLAGS_SAVED"
|
CXXFLAGS="$CXXFLAGS_SAVED"
|
||||||
|
|
||||||
AC_MSG_CHECKING([Boost rbtree is old])
|
|
||||||
AC_TRY_COMPILE([
|
|
||||||
#include <boost/version.hpp>
|
|
||||||
#if BOOST_VERSION < 104800
|
|
||||||
#error Too old
|
|
||||||
#endif
|
|
||||||
],,[AC_MSG_RESULT(no)
|
|
||||||
BOOST_OFFSET_PTR_OLD=no
|
|
||||||
],[AC_MSG_RESULT(yes)
|
|
||||||
BOOST_OFFSET_PTR_OLD=yes])
|
|
||||||
else
|
else
|
||||||
# This doesn't matter for non-g++
|
# This doesn't matter for non-g++
|
||||||
BOOST_NUMERIC_CAST_WOULDFAIL=no
|
BOOST_NUMERIC_CAST_WOULDFAIL=no
|
||||||
BOOST_OFFSET_PTR_OLD=no
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Boost interprocess::managed_mapped_file is highly system dependent and
|
|
||||||
# can cause many portability issues. We are going to check if it could
|
|
||||||
# compile at all, possibly with being lenient about compiler warnings.
|
|
||||||
BOOST_MAPPED_FILE_WOULDFAIL=yes
|
|
||||||
BOOST_MAPPED_FILE_CXXFLAG=
|
|
||||||
CXXFLAGS_SAVED="$CXXFLAGS"
|
|
||||||
try_flags="no"
|
|
||||||
if test "X$GXX" = "Xyes"; then
|
|
||||||
CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS -Wall -Wextra -Werror"
|
|
||||||
try_flags="$try_flags -Wno-error"
|
|
||||||
fi
|
|
||||||
|
|
||||||
AC_MSG_CHECKING([Boost managed_mapped_file compiles])
|
|
||||||
CXXFLAGS_SAVED2="$CXXFLAGS"
|
|
||||||
for flag in $try_flags; do
|
|
||||||
if test "$flag" != no; then
|
|
||||||
BOOST_MAPPED_FILE_CXXFLAG="$flag"
|
|
||||||
fi
|
|
||||||
CXXFLAGS="$CXXFLAGS $CLANG_CXXFLAGS $BOOST_MAPPED_FILE_CXXFLAG"
|
|
||||||
AC_TRY_COMPILE([
|
|
||||||
#include <boost/interprocess/managed_mapped_file.hpp>
|
|
||||||
],[
|
|
||||||
return (boost::interprocess::managed_mapped_file().all_memory_deallocated());
|
|
||||||
],[AC_MSG_RESULT([yes, with $flag flag])
|
|
||||||
BOOST_MAPPED_FILE_WOULDFAIL=no
|
|
||||||
break
|
|
||||||
],[])
|
|
||||||
|
|
||||||
CXXFLAGS="$CXXFLAGS_SAVED2"
|
|
||||||
done
|
|
||||||
|
|
||||||
if test $BOOST_MAPPED_FILE_WOULDFAIL = yes; then
|
|
||||||
AC_MSG_RESULT(no)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# BOOST_STATIC_ASSERT in versions below Boost 1.54.0 is known to result
|
# BOOST_STATIC_ASSERT in versions below Boost 1.54.0 is known to result
|
||||||
|
@@ -7,18 +7,6 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
|
|||||||
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exceptions
|
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exceptions
|
||||||
AM_CPPFLAGS += $(BOOST_INCLUDES)
|
AM_CPPFLAGS += $(BOOST_INCLUDES)
|
||||||
AM_CXXFLAGS = $(KEA_CXXFLAGS)
|
AM_CXXFLAGS = $(KEA_CXXFLAGS)
|
||||||
# If we use the shared-memory support, corresponding Boost library may
|
|
||||||
# cause build failures especially if it's strict about warnings. We've
|
|
||||||
# detected it in ./configure and set BOOST_MAPPED_FILE_CXXFLAG to be more
|
|
||||||
# lenient as necessary (specifically, when set it'd usually suppress -Werror).
|
|
||||||
# This is a module wide setting, and has a possible bad side effect of hiding
|
|
||||||
# issues in other files, but making it per-file seems to be too costly.
|
|
||||||
# So we begin with the wider setting. If the side effect turns out to be too
|
|
||||||
# harmful, we'll consider other measure, e.g, moving the related files into
|
|
||||||
# a subdirectory.
|
|
||||||
if USE_SHARED_MEMORY
|
|
||||||
AM_CXXFLAGS += $(BOOST_MAPPED_FILE_CXXFLAG)
|
|
||||||
endif
|
|
||||||
|
|
||||||
lib_LTLIBRARIES = libkea-util.la
|
lib_LTLIBRARIES = libkea-util.la
|
||||||
libkea_util_la_SOURCES = csv_file.h csv_file.cc
|
libkea_util_la_SOURCES = csv_file.h csv_file.cc
|
||||||
@@ -29,9 +17,6 @@ libkea_util_la_SOURCES += buffer.h io_utilities.h
|
|||||||
libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
|
libkea_util_la_SOURCES += time_utilities.h time_utilities.cc
|
||||||
libkea_util_la_SOURCES += memory_segment.h
|
libkea_util_la_SOURCES += memory_segment.h
|
||||||
libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
|
libkea_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
|
||||||
if USE_SHARED_MEMORY
|
|
||||||
libkea_util_la_SOURCES += memory_segment_mapped.h memory_segment_mapped.cc
|
|
||||||
endif
|
|
||||||
libkea_util_la_SOURCES += optional_value.h
|
libkea_util_la_SOURCES += optional_value.h
|
||||||
libkea_util_la_SOURCES += range_utilities.h
|
libkea_util_la_SOURCES += range_utilities.h
|
||||||
libkea_util_la_SOURCES += signal_set.cc signal_set.h
|
libkea_util_la_SOURCES += signal_set.cc signal_set.h
|
||||||
|
@@ -1,466 +0,0 @@
|
|||||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
#include <util/memory_segment_mapped.h>
|
|
||||||
#include <util/unittests/check_valgrind.h>
|
|
||||||
|
|
||||||
#include <exceptions/exceptions.h>
|
|
||||||
|
|
||||||
#include <boost/scoped_ptr.hpp>
|
|
||||||
#include <boost/interprocess/exceptions.hpp>
|
|
||||||
#include <boost/interprocess/managed_mapped_file.hpp>
|
|
||||||
#include <boost/interprocess/offset_ptr.hpp>
|
|
||||||
#include <boost/interprocess/mapped_region.hpp>
|
|
||||||
#include <boost/interprocess/sync/file_lock.hpp>
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <string>
|
|
||||||
#include <new>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// boost::interprocess namespace is big and can cause unexpected import
|
|
||||||
// (e.g., it has "read_only"), so it's safer to be specific for shortcuts.
|
|
||||||
using boost::interprocess::basic_managed_mapped_file;
|
|
||||||
using boost::interprocess::rbtree_best_fit;
|
|
||||||
using boost::interprocess::null_mutex_family;
|
|
||||||
using boost::interprocess::iset_index;
|
|
||||||
using boost::interprocess::create_only_t;
|
|
||||||
using boost::interprocess::create_only;
|
|
||||||
using boost::interprocess::open_or_create_t;
|
|
||||||
using boost::interprocess::open_or_create;
|
|
||||||
using boost::interprocess::open_read_only;
|
|
||||||
using boost::interprocess::open_only;
|
|
||||||
using boost::interprocess::offset_ptr;
|
|
||||||
|
|
||||||
namespace isc {
|
|
||||||
namespace util {
|
|
||||||
|
|
||||||
namespace { // unnamed namespace
|
|
||||||
|
|
||||||
const char* const RESERVED_NAMED_ADDRESS_STORAGE_NAME =
|
|
||||||
"_RESERVED_NAMED_ADDRESS_STORAGE";
|
|
||||||
|
|
||||||
} // end of unnamed namespace
|
|
||||||
|
|
||||||
|
|
||||||
// Definition of class static constant so it can be referenced by address
|
|
||||||
// or reference.
|
|
||||||
const size_t MemorySegmentMapped::INITIAL_SIZE;
|
|
||||||
|
|
||||||
// We customize managed_mapped_file to make it completely lock free. In our
|
|
||||||
// usage the application (or the system of applications) is expected to ensure
|
|
||||||
// there's at most one writer process or concurrent writing the shared memory
|
|
||||||
// segment is protected at a higher level. Using the null mutex is mainly for
|
|
||||||
// eliminating unnecessary dependency; the default version would require
|
|
||||||
// (probably depending on the system) Pthread library that is actually not
|
|
||||||
// needed and could cause various build time troubles.
|
|
||||||
typedef basic_managed_mapped_file<char,
|
|
||||||
rbtree_best_fit<null_mutex_family>,
|
|
||||||
iset_index> BaseSegment;
|
|
||||||
|
|
||||||
struct MemorySegmentMapped::Impl {
|
|
||||||
// Constructor for create-only (and read-write) mode. this case is
|
|
||||||
// tricky because we want to remove any existing file but we also want
|
|
||||||
// to detect possible conflict with other readers or writers using
|
|
||||||
// file lock.
|
|
||||||
Impl(const std::string& filename, create_only_t, size_t initial_size) :
|
|
||||||
read_only_(false), filename_(filename)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// First, try opening it in boost create_only mode; it fails if
|
|
||||||
// the file exists (among other reasons).
|
|
||||||
base_sgmt_.reset(new BaseSegment(create_only, filename.c_str(),
|
|
||||||
initial_size));
|
|
||||||
} catch (const boost::interprocess::interprocess_exception& ex) {
|
|
||||||
// We assume this is because the file exists; otherwise creating
|
|
||||||
// file_lock would fail with interprocess_exception, and that's
|
|
||||||
// what we want here (we wouldn't be able to create a segment
|
|
||||||
// anyway).
|
|
||||||
lock_.reset(new boost::interprocess::file_lock(filename.c_str()));
|
|
||||||
|
|
||||||
// Confirm there's no other reader or writer, and then release
|
|
||||||
// the lock before we remove the file; there's a chance of race
|
|
||||||
// here, but this check doesn't intend to guarantee 100% safety
|
|
||||||
// and so it should be okay.
|
|
||||||
checkWriter();
|
|
||||||
lock_.reset();
|
|
||||||
|
|
||||||
// now remove the file (if it happens to have been delete, this
|
|
||||||
// will be no-op), then re-open it with create_only. this time
|
|
||||||
// it should succeed, and if it fails again, that's fatal for this
|
|
||||||
// constructor.
|
|
||||||
boost::interprocess::file_mapping::remove(filename.c_str());
|
|
||||||
base_sgmt_.reset(new BaseSegment(create_only, filename.c_str(),
|
|
||||||
initial_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
// confirm there's no other user and there won't either.
|
|
||||||
lock_.reset(new boost::interprocess::file_lock(filename.c_str()));
|
|
||||||
checkWriter();
|
|
||||||
reserveMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor for open-or-write (and read-write) mode
|
|
||||||
Impl(const std::string& filename, open_or_create_t, size_t initial_size) :
|
|
||||||
read_only_(false), filename_(filename),
|
|
||||||
base_sgmt_(new BaseSegment(open_or_create, filename.c_str(),
|
|
||||||
initial_size)),
|
|
||||||
lock_(new boost::interprocess::file_lock(filename.c_str()))
|
|
||||||
{
|
|
||||||
checkWriter();
|
|
||||||
reserveMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor for existing segment, either read-only or read-write
|
|
||||||
Impl(const std::string& filename, bool read_only) :
|
|
||||||
read_only_(read_only), filename_(filename),
|
|
||||||
base_sgmt_(read_only_ ?
|
|
||||||
new BaseSegment(open_read_only, filename.c_str()) :
|
|
||||||
new BaseSegment(open_only, filename.c_str())),
|
|
||||||
lock_(new boost::interprocess::file_lock(filename.c_str()))
|
|
||||||
{
|
|
||||||
if (read_only_) {
|
|
||||||
checkReader();
|
|
||||||
} else {
|
|
||||||
checkWriter();
|
|
||||||
}
|
|
||||||
reserveMemory();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reserveMemory(bool no_grow = false) {
|
|
||||||
if (!read_only_) {
|
|
||||||
// Reserve a named address for use during
|
|
||||||
// setNamedAddress(). Though this will almost always succeed
|
|
||||||
// on the first try during construction, it may require
|
|
||||||
// multiple attempts later during a call from
|
|
||||||
// allMemoryDeallocated() when the segment has been in use
|
|
||||||
// for a while.
|
|
||||||
while (true) {
|
|
||||||
const offset_ptr<void>* reserved_storage =
|
|
||||||
base_sgmt_->find_or_construct<offset_ptr<void> >(
|
|
||||||
RESERVED_NAMED_ADDRESS_STORAGE_NAME, std::nothrow)();
|
|
||||||
|
|
||||||
if (reserved_storage) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assert(!no_grow);
|
|
||||||
|
|
||||||
growSegment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void freeReservedMemory() {
|
|
||||||
if (!read_only_) {
|
|
||||||
const bool deleted = base_sgmt_->destroy<offset_ptr<void> >
|
|
||||||
(RESERVED_NAMED_ADDRESS_STORAGE_NAME);
|
|
||||||
assert(deleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal helper to grow the underlying mapped segment.
|
|
||||||
void growSegment() {
|
|
||||||
// We first need to unmap it before calling grow().
|
|
||||||
const size_t prev_size = base_sgmt_->get_size();
|
|
||||||
base_sgmt_->flush();
|
|
||||||
base_sgmt_.reset();
|
|
||||||
|
|
||||||
// Double the segment size. In theory, this process could repeat
|
|
||||||
// so many times, counting to "infinity", and new_size eventually
|
|
||||||
// overflows. That would cause a harsh disruption or unexpected
|
|
||||||
// behavior. But we basically assume grow() would fail before this
|
|
||||||
// happens, so we assert it shouldn't happen.
|
|
||||||
const size_t new_size = prev_size * 2;
|
|
||||||
assert(new_size > prev_size);
|
|
||||||
|
|
||||||
const bool grown = BaseSegment::grow(filename_.c_str(),
|
|
||||||
new_size - prev_size);
|
|
||||||
|
|
||||||
// Remap the file, whether or not grow() succeeded. this should
|
|
||||||
// normally succeed(*), but it's not 100% guaranteed. We abort
|
|
||||||
// if it fails (see the method description in the header file).
|
|
||||||
// (*) Although it's not formally documented, the implementation
|
|
||||||
// of grow() seems to provide strong guarantee, i.e, if it fails
|
|
||||||
// the underlying file can be used with the previous size.
|
|
||||||
try {
|
|
||||||
base_sgmt_.reset(new BaseSegment(open_only, filename_.c_str()));
|
|
||||||
} catch (...) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
if (!grown) {
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember if the segment is opened read-only or not
|
|
||||||
const bool read_only_;
|
|
||||||
|
|
||||||
// mapped file; remember it in case we need to grow it.
|
|
||||||
const std::string filename_;
|
|
||||||
|
|
||||||
// actual Boost implementation of mapped segment.
|
|
||||||
boost::scoped_ptr<BaseSegment> base_sgmt_;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// helper methods and member to detect any reader-writer conflict at
|
|
||||||
// the time of construction using an advisory file lock. The lock will
|
|
||||||
// be held throughout the lifetime of the object and will be released
|
|
||||||
// automatically.
|
|
||||||
|
|
||||||
void checkReader() {
|
|
||||||
if (!lock_->try_lock_sharable()) {
|
|
||||||
isc_throw(MemorySegmentOpenError,
|
|
||||||
"mapped memory segment can't be opened as read-only "
|
|
||||||
"with a writer process");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkWriter() {
|
|
||||||
if (!lock_->try_lock()) {
|
|
||||||
isc_throw(MemorySegmentOpenError,
|
|
||||||
"mapped memory segment can't be opened as read-write "
|
|
||||||
"with other reader or writer processes");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::scoped_ptr<boost::interprocess::file_lock> lock_;
|
|
||||||
};
|
|
||||||
|
|
||||||
MemorySegmentMapped::MemorySegmentMapped(const std::string& filename) :
|
|
||||||
impl_(NULL)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
impl_ = new Impl(filename, true);
|
|
||||||
} catch (const boost::interprocess::interprocess_exception& ex) {
|
|
||||||
isc_throw(MemorySegmentOpenError,
|
|
||||||
"failed to open mapped memory segment for " << filename
|
|
||||||
<< ": " << ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MemorySegmentMapped::MemorySegmentMapped(const std::string& filename,
|
|
||||||
OpenMode mode, size_t initial_size) :
|
|
||||||
impl_(NULL)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
switch (mode) {
|
|
||||||
case OPEN_FOR_WRITE:
|
|
||||||
impl_ = new Impl(filename, false);
|
|
||||||
break;
|
|
||||||
case OPEN_OR_CREATE:
|
|
||||||
impl_ = new Impl(filename, open_or_create, initial_size);
|
|
||||||
break;
|
|
||||||
case CREATE_ONLY:
|
|
||||||
impl_ = new Impl(filename, create_only, initial_size);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
isc_throw(InvalidParameter,
|
|
||||||
"invalid open mode for MemorySegmentMapped: " << mode);
|
|
||||||
}
|
|
||||||
} catch (const boost::interprocess::interprocess_exception& ex) {
|
|
||||||
isc_throw(MemorySegmentOpenError,
|
|
||||||
"failed to open mapped memory segment for " << filename
|
|
||||||
<< ": " << ex.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MemorySegmentMapped::~MemorySegmentMapped() {
|
|
||||||
if (impl_->base_sgmt_ && !impl_->read_only_) {
|
|
||||||
impl_->freeReservedMemory();
|
|
||||||
impl_->base_sgmt_->flush(); // note: this is exception free
|
|
||||||
}
|
|
||||||
delete impl_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void*
|
|
||||||
MemorySegmentMapped::allocate(size_t size) {
|
|
||||||
if (impl_->read_only_) {
|
|
||||||
isc_throw(MemorySegmentError, "allocate attempt on read-only segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// We explicitly check the free memory size; it appears
|
|
||||||
// managed_mapped_file::allocate() could incorrectly return a seemingly
|
|
||||||
// valid pointer for some very large requested size.
|
|
||||||
if (impl_->base_sgmt_->get_free_memory() >= size) {
|
|
||||||
void* ptr = impl_->base_sgmt_->allocate(size, std::nothrow);
|
|
||||||
if (ptr) {
|
|
||||||
return (ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grow the mapped segment doubling the size until we have sufficient
|
|
||||||
// free memory in the revised segment for the requested size.
|
|
||||||
do {
|
|
||||||
impl_->growSegment();
|
|
||||||
} while (impl_->base_sgmt_->get_free_memory() < size);
|
|
||||||
isc_throw(MemorySegmentGrown, "mapped memory segment grown, size: "
|
|
||||||
<< impl_->base_sgmt_->get_size() << ", free size: "
|
|
||||||
<< impl_->base_sgmt_->get_free_memory());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MemorySegmentMapped::deallocate(void* ptr, size_t) {
|
|
||||||
if (impl_->read_only_) {
|
|
||||||
isc_throw(MemorySegmentError,
|
|
||||||
"deallocate attempt on read-only segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// the underlying deallocate() would deal with the case where ptr == NULL,
|
|
||||||
// but it's an undocumented behavior, so we handle it ourselves for safety.
|
|
||||||
if (!ptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_->base_sgmt_->deallocate(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MemorySegmentMapped::allMemoryDeallocated() const {
|
|
||||||
// This method is not technically const, but it reserves the
|
|
||||||
// const-ness property. In case of exceptions, we abort here. (See
|
|
||||||
// ticket #2850 for additional commentary.)
|
|
||||||
try {
|
|
||||||
impl_->freeReservedMemory();
|
|
||||||
const bool result = impl_->base_sgmt_->all_memory_deallocated();
|
|
||||||
// reserveMemory() should succeed now as the memory was already
|
|
||||||
// allocated, so we set no_grow to true.
|
|
||||||
impl_->reserveMemory(true);
|
|
||||||
return (result);
|
|
||||||
} catch (...) {
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MemorySegment::NamedAddressResult
|
|
||||||
MemorySegmentMapped::getNamedAddressImpl(const char* name) const {
|
|
||||||
offset_ptr<void>* storage =
|
|
||||||
impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
|
|
||||||
if (storage) {
|
|
||||||
return (NamedAddressResult(true, storage->get()));
|
|
||||||
}
|
|
||||||
return (NamedAddressResult(false, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MemorySegmentMapped::setNamedAddressImpl(const char* name, void* addr) {
|
|
||||||
if (impl_->read_only_) {
|
|
||||||
isc_throw(MemorySegmentError, "setNamedAddress on read-only segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addr && !impl_->base_sgmt_->belongs_to_segment(addr)) {
|
|
||||||
isc_throw(MemorySegmentError, "address is out of segment: " << addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporarily save the passed addr into pre-allocated offset_ptr in
|
|
||||||
// case there are any relocations caused by allocations.
|
|
||||||
offset_ptr<void>* reserved_storage =
|
|
||||||
impl_->base_sgmt_->find<offset_ptr<void> >(
|
|
||||||
RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
|
|
||||||
assert(reserved_storage);
|
|
||||||
*reserved_storage = addr;
|
|
||||||
|
|
||||||
bool grown = false;
|
|
||||||
while (true) {
|
|
||||||
offset_ptr<void>* storage =
|
|
||||||
impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
|
|
||||||
name, std::nothrow)();
|
|
||||||
if (storage) {
|
|
||||||
// Move the address from saved offset_ptr into the
|
|
||||||
// newly-allocated storage.
|
|
||||||
reserved_storage =
|
|
||||||
impl_->base_sgmt_->find<offset_ptr<void> >(
|
|
||||||
RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
|
|
||||||
assert(reserved_storage);
|
|
||||||
*storage = *reserved_storage;
|
|
||||||
return (grown);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_->growSegment();
|
|
||||||
grown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MemorySegmentMapped::clearNamedAddressImpl(const char* name) {
|
|
||||||
if (impl_->read_only_) {
|
|
||||||
isc_throw(MemorySegmentError,
|
|
||||||
"clearNamedAddress on read-only segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (impl_->base_sgmt_->destroy<offset_ptr<void> >(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MemorySegmentMapped::shrinkToFit() {
|
|
||||||
if (impl_->read_only_) {
|
|
||||||
isc_throw(MemorySegmentError, "shrinkToFit on read-only segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
// It appears an assertion failure is triggered within Boost if the size
|
|
||||||
// is too small (happening if shrink_to_fit() is called twice without
|
|
||||||
// allocating any memory from the shrunk segment). To work this around
|
|
||||||
// we'll make it no-op if the size is already reasonably small.
|
|
||||||
// Using INITIAL_SIZE is not 100% reliable as it's irrelevant to the
|
|
||||||
// internal constraint of the Boost implementation. But, in practice,
|
|
||||||
// it should be sufficiently large and safe.
|
|
||||||
if (getSize() < INITIAL_SIZE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, unmap the underlying file.
|
|
||||||
impl_->base_sgmt_->flush();
|
|
||||||
impl_->base_sgmt_.reset();
|
|
||||||
|
|
||||||
BaseSegment::shrink_to_fit(impl_->filename_.c_str());
|
|
||||||
try {
|
|
||||||
// Remap the shrunk file; this should succeed, but it's not 100%
|
|
||||||
// guaranteed. If it fails we treat it as if we fail to create
|
|
||||||
// the new segment. Note that this is different from the case where
|
|
||||||
// reset() after grow() fails. While the same argument can apply
|
|
||||||
// in theory, it should be less likely that other methods will be
|
|
||||||
// called after shrinkToFit() (and the destructor can still be called
|
|
||||||
// safely), so we give the application an opportunity to handle the
|
|
||||||
// case as gracefully as possible.
|
|
||||||
impl_->base_sgmt_.reset(
|
|
||||||
new BaseSegment(open_only, impl_->filename_.c_str()));
|
|
||||||
} catch (const boost::interprocess::interprocess_exception& ex) {
|
|
||||||
isc_throw(MemorySegmentError,
|
|
||||||
"remap after shrink failed; segment is now unusable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
MemorySegmentMapped::getSize() const {
|
|
||||||
return (impl_->base_sgmt_->get_size());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
MemorySegmentMapped::getCheckSum() const {
|
|
||||||
const size_t pagesize =
|
|
||||||
boost::interprocess::mapped_region::get_page_size();
|
|
||||||
const uint8_t* const cp_begin = static_cast<const uint8_t*>(
|
|
||||||
impl_->base_sgmt_->get_address());
|
|
||||||
const uint8_t* const cp_end = cp_begin + impl_->base_sgmt_->get_size();
|
|
||||||
|
|
||||||
size_t sum = 0;
|
|
||||||
for (const uint8_t* cp = cp_begin; cp < cp_end; cp += pagesize) {
|
|
||||||
sum += *cp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace util
|
|
||||||
} // namespace isc
|
|
@@ -1,268 +0,0 @@
|
|||||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
#ifndef MEMORY_SEGMENT_MAPPED_H
|
|
||||||
#define MEMORY_SEGMENT_MAPPED_H
|
|
||||||
|
|
||||||
#include <util/memory_segment.h>
|
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace isc {
|
|
||||||
namespace util {
|
|
||||||
|
|
||||||
/// \brief Mapped-file based Memory Segment class.
|
|
||||||
///
|
|
||||||
/// This implementation of \c MemorySegment uses a concrete file to be mapped
|
|
||||||
/// into memory. Multiple processes can share the same mapped memory image.
|
|
||||||
///
|
|
||||||
/// This class provides two operation modes: read-only and read-write.
|
|
||||||
/// A \c MemorySegmentMapped object in the read-only mode cannot modify the
|
|
||||||
/// mapped memory image or other internal maintenance data of the object;
|
|
||||||
/// In the read-write mode the object can allocate or deallocate memory
|
|
||||||
/// from the mapped image, and the owner process can change the content.
|
|
||||||
///
|
|
||||||
/// Multiple processes can open multiple segments for the same file in
|
|
||||||
/// read-only mode at the same time. But there shouldn't be more than
|
|
||||||
/// one process that opens segments for the same file in read-write mode
|
|
||||||
/// at the same time. Likewise, if one process opens a segment for a
|
|
||||||
/// file in read-write mode, there shouldn't be any other process that
|
|
||||||
/// opens a segment for the file in read-only mode. If one or more
|
|
||||||
/// processes open segments for a file in read-only mode, there
|
|
||||||
/// shouldn't be any other process that opens a segment for the file in
|
|
||||||
/// read-write mode. This class tries to detect any violation of this
|
|
||||||
/// restriction, but this does not intend to provide 100% safety. It's
|
|
||||||
/// generally the user's responsibility to ensure this condition.
|
|
||||||
///
|
|
||||||
/// The same restriction applies within the single process, whether
|
|
||||||
/// multi-threaded or not: a process shouldn't open read-only and read-write
|
|
||||||
/// (or multiple read-write) segments for the same file. The violation
|
|
||||||
/// detection mentioned above may or may not work in such cases due to
|
|
||||||
/// limitation of the underlying API. It's completely user's responsibility
|
|
||||||
/// to prevent this from happening. A single process may open multiple
|
|
||||||
/// segments in read-only mode for the same file, but that shouldn't be
|
|
||||||
/// necessary in practice; since it's read-only there wouldn't be a reason
|
|
||||||
/// to have a redundant copy.
|
|
||||||
class MemorySegmentMapped : boost::noncopyable, public MemorySegment {
|
|
||||||
public:
|
|
||||||
/// \brief The default value of the mapped file size when newly created.
|
|
||||||
///
|
|
||||||
/// Its value, 32KB, is an arbitrary choice, but considered to be
|
|
||||||
/// sufficiently but not too large.
|
|
||||||
static const size_t INITIAL_SIZE = 32768;
|
|
||||||
|
|
||||||
/// \brief Open modes of \c MemorySegmentMapped.
|
|
||||||
///
|
|
||||||
/// These modes matter only for \c MemorySegmentMapped to be opened
|
|
||||||
/// in read-write mode, and specify further details of open operation.
|
|
||||||
enum OpenMode {
|
|
||||||
OPEN_FOR_WRITE = 0, ///< Open only. File must exist.
|
|
||||||
OPEN_OR_CREATE, ///< If file doesn't exist it's created.
|
|
||||||
CREATE_ONLY ///< New file is created; existing one will be removed.
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Constructor in the read-only mode.
|
|
||||||
///
|
|
||||||
/// This constructor will map the content of the given file into memory
|
|
||||||
/// in read-only mode; the resulting memory segment object cannot
|
|
||||||
/// be used with methods that would require the mapped memory (see method
|
|
||||||
/// descriptions). Also, if the application tries to modify memory in
|
|
||||||
/// the segment, it will make the application crash.
|
|
||||||
///
|
|
||||||
/// The file must have been created by the other version of the
|
|
||||||
/// constructor beforehand and must be readable for the process
|
|
||||||
/// constructing this object. Otherwise \c MemorySegmentOpenError
|
|
||||||
/// exception will be thrown.
|
|
||||||
///
|
|
||||||
/// \throw MemorySegmentOpenError The given file does not exist, is not
|
|
||||||
/// readable, or not valid mappable segment. Or there is another process
|
|
||||||
/// that has already opened a segment for the file.
|
|
||||||
/// \throw std::bad_alloc (rare case) internal resource allocation
|
|
||||||
/// failure.
|
|
||||||
///
|
|
||||||
/// \param filename The file name to be mapped to memory.
|
|
||||||
MemorySegmentMapped(const std::string& filename);
|
|
||||||
|
|
||||||
/// \brief Constructor in the read-write mode.
|
|
||||||
///
|
|
||||||
/// This is similar to the read-only version of the constructor, but
|
|
||||||
/// does not have the restrictions that the read-only version has.
|
|
||||||
///
|
|
||||||
/// The \c mode parameter specifies further details of how the segment
|
|
||||||
/// should be opened.
|
|
||||||
/// - OPEN_FOR_WRITE: this is open-only mode. The file must exist,
|
|
||||||
/// and it will be opened without any initial modification.
|
|
||||||
/// - OPEN_OR_CREATE: similar to OPEN_FOR_WRITE, but if the file does not
|
|
||||||
/// exist, a new one will be created. An existing file will be used
|
|
||||||
/// any initial modification.
|
|
||||||
/// - CREATE_ONLY: a new file (of the given file name) will be created;
|
|
||||||
/// any existing file of the same name will be removed.
|
|
||||||
///
|
|
||||||
/// If OPEN_FOR_WRITE is specified, the specified file must exist
|
|
||||||
/// and be writable, and have been previously initialized by this
|
|
||||||
/// version of constructor either with OPEN_OR_CREATE or CREATE_ONLY.
|
|
||||||
/// If the mode is OPEN_OR_CREATE or CREATE_ONLY, and the file needs
|
|
||||||
/// to be created, then this method tries to create a new file of the
|
|
||||||
/// name and build internal data on it so that the file will be mappable
|
|
||||||
/// by this class object. If any of these conditions is not met, or
|
|
||||||
/// create or initialization fails, \c MemorySegmentOpenError exception
|
|
||||||
/// will be thrown.
|
|
||||||
///
|
|
||||||
/// This constructor also throws \c MemorySegmentOpenError when it
|
|
||||||
/// detects violation of the restriction on the mixed open of read-only
|
|
||||||
/// and read-write mode (see the class description).
|
|
||||||
///
|
|
||||||
/// When initial_size is specified but is too small (including a value of
|
|
||||||
/// 0), the underlying Boost library will reject it, and this constructor
|
|
||||||
/// throws \c MemorySegmentOpenError exception. The Boost documentation
|
|
||||||
/// does not specify how large it should be, but the default
|
|
||||||
/// \c INITIAL_SIZE should be sufficiently large in practice.
|
|
||||||
///
|
|
||||||
/// \throw MemorySegmentOpenError see the description.
|
|
||||||
///
|
|
||||||
/// \param filename The file name to be mapped to memory.
|
|
||||||
/// \param mode Open mode (see the description).
|
|
||||||
/// \param initial_size Specifies the size of the newly created file;
|
|
||||||
/// ignored if \c mode is OPEN_FOR_WRITE.
|
|
||||||
MemorySegmentMapped(const std::string& filename, OpenMode mode,
|
|
||||||
size_t initial_size = INITIAL_SIZE);
|
|
||||||
|
|
||||||
/// \brief Destructor.
|
|
||||||
///
|
|
||||||
/// If the object was constructed in the read-write mode and the underlying
|
|
||||||
/// memory segment wasn't broken due to an exceptional event, the
|
|
||||||
/// destructor ensures the content of the mapped memory is written back to
|
|
||||||
/// the corresponding file.
|
|
||||||
virtual ~MemorySegmentMapped();
|
|
||||||
|
|
||||||
/// \brief Allocate/acquire a segment of memory.
|
|
||||||
///
|
|
||||||
/// This version can throw \c MemorySegmentGrown. Furthermore, there is
|
|
||||||
/// a very small chance that the object loses its integrity and can't be
|
|
||||||
/// usable in the case where \c MemorySegmentGrown would be thrown.
|
|
||||||
/// In this case, throwing a different exception wouldn't help, because
|
|
||||||
/// an application trying to provide exception safety might then call
|
|
||||||
/// deallocate() or named address APIs on this object, which would simply
|
|
||||||
/// cause a crash. So, while suboptimal, this method just aborts the
|
|
||||||
/// program in this case, indicating there's no hope to shutdown cleanly.
|
|
||||||
///
|
|
||||||
/// This method cannot be called if the segment object is created in the
|
|
||||||
/// read-only mode; in that case MemorySegmentError will be thrown.
|
|
||||||
virtual void* allocate(size_t size);
|
|
||||||
|
|
||||||
/// \brief Deallocate/release a segment of memory.
|
|
||||||
///
|
|
||||||
/// This implementation does not check the validity of \c size, because
|
|
||||||
/// if this segment object was constructed for an existing file to map,
|
|
||||||
/// the underlying segment may already contain allocated regions, so
|
|
||||||
/// this object cannot reliably detect whether it's safe to deallocate
|
|
||||||
/// the given size of memory from the underlying segment.
|
|
||||||
///
|
|
||||||
/// Parameter \c ptr must point to an address that was returned by a
|
|
||||||
/// prior call to \c allocate() of this segment object, and there should
|
|
||||||
/// not be a \c MemorySegmentGrown exception thrown from \c allocate()
|
|
||||||
/// since then; if it was thrown the corresponding address must have been
|
|
||||||
/// adjusted some way; e.g., by re-fetching the latest mapped address
|
|
||||||
/// via \c getNamedAddress().
|
|
||||||
///
|
|
||||||
/// This method cannot be called if the segment object is created in the
|
|
||||||
/// read-only mode; in that case MemorySegmentError will be thrown.
|
|
||||||
virtual void deallocate(void* ptr, size_t size);
|
|
||||||
|
|
||||||
virtual bool allMemoryDeallocated() const;
|
|
||||||
|
|
||||||
/// \brief Mapped segment version of setNamedAddress.
|
|
||||||
///
|
|
||||||
/// This implementation detects if \c addr is invalid (see the base class
|
|
||||||
/// description) and throws \c MemorySegmentError in that case.
|
|
||||||
///
|
|
||||||
/// This version of method should normally return false. However,
|
|
||||||
/// it internally allocates memory in the segment for the name and
|
|
||||||
/// address to be stored, which can require segment extension, just like
|
|
||||||
/// allocate(). So it's possible to return true unlike
|
|
||||||
/// \c MemorySegmentLocal version of the method.
|
|
||||||
///
|
|
||||||
/// This method cannot be called if the segment object is created in the
|
|
||||||
/// read-only mode; in that case MemorySegmentError will be thrown.
|
|
||||||
virtual bool setNamedAddressImpl(const char* name, void* addr);
|
|
||||||
|
|
||||||
/// \brief Mapped segment version of getNamedAddress.
|
|
||||||
///
|
|
||||||
/// This version never throws.
|
|
||||||
virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
|
|
||||||
|
|
||||||
/// \brief Mapped segment version of clearNamedAddress.
|
|
||||||
///
|
|
||||||
/// This method cannot be called if the segment object is created in the
|
|
||||||
/// read-only mode; in that case MemorySegmentError will be thrown.
|
|
||||||
virtual bool clearNamedAddressImpl(const char* name);
|
|
||||||
|
|
||||||
/// \brief Shrink the underlying mapped segment to actually used size.
|
|
||||||
///
|
|
||||||
/// When a large amount of memory is allocated and then deallocated
|
|
||||||
/// from the segment, this method can be used to keep the resulting
|
|
||||||
/// segment at a reasonable size.
|
|
||||||
///
|
|
||||||
/// This method works by a best-effort basis, and does not guarantee
|
|
||||||
/// any specific result.
|
|
||||||
///
|
|
||||||
/// This method is generally expected to be failure-free, but it's still
|
|
||||||
/// possible to fail. For example, the underlying file may not be writable
|
|
||||||
/// at the time of shrink attempt; it also tries to remap the shrunk
|
|
||||||
/// segment internally, and there's a small chance it could fail.
|
|
||||||
/// In such a case it throws \c MemorySegmentError. If it's thrown the
|
|
||||||
/// segment is not usable anymore.
|
|
||||||
///
|
|
||||||
/// This method cannot be called if the segment object is created in the
|
|
||||||
/// read-only mode; in that case MemorySegmentError will be thrown.
|
|
||||||
///
|
|
||||||
/// \throw MemorySegmentError see the description.
|
|
||||||
void shrinkToFit();
|
|
||||||
|
|
||||||
/// \brief Return the actual segment size.
|
|
||||||
///
|
|
||||||
/// This is generally expected to be the file size to map. It's
|
|
||||||
/// provided mainly for diagnosis and testing purposes; the application
|
|
||||||
/// shouldn't rely on specific return values of this method.
|
|
||||||
///
|
|
||||||
/// \throw None
|
|
||||||
size_t getSize() const;
|
|
||||||
|
|
||||||
/// \brief Calculate a checksum over the memory segment.
|
|
||||||
///
|
|
||||||
/// This method goes over all pages of the underlying mapped memory
|
|
||||||
/// segment, and returns the sum of the value of the first byte of each
|
|
||||||
/// page (wrapping around upon overflow). It only proves weak integrity
|
|
||||||
/// of the file contents, but can run fast enough and will ensure all
|
|
||||||
/// pages are actually in memory. The latter property will be useful
|
|
||||||
/// if the application cannot allow the initial page fault overhead.
|
|
||||||
///
|
|
||||||
/// \throw None
|
|
||||||
size_t getCheckSum() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Impl;
|
|
||||||
Impl* impl_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace util
|
|
||||||
} // namespace isc
|
|
||||||
|
|
||||||
#endif // MEMORY_SEGMENT_MAPPED_H
|
|
||||||
|
|
||||||
// Local Variables:
|
|
||||||
// mode: c++
|
|
||||||
// End:
|
|
@@ -35,9 +35,6 @@ run_unittests_SOURCES += hex_unittest.cc
|
|||||||
run_unittests_SOURCES += io_utilities_unittest.cc
|
run_unittests_SOURCES += io_utilities_unittest.cc
|
||||||
run_unittests_SOURCES += lru_list_unittest.cc
|
run_unittests_SOURCES += lru_list_unittest.cc
|
||||||
run_unittests_SOURCES += memory_segment_local_unittest.cc
|
run_unittests_SOURCES += memory_segment_local_unittest.cc
|
||||||
if USE_SHARED_MEMORY
|
|
||||||
run_unittests_SOURCES += memory_segment_mapped_unittest.cc
|
|
||||||
endif
|
|
||||||
run_unittests_SOURCES += memory_segment_common_unittest.h
|
run_unittests_SOURCES += memory_segment_common_unittest.h
|
||||||
run_unittests_SOURCES += memory_segment_common_unittest.cc
|
run_unittests_SOURCES += memory_segment_common_unittest.cc
|
||||||
run_unittests_SOURCES += optional_value_unittest.cc
|
run_unittests_SOURCES += optional_value_unittest.cc
|
||||||
|
@@ -1,650 +0,0 @@
|
|||||||
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
||||||
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
||||||
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
#include <util/tests/memory_segment_common_unittest.h>
|
|
||||||
#include <util/unittests/check_valgrind.h>
|
|
||||||
#include <util/unittests/interprocess_util.h>
|
|
||||||
|
|
||||||
#include <util/memory_segment_mapped.h>
|
|
||||||
#include <exceptions/exceptions.h>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <boost/interprocess/file_mapping.hpp>
|
|
||||||
#include <boost/interprocess/mapped_region.hpp>
|
|
||||||
#include <boost/scoped_ptr.hpp>
|
|
||||||
#include <boost/foreach.hpp>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <limits>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
using namespace isc::util;
|
|
||||||
using boost::scoped_ptr;
|
|
||||||
using isc::util::unittests::parentReadState;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
// Shortcut to keep code shorter
|
|
||||||
const MemorySegmentMapped::OpenMode OPEN_FOR_WRITE =
|
|
||||||
MemorySegmentMapped::OPEN_FOR_WRITE;
|
|
||||||
const MemorySegmentMapped::OpenMode OPEN_OR_CREATE =
|
|
||||||
MemorySegmentMapped::OPEN_OR_CREATE;
|
|
||||||
const MemorySegmentMapped::OpenMode CREATE_ONLY =
|
|
||||||
MemorySegmentMapped::CREATE_ONLY;
|
|
||||||
|
|
||||||
const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
|
|
||||||
const size_t DEFAULT_INITIAL_SIZE = 32 * 1024; // intentionally hardcoded
|
|
||||||
|
|
||||||
// A simple RAII-style wrapper for a pipe. Several tests in this file use
|
|
||||||
// pipes, so this helper will be useful.
|
|
||||||
class PipeHolder {
|
|
||||||
public:
|
|
||||||
PipeHolder() {
|
|
||||||
if (pipe(fds_) == -1) {
|
|
||||||
isc_throw(isc::Unexpected, "pipe failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~PipeHolder() {
|
|
||||||
close(fds_[0]);
|
|
||||||
close(fds_[1]);
|
|
||||||
}
|
|
||||||
int getReadFD() const { return (fds_[0]); }
|
|
||||||
int getWriteFD() const { return (fds_[1]); }
|
|
||||||
private:
|
|
||||||
int fds_[2];
|
|
||||||
};
|
|
||||||
|
|
||||||
class MemorySegmentMappedTest : public ::testing::Test {
|
|
||||||
protected:
|
|
||||||
MemorySegmentMappedTest() {
|
|
||||||
resetSegment();
|
|
||||||
}
|
|
||||||
|
|
||||||
~MemorySegmentMappedTest() {
|
|
||||||
segment_.reset();
|
|
||||||
boost::interprocess::file_mapping::remove(mapped_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For initialization and for tests after the segment possibly becomes
|
|
||||||
// broken.
|
|
||||||
void resetSegment() {
|
|
||||||
segment_.reset();
|
|
||||||
boost::interprocess::file_mapping::remove(mapped_file);
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
scoped_ptr<MemorySegmentMapped> segment_;
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(MemorySegmentMappedConstantTest, staticVariables) {
|
|
||||||
// Attempt to take address of MemorySegmentMapped::INITIAL_SIZE.
|
|
||||||
// It helps in case we accidentally remove the definition from the main
|
|
||||||
// code.
|
|
||||||
EXPECT_EQ(DEFAULT_INITIAL_SIZE, *(&MemorySegmentMapped::INITIAL_SIZE));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, createAndModify) {
|
|
||||||
// We are going to do the same set of basic tests twice; one after creating
|
|
||||||
// the mapped file, the other by re-opening the existing file in the
|
|
||||||
// read-write mode.
|
|
||||||
for (int i = 0; i < 2; ++i) {
|
|
||||||
// It should have the default size (intentionally hardcoded)
|
|
||||||
EXPECT_EQ(DEFAULT_INITIAL_SIZE, segment_->getSize());
|
|
||||||
|
|
||||||
// By default, nothing is allocated.
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
void* ptr = segment_->allocate(1024);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
|
|
||||||
// Now, we have an allocation:
|
|
||||||
EXPECT_FALSE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
// deallocate it; it shouldn't cause disruption.
|
|
||||||
segment_->deallocate(ptr, 1024);
|
|
||||||
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
// re-open it in read-write mode, but don't try to create it
|
|
||||||
// this time.
|
|
||||||
segment_.reset(); // make sure close is first.
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, createWithSize) {
|
|
||||||
boost::interprocess::file_mapping::remove(mapped_file);
|
|
||||||
|
|
||||||
// Re-create the mapped file with a non-default initial size, and confirm
|
|
||||||
// the size is actually the specified one.
|
|
||||||
const size_t new_size = 64 * 1024;
|
|
||||||
EXPECT_NE(new_size, segment_->getSize());
|
|
||||||
segment_.reset();
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE,
|
|
||||||
new_size));
|
|
||||||
EXPECT_EQ(new_size, segment_->getSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, createOnly) {
|
|
||||||
// First, allocate some data in the existing segment
|
|
||||||
EXPECT_TRUE(segment_->allocate(16));
|
|
||||||
// Close it, and then open it again in the create-only mode. the existing
|
|
||||||
// file should be internally removed, and so the resulting segment
|
|
||||||
// should be "empty" (all deallocated).
|
|
||||||
segment_.reset();
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, openFail) {
|
|
||||||
// The given file is directory
|
|
||||||
EXPECT_THROW(MemorySegmentMapped("/", OPEN_OR_CREATE),
|
|
||||||
MemorySegmentOpenError);
|
|
||||||
|
|
||||||
// file doesn't exist and directory isn't writable (we assume the
|
|
||||||
// following path is not writable for the user running the test).
|
|
||||||
EXPECT_THROW(MemorySegmentMapped("/random-glkwjer098/test.mapped",
|
|
||||||
OPEN_OR_CREATE), MemorySegmentOpenError);
|
|
||||||
|
|
||||||
// It should fail when file doesn't exist and it's read-only (so
|
|
||||||
// open-only).
|
|
||||||
EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped"),
|
|
||||||
MemorySegmentOpenError);
|
|
||||||
// Likewise, it should fail in read-write mode when creation is
|
|
||||||
// suppressed.
|
|
||||||
EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped",
|
|
||||||
OPEN_FOR_WRITE), MemorySegmentOpenError);
|
|
||||||
|
|
||||||
// creating with a very small size fails (for sure about 0, and other
|
|
||||||
// small values should also make it fail, but it's internal restriction
|
|
||||||
// of Boost and cannot be predictable).
|
|
||||||
EXPECT_THROW(MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 0),
|
|
||||||
MemorySegmentOpenError);
|
|
||||||
|
|
||||||
// invalid read-write mode
|
|
||||||
EXPECT_THROW(MemorySegmentMapped(
|
|
||||||
mapped_file,
|
|
||||||
static_cast<MemorySegmentMapped::OpenMode>(
|
|
||||||
static_cast<int>(CREATE_ONLY) + 1)),
|
|
||||||
isc::InvalidParameter);
|
|
||||||
|
|
||||||
// Close the existing segment, break its file with bogus data, and
|
|
||||||
// try to reopen. It should fail with exception whether in the
|
|
||||||
// read-only or read-write, or "create if not exist" mode.
|
|
||||||
segment_.reset();
|
|
||||||
std::ofstream ofs(mapped_file, std::ios::trunc);
|
|
||||||
ofs << std::string(1024, 'x');
|
|
||||||
ofs.close();
|
|
||||||
EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file), MemorySegmentOpenError);
|
|
||||||
EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_FOR_WRITE),
|
|
||||||
MemorySegmentOpenError);
|
|
||||||
EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_OR_CREATE),
|
|
||||||
MemorySegmentOpenError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, allocate) {
|
|
||||||
// Various case of allocation. The simplest cases are covered above.
|
|
||||||
|
|
||||||
// Initially, nothing is allocated.
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
// (Clearly) exceeding the available size, which should cause growing
|
|
||||||
// the segment
|
|
||||||
const size_t prev_size = segment_->getSize();
|
|
||||||
EXPECT_THROW(segment_->allocate(prev_size + 1), MemorySegmentGrown);
|
|
||||||
// The size should have been doubled.
|
|
||||||
EXPECT_EQ(prev_size * 2, segment_->getSize());
|
|
||||||
// But nothing should have been allocated.
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
// Now, the allocation should now succeed.
|
|
||||||
void* ptr = segment_->allocate(prev_size + 1);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
EXPECT_FALSE(segment_->allMemoryDeallocated());
|
|
||||||
|
|
||||||
// Same set of checks, but for a larger size.
|
|
||||||
EXPECT_THROW(segment_->allocate(prev_size * 10), MemorySegmentGrown);
|
|
||||||
// the segment should have grown to the minimum power-of-2 size that
|
|
||||||
// could allocate the given size of memory.
|
|
||||||
EXPECT_EQ(prev_size * 16, segment_->getSize());
|
|
||||||
// And allocate() should now succeed.
|
|
||||||
ptr = segment_->allocate(prev_size * 10);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
|
|
||||||
// (we'll left the regions created in the file there; the entire file
|
|
||||||
// will be removed at the end of the test)
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, badAllocate) {
|
|
||||||
// If the test is run as the root user, the following allocate()
|
|
||||||
// call will result in a successful MemorySegmentGrown exception,
|
|
||||||
// instead of an abort (due to insufficient permissions during
|
|
||||||
// reopen).
|
|
||||||
if (getuid() == 0) {
|
|
||||||
std::cerr << "Skipping test as it's run as the root user" << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the mapped file non-writable; managed_mapped_file::grow() will
|
|
||||||
// fail, resulting in abort.
|
|
||||||
const int ret = chmod(mapped_file, 0444);
|
|
||||||
ASSERT_EQ(0, ret);
|
|
||||||
|
|
||||||
if (!isc::util::unittests::runningOnValgrind()) {
|
|
||||||
EXPECT_DEATH_IF_SUPPORTED(
|
|
||||||
{segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: this test can cause too strong side effect (creating a very large
|
|
||||||
// file), so we disable it by default
|
|
||||||
TEST_F(MemorySegmentMappedTest, DISABLED_allocateHuge) {
|
|
||||||
EXPECT_THROW(segment_->allocate(std::numeric_limits<size_t>::max()),
|
|
||||||
std::bad_alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, badDeallocate) {
|
|
||||||
void* ptr = segment_->allocate(4);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
|
|
||||||
segment_->deallocate(ptr, 4); // this is okay
|
|
||||||
// This is duplicate dealloc; should trigger assertion failure.
|
|
||||||
if (!isc::util::unittests::runningOnValgrind()) {
|
|
||||||
EXPECT_DEATH_IF_SUPPORTED({segment_->deallocate(ptr, 4);}, "");
|
|
||||||
resetSegment(); // the segment is possibly broken; reset it.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deallocating at an invalid address; this would result in crash (the
|
|
||||||
// behavior may not be portable enough; if so we should disable it by
|
|
||||||
// default).
|
|
||||||
if (!isc::util::unittests::runningOnValgrind()) {
|
|
||||||
ptr = segment_->allocate(4);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
EXPECT_DEATH_IF_SUPPORTED({
|
|
||||||
segment_->deallocate(static_cast<char*>(ptr) + 1, 3);
|
|
||||||
}, "");
|
|
||||||
resetSegment();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid size; this implementation doesn't detect such errors.
|
|
||||||
ptr = segment_->allocate(4);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), ptr);
|
|
||||||
segment_->deallocate(ptr, 8);
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
}
|
|
||||||
|
|
||||||
// A helper of namedAddress.
|
|
||||||
void
|
|
||||||
checkNamedData(const std::string& name, const std::vector<uint8_t>& data,
|
|
||||||
MemorySegment& sgmt, bool delete_after_check = false)
|
|
||||||
{
|
|
||||||
const MemorySegment::NamedAddressResult result =
|
|
||||||
sgmt.getNamedAddress(name.c_str());
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
ASSERT_TRUE(result.second);
|
|
||||||
EXPECT_EQ(0, std::memcmp(result.second, &data[0], data.size()));
|
|
||||||
|
|
||||||
if (delete_after_check) {
|
|
||||||
sgmt.deallocate(result.second, data.size());
|
|
||||||
sgmt.clearNamedAddress(name.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, namedAddress) {
|
|
||||||
// common test cases
|
|
||||||
isc::util::test::checkSegmentNamedAddress(*segment_, false);
|
|
||||||
|
|
||||||
// Set it again and read it in the read-only mode.
|
|
||||||
void* ptr16 = segment_->allocate(sizeof(uint16_t));
|
|
||||||
const uint16_t test_val16 = 42000;
|
|
||||||
*static_cast<uint16_t*>(ptr16) = test_val16;
|
|
||||||
EXPECT_FALSE(segment_->setNamedAddress("test address", ptr16));
|
|
||||||
segment_.reset(); // close it before opening another one
|
|
||||||
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file));
|
|
||||||
MemorySegment::NamedAddressResult result =
|
|
||||||
segment_->getNamedAddress("test address");
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
|
|
||||||
|
|
||||||
// try to set an unusually long name. We re-create the file so
|
|
||||||
// creating the name would cause allocation failure and trigger internal
|
|
||||||
// segment extension.
|
|
||||||
segment_.reset();
|
|
||||||
boost::interprocess::file_mapping::remove(mapped_file);
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 1024));
|
|
||||||
const std::string long_name(1025, 'x'); // definitely larger than segment
|
|
||||||
// setNamedAddress should return true, indicating segment has grown.
|
|
||||||
EXPECT_TRUE(segment_->setNamedAddress(long_name.c_str(), NULL));
|
|
||||||
result = segment_->getNamedAddress(long_name.c_str());
|
|
||||||
EXPECT_TRUE(result.first);
|
|
||||||
EXPECT_FALSE(result.second);
|
|
||||||
|
|
||||||
// Check contents pointed by named addresses survive growing and
|
|
||||||
// shrinking segment.
|
|
||||||
segment_.reset();
|
|
||||||
boost::interprocess::file_mapping::remove(mapped_file);
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::vector<uint8_t> > TestData;
|
|
||||||
|
|
||||||
TestData data_list;
|
|
||||||
data_list["data1"] =
|
|
||||||
std::vector<uint8_t>(80); // arbitrarily chosen small data
|
|
||||||
data_list["data2"] =
|
|
||||||
std::vector<uint8_t>(5000); // larger than usual segment size
|
|
||||||
data_list["data3"] =
|
|
||||||
std::vector<uint8_t>(65535); // bigger than most usual data
|
|
||||||
bool grown = false;
|
|
||||||
|
|
||||||
// Allocate memory and store data
|
|
||||||
for (TestData::iterator it = data_list.begin(); it != data_list.end();
|
|
||||||
++it)
|
|
||||||
{
|
|
||||||
std::vector<uint8_t>& data = it->second;
|
|
||||||
for (int i = 0; i < data.size(); ++i) {
|
|
||||||
data[i] = i;
|
|
||||||
}
|
|
||||||
void *dp = NULL;
|
|
||||||
while (!dp) {
|
|
||||||
try {
|
|
||||||
dp = segment_->allocate(data.size());
|
|
||||||
std::memcpy(dp, &data[0], data.size());
|
|
||||||
segment_->setNamedAddress(it->first.c_str(), dp);
|
|
||||||
} catch (const MemorySegmentGrown&) {
|
|
||||||
grown = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Confirm there's at least one segment extension
|
|
||||||
EXPECT_TRUE(grown);
|
|
||||||
// Check named data are still valid
|
|
||||||
for (TestData::iterator it = data_list.begin(); it != data_list.end();
|
|
||||||
++it)
|
|
||||||
{
|
|
||||||
checkNamedData(it->first, it->second, *segment_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm they are still valid, while we shrink the segment. We'll
|
|
||||||
// intentionally delete bigger data first so it'll be more likely that
|
|
||||||
// shrink has some real effect.
|
|
||||||
const char* const names[] = { "data3", "data2", "data1", NULL };
|
|
||||||
for (int i = 0; names[i]; ++i) {
|
|
||||||
checkNamedData(names[i], data_list[names[i]], *segment_, true);
|
|
||||||
segment_->shrinkToFit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, multiProcess) {
|
|
||||||
// Test using fork() doesn't work well on valgrind
|
|
||||||
if (isc::util::unittests::runningOnValgrind()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate some data and name its address
|
|
||||||
void* ptr = segment_->allocate(sizeof(uint32_t));
|
|
||||||
*static_cast<uint32_t*>(ptr) = 424242;
|
|
||||||
segment_->setNamedAddress("test address", ptr);
|
|
||||||
|
|
||||||
// close the read-write segment at this point. our intended use case is
|
|
||||||
// to have one or more reader process or at most one exclusive writer
|
|
||||||
// process. so we don't mix reader and writer.
|
|
||||||
segment_.reset();
|
|
||||||
|
|
||||||
// Spawn another process and have it open and read the same data.
|
|
||||||
PipeHolder pipe_to_child;
|
|
||||||
PipeHolder pipe_to_parent;
|
|
||||||
const pid_t child_pid = fork();
|
|
||||||
ASSERT_NE(-1, child_pid);
|
|
||||||
if (child_pid == 0) {
|
|
||||||
// child: wait until the parent has opened the read-only segment.
|
|
||||||
char from_parent;
|
|
||||||
EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &from_parent,
|
|
||||||
sizeof(from_parent)));
|
|
||||||
EXPECT_EQ(0, from_parent);
|
|
||||||
|
|
||||||
MemorySegmentMapped sgmt(mapped_file);
|
|
||||||
const MemorySegment::NamedAddressResult result =
|
|
||||||
sgmt.getNamedAddress("test address");
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
EXPECT_TRUE(result.second);
|
|
||||||
if (result.second) {
|
|
||||||
const uint32_t val = *static_cast<const uint32_t*>(result.second);
|
|
||||||
EXPECT_EQ(424242, val);
|
|
||||||
// tell the parent whether it succeeded. 0 means it did,
|
|
||||||
// 0xff means it failed.
|
|
||||||
const char ok = (val == 424242) ? 0 : 0xff;
|
|
||||||
EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ok, sizeof(ok)));
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
// parent: open another read-only segment, then tell the child to open
|
|
||||||
// its own segment.
|
|
||||||
segment_.reset(new MemorySegmentMapped(mapped_file));
|
|
||||||
const MemorySegment::NamedAddressResult result =
|
|
||||||
segment_->getNamedAddress("test address");
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
ASSERT_TRUE(result.second);
|
|
||||||
EXPECT_EQ(424242, *static_cast<const uint32_t*>(result.second));
|
|
||||||
const char some_data = 0;
|
|
||||||
EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
|
|
||||||
sizeof(some_data)));
|
|
||||||
|
|
||||||
// wait for the completion of the child and checks the result.
|
|
||||||
EXPECT_EQ(0, parentReadState(pipe_to_parent.getReadFD()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, nullDeallocate) {
|
|
||||||
// NULL deallocation is a no-op.
|
|
||||||
EXPECT_NO_THROW(segment_->deallocate(0, 1024));
|
|
||||||
EXPECT_TRUE(segment_->allMemoryDeallocated());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, shrink) {
|
|
||||||
segment_->shrinkToFit();
|
|
||||||
// Normally we should be able to expect that the resulting size is
|
|
||||||
// smaller than the initial default size. But it's not really
|
|
||||||
// guaranteed by the API, so we may have to disable this check (or
|
|
||||||
// use EXPECT_GE).
|
|
||||||
const size_t shrinked_size = segment_->getSize();
|
|
||||||
EXPECT_GT(DEFAULT_INITIAL_SIZE, shrinked_size);
|
|
||||||
|
|
||||||
// Another shrink shouldn't cause disruption. We expect the size is
|
|
||||||
// the same so we confirm it. The underlying library doesn't guarantee
|
|
||||||
// that, so we may have to change it to EXPECT_GE if the test fails
|
|
||||||
// on that (MemorySegmentMapped class doesn't rely on this expectation,
|
|
||||||
// so it's okay even if it does not always hold).
|
|
||||||
segment_->shrinkToFit();
|
|
||||||
EXPECT_EQ(shrinked_size, segment_->getSize());
|
|
||||||
|
|
||||||
// Check that the segment is still usable after shrink.
|
|
||||||
void *p = NULL;
|
|
||||||
while (!p) {
|
|
||||||
try {
|
|
||||||
p = segment_->allocate(sizeof(uint32_t));
|
|
||||||
} catch (const MemorySegmentGrown&) {
|
|
||||||
// Do nothing. Just try again.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segment_->deallocate(p, sizeof(uint32_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, violateReadOnly) {
|
|
||||||
// Create a named address for the tests below, then reset the writer
|
|
||||||
// segment so that it won't fail for different reason (i.e., read-write
|
|
||||||
// conflict).
|
|
||||||
void* ptr = segment_->allocate(sizeof(uint32_t));
|
|
||||||
segment_->setNamedAddress("test address", ptr);
|
|
||||||
segment_.reset();
|
|
||||||
|
|
||||||
// Attempts to modify memory from the read-only segment directly
|
|
||||||
// will result in a crash.
|
|
||||||
if (!isc::util::unittests::runningOnValgrind()) {
|
|
||||||
EXPECT_DEATH_IF_SUPPORTED({
|
|
||||||
MemorySegmentMapped segment_ro(mapped_file);
|
|
||||||
const MemorySegment::NamedAddressResult result =
|
|
||||||
segment_ro.getNamedAddress("test address");
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
ASSERT_TRUE(result.second);
|
|
||||||
*static_cast<uint32_t*>(result.second) = 0;
|
|
||||||
}, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the segment is opened in the read-only mode, modification
|
|
||||||
// attempts are prohibited. When detectable it must result in an
|
|
||||||
// exception.
|
|
||||||
MemorySegmentMapped segment_ro(mapped_file);
|
|
||||||
const MemorySegment::NamedAddressResult result =
|
|
||||||
segment_ro.getNamedAddress("test address");
|
|
||||||
ASSERT_TRUE(result.first);
|
|
||||||
EXPECT_NE(static_cast<void*>(NULL), result.second);
|
|
||||||
|
|
||||||
EXPECT_THROW(segment_ro.deallocate(result.second, 4), MemorySegmentError);
|
|
||||||
|
|
||||||
EXPECT_THROW(segment_ro.allocate(16), MemorySegmentError);
|
|
||||||
// allocation that would otherwise require growing the segment; permission
|
|
||||||
// check should be performed before that.
|
|
||||||
EXPECT_THROW(segment_ro.allocate(DEFAULT_INITIAL_SIZE * 2),
|
|
||||||
MemorySegmentError);
|
|
||||||
EXPECT_THROW(segment_ro.setNamedAddress("test", NULL), MemorySegmentError);
|
|
||||||
EXPECT_THROW(segment_ro.clearNamedAddress("test"), MemorySegmentError);
|
|
||||||
EXPECT_THROW(segment_ro.shrinkToFit(), MemorySegmentError);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, getCheckSum) {
|
|
||||||
const size_t old_cksum = segment_->getCheckSum();
|
|
||||||
|
|
||||||
// We assume the initial segment size is sufficiently larger than
|
|
||||||
// the page size. We'll allocate memory of the page size, and
|
|
||||||
// increment all bytes in that page by one. It will increase our
|
|
||||||
// simple checksum value (which just uses the first byte of each
|
|
||||||
// page) by one, too.
|
|
||||||
const size_t page_sz = boost::interprocess::mapped_region::get_page_size();
|
|
||||||
uint8_t* cp0 = static_cast<uint8_t*>(segment_->allocate(page_sz));
|
|
||||||
for (uint8_t* cp = cp0; cp < cp0 + page_sz; ++cp) {
|
|
||||||
++*cp;
|
|
||||||
}
|
|
||||||
|
|
||||||
EXPECT_EQ(old_cksum + 1, segment_->getCheckSum());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode of opening segments in the tests below.
|
|
||||||
enum TestOpenMode {
|
|
||||||
READER = 0,
|
|
||||||
WRITER_FOR_WRITE,
|
|
||||||
WRITER_OPEN_OR_CREATE,
|
|
||||||
WRITER_CREATE_ONLY
|
|
||||||
};
|
|
||||||
|
|
||||||
// A shortcut to attempt to open a specified type of segment (generally
|
|
||||||
// expecting it to fail)
|
|
||||||
void
|
|
||||||
setSegment(TestOpenMode mode, scoped_ptr<MemorySegmentMapped>& sgmt_ptr) {
|
|
||||||
switch (mode) {
|
|
||||||
case READER:
|
|
||||||
sgmt_ptr.reset(new MemorySegmentMapped(mapped_file));
|
|
||||||
break;
|
|
||||||
case WRITER_FOR_WRITE:
|
|
||||||
sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
|
|
||||||
break;
|
|
||||||
case WRITER_OPEN_OR_CREATE:
|
|
||||||
sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
|
|
||||||
break;
|
|
||||||
case WRITER_CREATE_ONLY:
|
|
||||||
sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common logic for conflictReaderWriter test. The segment opened in the
|
|
||||||
// parent process will prevent the segment in the child from being used.
|
|
||||||
void
|
|
||||||
conflictCheck(TestOpenMode parent_mode, TestOpenMode child_mode) {
|
|
||||||
PipeHolder pipe_to_child;
|
|
||||||
PipeHolder pipe_to_parent;
|
|
||||||
const pid_t child_pid = fork();
|
|
||||||
ASSERT_NE(-1, child_pid);
|
|
||||||
|
|
||||||
if (child_pid == 0) {
|
|
||||||
char ch;
|
|
||||||
EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &ch, sizeof(ch)));
|
|
||||||
|
|
||||||
ch = 0; // 0 = open success, 1 = fail
|
|
||||||
try {
|
|
||||||
scoped_ptr<MemorySegmentMapped> sgmt;
|
|
||||||
setSegment(child_mode, sgmt);
|
|
||||||
EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
|
|
||||||
} catch (const MemorySegmentOpenError&) {
|
|
||||||
ch = 1;
|
|
||||||
EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// parent: open a segment, then tell the child to open its own segment of
|
|
||||||
// the specified type.
|
|
||||||
scoped_ptr<MemorySegmentMapped> sgmt;
|
|
||||||
setSegment(parent_mode, sgmt);
|
|
||||||
const char some_data = 0;
|
|
||||||
EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
|
|
||||||
sizeof(some_data)));
|
|
||||||
|
|
||||||
// wait for the completion of the child and checks the result. open at
|
|
||||||
// the child side should fail, so the parent should get the value of 1.
|
|
||||||
EXPECT_EQ(1, parentReadState(pipe_to_parent.getReadFD()));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(MemorySegmentMappedTest, conflictReaderWriter) {
|
|
||||||
// Test using fork() doesn't work well on valgrind
|
|
||||||
if (isc::util::unittests::runningOnValgrind()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Below, we check all combinations of conflicts between reader and writer
|
|
||||||
// will fail. We first make sure there's no other reader or writer.
|
|
||||||
segment_.reset();
|
|
||||||
|
|
||||||
// reader opens segment, then writer (OPEN_FOR_WRITE) tries to open
|
|
||||||
conflictCheck(READER, WRITER_FOR_WRITE);
|
|
||||||
// reader opens segment, then writer (OPEN_OR_CREATE) tries to open
|
|
||||||
conflictCheck(READER, WRITER_OPEN_OR_CREATE);
|
|
||||||
// reader opens segment, then writer (CREATE_ONLY) tries to open
|
|
||||||
conflictCheck(READER, WRITER_CREATE_ONLY);
|
|
||||||
|
|
||||||
// writer (OPEN_FOR_WRITE) opens a segment, then reader tries to open
|
|
||||||
conflictCheck(WRITER_FOR_WRITE, READER);
|
|
||||||
// writer (OPEN_OR_CREATE) opens a segment, then reader tries to open
|
|
||||||
conflictCheck(WRITER_OPEN_OR_CREATE, READER);
|
|
||||||
// writer (CREATE_ONLY) opens a segment, then reader tries to open
|
|
||||||
conflictCheck(WRITER_CREATE_ONLY, READER);
|
|
||||||
|
|
||||||
// writer opens segment, then another writer (OPEN_FOR_WRITE) tries to open
|
|
||||||
conflictCheck(WRITER_FOR_WRITE, WRITER_FOR_WRITE);
|
|
||||||
// writer opens segment, then another writer (OPEN_OR_CREATE) tries to open
|
|
||||||
conflictCheck(WRITER_FOR_WRITE, WRITER_OPEN_OR_CREATE);
|
|
||||||
// writer opens segment, then another writer (CREATE_ONLY) tries to open
|
|
||||||
conflictCheck(WRITER_FOR_WRITE, WRITER_CREATE_ONLY);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Reference in New Issue
Block a user