mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 13:37:55 +00:00
[trac3614] removed no longer used --with-shared-memory and dependencies
This commit is contained in:
parent
adae540811
commit
adee8c93f7
@ -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
|
||||
Fixed subdir-objects warnings from recent versions of autotools,
|
||||
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.])
|
||||
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.
|
||||
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 BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
|
||||
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 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_LANG_SAVE
|
||||
AC_LANG([C++])
|
||||
@ -119,56 +108,9 @@ if test "X$GXX" = "Xyes"; then
|
||||
BOOST_NUMERIC_CAST_WOULDFAIL=yes])
|
||||
|
||||
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
|
||||
# This doesn't matter for non-g++
|
||||
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
|
||||
|
||||
# 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 += $(BOOST_INCLUDES)
|
||||
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
|
||||
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 += memory_segment.h
|
||||
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 += range_utilities.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 += lru_list_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.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);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user