diff --git a/ChangeLog b/ChangeLog index a2b51eabd1..f8f17c2d94 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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. diff --git a/configure.ac b/configure.ac index 8560c22aa0..a92077ea5e 100644 --- a/configure.ac +++ b/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" diff --git a/m4macros/ax_boost_for_kea.m4 b/m4macros/ax_boost_for_kea.m4 index 99be90a27e..263e022ac5 100644 --- a/m4macros/ax_boost_for_kea.m4 +++ b/m4macros/ax_boost_for_kea.m4 @@ -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 - #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 - ],[ - 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 diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am index a1e19f8a7f..7ad7749589 100644 --- a/src/lib/util/Makefile.am +++ b/src/lib/util/Makefile.am @@ -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 diff --git a/src/lib/util/memory_segment_mapped.cc b/src/lib/util/memory_segment_mapped.cc deleted file mode 100644 index 8fea5ea3a3..0000000000 --- a/src/lib/util/memory_segment_mapped.cc +++ /dev/null @@ -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 -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -// 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, - 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* reserved_storage = - base_sgmt_->find_or_construct >( - 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 > - (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 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 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* storage = - impl_->base_sgmt_->find >(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* reserved_storage = - impl_->base_sgmt_->find >( - RESERVED_NAMED_ADDRESS_STORAGE_NAME).first; - assert(reserved_storage); - *reserved_storage = addr; - - bool grown = false; - while (true) { - offset_ptr* storage = - impl_->base_sgmt_->find_or_construct >( - name, std::nothrow)(); - if (storage) { - // Move the address from saved offset_ptr into the - // newly-allocated storage. - reserved_storage = - impl_->base_sgmt_->find >( - 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 >(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( - 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 diff --git a/src/lib/util/memory_segment_mapped.h b/src/lib/util/memory_segment_mapped.h deleted file mode 100644 index 301b174c84..0000000000 --- a/src/lib/util/memory_segment_mapped.h +++ /dev/null @@ -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 - -#include - -#include - -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: diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 91722be1ba..7bad6f9613 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -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 diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc deleted file mode 100644 index 8ae6fea330..0000000000 --- a/src/lib/util/tests/memory_segment_mapped_unittest.cc +++ /dev/null @@ -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 -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -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 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(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( - static_cast(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(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(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::max()), - std::bad_alloc); -} - -TEST_F(MemorySegmentMappedTest, badDeallocate) { - void* ptr = segment_->allocate(4); - EXPECT_NE(static_cast(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(NULL), ptr); - EXPECT_DEATH_IF_SUPPORTED({ - segment_->deallocate(static_cast(ptr) + 1, 3); - }, ""); - resetSegment(); - } - - // Invalid size; this implementation doesn't detect such errors. - ptr = segment_->allocate(4); - EXPECT_NE(static_cast(NULL), ptr); - segment_->deallocate(ptr, 8); - EXPECT_TRUE(segment_->allMemoryDeallocated()); -} - -// A helper of namedAddress. -void -checkNamedData(const std::string& name, const std::vector& 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(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(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 > TestData; - - TestData data_list; - data_list["data1"] = - std::vector(80); // arbitrarily chosen small data - data_list["data2"] = - std::vector(5000); // larger than usual segment size - data_list["data3"] = - std::vector(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& 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(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(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(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(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(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(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& 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 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 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); -} - -}