2
0
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:
Francis Dupont 2014-10-21 20:40:48 +02:00
parent adae540811
commit adee8c93f7
8 changed files with 5 additions and 1485 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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);
}
}