Browse Source

Merge remote-tracking branch 'origin/trac2831' into trac2850_2

Mukund Sivaraman 12 years ago
parent
commit
08f6a3e96b

+ 11 - 0
configure.ac

@@ -884,6 +884,17 @@ 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"; 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; 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])
+AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
+
 # Add some default CPP flags needed for Boost, identified by the AX macro.
 CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
 

+ 46 - 3
m4macros/ax_boost_for_bind10.m4

@@ -23,7 +23,11 @@ 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
+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.
 
 AC_DEFUN([AX_BOOST_FOR_BIND10], [
 AC_LANG_SAVE
@@ -101,10 +105,49 @@ if test "X$GXX" = "Xyes"; then
 
    CXXFLAGS="$CXXFLAGS_SAVED"
 else
-  # This doesn't matter for non-g++
-  BOOST_NUMERIC_CAST_WOULDFAIL=no
+   # This doesn't matter for non-g++
+   BOOST_NUMERIC_CAST_WOULDFAIL=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 -Wall -Wextra -Werror"
+  try_flags="$try_flags -Wno-error"
+fi
+# clang can cause false positives with -Werror without -Qunused-arguments
+AC_CHECK_DECL([__clang__], [CXXFLAGS="$CXXFLAGS -Qunused-arguments"], [])
+
+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 $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
+
+CXXFLAGS="$CXXFLAGS_SAVED"
+
 AC_SUBST(BOOST_INCLUDES)
 
 CPPFLAGS="$CPPFLAGS_SAVED"

+ 15 - 0
src/lib/util/Makefile.am

@@ -6,6 +6,18 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exce
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DLOCKFILE_DIR=\"${localstatedir}/${PACKAGE_NAME}\"
 AM_CXXFLAGS = $(B10_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 = libb10-util.la
 libb10_util_la_SOURCES  = filename.h filename.cc
@@ -18,6 +30,9 @@ libb10_util_la_SOURCES += interprocess_sync_file.h interprocess_sync_file.cc
 libb10_util_la_SOURCES += interprocess_sync_null.h interprocess_sync_null.cc
 libb10_util_la_SOURCES += memory_segment.h
 libb10_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
+if USE_SHARED_MEMORY
+libb10_util_la_SOURCES += memory_segment_mapped.h memory_segment_mapped.cc
+endif
 libb10_util_la_SOURCES += range_utilities.h
 libb10_util_la_SOURCES += hash/sha1.h hash/sha1.cc
 libb10_util_la_SOURCES += encode/base16_from_binary.h

+ 244 - 9
src/lib/util/memory_segment.h

@@ -15,27 +15,107 @@
 #ifndef MEMORY_SEGMENT_H
 #define MEMORY_SEGMENT_H
 
+#include <exceptions/exceptions.h>
+
 #include <stdlib.h>
 
 namespace isc {
 namespace util {
 
+/// \brief Exception that can be thrown when constructing a MemorySegment
+/// object.
+class MemorySegmentOpenError : public Exception {
+public:
+    MemorySegmentOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// \brief Exception that is thrown, when allocating space in a MemorySegment
+/// results in growing the underlying segment.
+///
+/// See MemorySegment::allocate() for details.
+class MemorySegmentGrown : public Exception {
+public:
+    MemorySegmentGrown(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// \brief General error that can be thrown by a MemorySegment
+/// implementation.
+class MemorySegmentError : public Exception {
+public:
+    MemorySegmentError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// \brief Memory Segment Class
 ///
-/// This class specifies an interface for allocating memory
-/// segments. This is an abstract class and a real
-/// implementation such as MemorySegmentLocal should be used
-/// in code.
+/// This class specifies an interface for allocating memory segments.
+/// It's intended to provide a unified interface, whether the underlying
+/// memory is local to a specific process or is sharable by multiple
+/// processes.
+///
+/// This is an abstract class and a real implementation such as
+/// MemorySegmentLocal should be used in code.
 class MemorySegment {
 public:
     /// \brief Destructor
     virtual ~MemorySegment() {}
 
-    /// \brief Allocate/acquire a segment of memory. The source of the
-    /// memory is dependent on the implementation used.
+    /// \brief Allocate/acquire a fragment of memory.
     ///
-    /// Throws <code>std::bad_alloc</code> if the implementation cannot
-    /// allocate the requested storage.
+    /// The source of the memory is dependent on the implementation used.
+    ///
+    /// Depending on the implementation details, it may have to grow the
+    /// internal memory segment (again, in an implementation dependent way)
+    /// to allocate the required size of memory.  In that case the
+    /// implementation must grow the internal segment sufficiently so the
+    /// next call to allocate() for the same size will succeed, and throw
+    /// a \c MemorySegmentGrown exception (not really allocating the memory
+    /// yet).
+    ///
+    /// An application that uses this memory segment abstraction to allocate
+    /// memory should expect this exception, and should normally catch it
+    /// at an appropriate layer (which may be immediately after a call to
+    /// \c allocate() or a bit higher layer).  It should interpret the
+    /// exception as any raw address that belongs to the segment may have
+    /// been remapped and must be re-fetched via an already established
+    /// named address using the \c getNamedAddress() method.
+    ///
+    /// The intended use case of \c allocate() with the \c MemorySegmentGrown
+    /// exception is to build a complex object that would internally require
+    /// multiple calls to \c allocate():
+    ///
+    /// \code
+    /// ComplicatedStuff* stuff = NULL;
+    /// while (!stuff) { // this must eventually succeed or result in bad_alloc
+    ///     try {
+    ///         // create() is a factory method that takes a memory segment
+    ///         // and calls allocate() on it multiple times.  create()
+    ///         // provides an exception guarantee that any intermediately
+    ///         // allocated memory will be properly deallocate()-ed on
+    ///         // exception.
+    ///         stuff = ComplicatedStuff::create(mem_segment);
+    ///     } catch (const MemorySegmentGrown&) { /* just try again */ }
+    /// }
+    /// \endcode
+    ///
+    /// This way, \c create() can be written as if each call to \c allocate()
+    /// always succeeds.
+    ///
+    /// Alternatively, or in addition to this, we could introduce a "no throw"
+    /// version of this method with a way to tell the caller the reason of
+    /// any failure (whether it's really out of memory or just due to growing
+    /// the segment).  That would be more convenient if the caller wants to
+    /// deal with the failures on a per-call basis rather than as a set
+    /// of calls like in the above example.  At the moment, we don't expect
+    /// to have such use-cases, so we only provide the exception
+    /// version.
+    ///
+    /// \throw std::bad_alloc The implementation cannot allocate the
+    /// requested storage.
+    /// \throw MemorySegmentGrown The memory segment doesn't have sufficient
+    /// space for the requested size and has grown internally.
     ///
     /// \param size The size of the memory requested in bytes.
     /// \return Returns pointer to the memory allocated.
@@ -50,6 +130,18 @@ public:
     /// use this argument in some implementations to test if all allocated
     /// memory was deallocated properly.
     ///
+    /// Specific implementation may also throw \c MemorySegmentError if it
+    /// encounters violation of implementation specific restrictions.
+    ///
+    /// In general, however, this method must succeed and exception free
+    /// as long as the caller passes valid parameters (\c ptr specifies
+    /// memory previously allocated and \c size is correct).
+    ///
+    /// \throw OutOfRange The passed size doesn't match the allocated memory
+    /// size (when identifiable for the implementation).
+    /// \throw MemorySegmentError Failure of implementation specific
+    /// validation.
+    ///
     /// \param ptr Pointer to the block of memory to free/release. This
     /// should be equal to a value returned by <code>allocate()</code>.
     /// \param size The size of the memory to be freed in bytes. This
@@ -58,12 +150,155 @@ public:
 
     /// \brief Check if all allocated memory was deallocated.
     ///
-    /// \return Returns <code>true</code> if all allocated memory was
+    /// \return Returns <code>true</code> if all allocated memory (including
+    /// names associated by memory addresses by \c setNamedAddress()) was
     /// deallocated, <code>false</code> otherwise.
     virtual bool allMemoryDeallocated() const = 0;
+
+    /// \brief Associate specified address in the segment with a given name.
+    ///
+    /// This method establishes an association between the given name and
+    /// the address in an implementation specific way.  The stored address
+    /// is retrieved by the name later by calling \c getNamedAddress().
+    /// If the underlying memory segment is sharable by multiple processes,
+    /// the implementation must ensure the portability of the association;
+    /// if a process gives an address in the shared segment a name, another
+    /// process that shares the same segment should be able to retrieve the
+    /// corresponding address by that name (in such cases the real address
+    /// may be different between these two processes).
+    ///
+    /// \c addr must be 0 (NULL) or an address that belongs to this segment.
+    /// The latter case means it must be the return value of a previous call
+    /// to \c allocate().  The actual implementation is encouraged to detect
+    /// violation of this restriction and signal it with an exception, but
+    /// it's not an API requirement.  It's generally the caller's
+    /// responsibility to meet the restriction.  Note that NULL is allowed
+    /// as \c addr even if it wouldn't be considered to "belong to" the
+    /// segment in its normal sense; it can be used to indicate that memory
+    /// has not been allocated for the specified name.  A subsequent call
+    /// to \c getNamedAddress() will return NULL for that name.
+    ///
+    /// \note Naming an address is intentionally separated from allocation
+    /// so that, for example, one module of a program can name a memory
+    /// region allocated by another module of the program.
+    ///
+    /// There can be an existing association for the name; in that case the
+    /// association will be overridden with the newly given address.
+    ///
+    /// While normally unexpected, it's possible that available space in the
+    /// segment is not sufficient to allocate a space (if not already exist)
+    /// for the specified name in the segment.  In that case, if possible, the
+    /// implementation should try to grow the internal segment and retry
+    /// establishing the association.  The implementation should throw
+    /// std::bad_alloc if even reasonable attempts of retry still fail.
+    ///
+    /// This method should normally return false, but if the internal segment
+    /// had to grow to store the given name, it must return true.  The
+    /// application should interpret it just like the case of
+    /// \c MemorySegmentGrown exception thrown from the \c allocate() method.
+    ///
+    /// \note The behavior in case the internal segment grows is different
+    /// from that of \c allocate().  This is intentional.  In intended use
+    /// cases (for the moment) this method will be called independently,
+    /// rather than as part of a set of allocations.  It's also expected
+    /// that other raw memory addresses (which would have been invalidated
+    /// due to the change to the segment) won't be referenced directly
+    /// immediately after this call.  So, the caller should normally be able
+    /// to call this method as mostly never-fail one (except in case of real
+    /// memory exhaustion) and ignore the return value.
+    ///
+    /// \throw std::bad_alloc Allocation of a segment space for the given name
+    /// failed.
+    /// \throw InvalidParameter name is NULL.
+    /// \throw MemorySegmentError Failure of implementation specific
+    /// validation.
+    ///
+    /// \param name A C string to be associated with \c addr. Must not be NULL.
+    /// \param addr A memory address returned by a prior call to \c allocate.
+    /// \return true if the internal segment has grown to allocate space for
+    /// the name; false otherwise (see above).
+    bool setNamedAddress(const char* name, void* addr) {
+        // This public method implements common validation.  The actual
+        // work specific to the derived segment is delegated to the
+        // corresponding protected method.
+        if (!name) {
+            isc_throw(InvalidParameter,
+                      "NULL name is given to setNamedAddress");
+        }
+        return (setNamedAddressImpl(name, addr));
+    }
+
+    /// \brief Return the address in the segment that has the given name.
+    ///
+    /// This method returns the memory address in the segment corresponding
+    /// to the specified \c name.  The name and address must have been
+    /// associated by a prior call to \c setNameAddress().  If no address
+    /// associated with the given name is found, it returns NULL.
+    ///
+    /// This method should generally be considered exception free, but there
+    /// can be a small chance it throws, depending on the internal
+    /// implementation (e.g., if it converts the name to std::string), so the
+    /// API doesn't guarantee that property.  In general, if this method
+    /// throws it should be considered a fatal condition.
+    ///
+    /// \throw InvalidParameter name is NULL.
+    ///
+    /// \param name A C string of which the segment memory address is to be
+    /// returned.  Must not be NULL.
+    /// \return The address associated with the name, or NULL if not found.
+    void* getNamedAddress(const char* name) {
+        // This public method implements common validation.  The actual
+        // work specific to the derived segment is delegated to the
+        // corresponding protected method.
+        if (!name) {
+            isc_throw(InvalidParameter,
+                      "NULL name is given to getNamedAddress");
+        }
+        return (getNamedAddressImpl(name));
+    }
+
+    /// \brief Delete a name previously associated with a segment address.
+    ///
+    /// This method deletes the association of the given \c name to
+    /// a corresponding segment address previously established by
+    /// \c setNamedAddress().  If there is no association for the given name
+    /// this method returns false; otherwise it returns true.
+    ///
+    /// See \c getNamedAddress() about exception consideration.
+    ///
+    /// \throw InvalidParameter name is NULL.
+    /// \throw MemorySegmentError Failure of implementation specific
+    /// validation.
+    ///
+    /// \param name A C string of which the segment memory address is to be
+    /// deleted. Must not be NULL.
+    bool clearNamedAddress(const char* name) {
+        // This public method implements common validation.  The actual
+        // work specific to the derived segment is delegated to the
+        // corresponding protected method.
+        if (!name) {
+            isc_throw(InvalidParameter,
+                      "NULL name is given to clearNamedAddress");
+        }
+        return (clearNamedAddressImpl(name));
+    }
+
+protected:
+    /// \brief Implementation of setNamedAddress beyond common validation.
+    virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
+
+    /// \brief Implementation of getNamedAddress beyond common validation.
+    virtual void* getNamedAddressImpl(const char* name) = 0;
+
+    /// \brief Implementation of clearNamedAddress beyond common validation.
+    virtual bool clearNamedAddressImpl(const char* name) = 0;
 };
 
 } // namespace util
 } // namespace isc
 
 #endif // MEMORY_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 22 - 1
src/lib/util/memory_segment_local.cc

@@ -48,7 +48,28 @@ MemorySegmentLocal::deallocate(void* ptr, size_t size) {
 
 bool
 MemorySegmentLocal::allMemoryDeallocated() const {
-    return (allocated_size_ == 0);
+    return (allocated_size_ == 0 && named_addrs_.empty());
+}
+
+void*
+MemorySegmentLocal::getNamedAddressImpl(const char* name) {
+    std::map<std::string, void*>::iterator found = named_addrs_.find(name);
+    if (found != named_addrs_.end()) {
+        return (found->second);
+    }
+    return (0);
+}
+
+bool
+MemorySegmentLocal::setNamedAddressImpl(const char* name, void* addr) {
+    named_addrs_[name] = addr;
+    return (false);
+}
+
+bool
+MemorySegmentLocal::clearNamedAddressImpl(const char* name) {
+    const size_t n_erased = named_addrs_.erase(name);
+    return (n_erased != 0);
 }
 
 } // namespace util

+ 32 - 0
src/lib/util/memory_segment_local.h

@@ -17,6 +17,9 @@
 
 #include <util/memory_segment.h>
 
+#include <string>
+#include <map>
+
 namespace isc {
 namespace util {
 
@@ -63,14 +66,43 @@ public:
     /// deallocated, <code>false</code> otherwise.
     virtual bool allMemoryDeallocated() const;
 
+    /// \brief Local segment version of getNamedAddress.
+    ///
+    /// There's a small chance this method could throw std::bad_alloc.
+    /// It should be considered a fatal error.
+    virtual void* getNamedAddressImpl(const char* name);
+
+    /// \brief Local segment version of setNamedAddress.
+    ///
+    /// This version does not validate the given address to see whether it
+    /// belongs to this segment.
+    ///
+    /// This implementation of this method always returns \c false (but the
+    /// application should expect a return value of \c true unless it knows
+    /// the memory segment class is \c MemorySegmentLocal and needs to
+    /// exploit the fact).
+    virtual bool setNamedAddressImpl(const char* name, void* addr);
+
+    /// \brief Local segment version of clearNamedAddress.
+    ///
+    /// There's a small chance this method could throw std::bad_alloc.
+    /// It should be considered a fatal error.
+    virtual bool clearNamedAddressImpl(const char* name);
+
 private:
     // allocated_size_ can underflow, wrap around to max size_t (which
     // is unsigned). But because we only do a check against 0 and not a
     // relation comparison, this is okay.
     size_t allocated_size_;
+
+    std::map<std::string, void*> named_addrs_;
 };
 
 } // namespace util
 } // namespace isc
 
 #endif // MEMORY_SEGMENT_LOCAL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 382 - 0
src/lib/util/memory_segment_mapped.cc

@@ -0,0 +1,382 @@
+// 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>
+
+// 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 {
+// 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();
+    }
+
+    // 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();
+    }
+
+    // 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();
+        }
+    }
+
+    // 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_.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);
+
+        if (!BaseSegment::grow(filename_.c_str(), new_size - prev_size)) {
+            throw std::bad_alloc();
+        }
+
+        try {
+            // Remap the grown 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.
+            base_sgmt_.reset(new BaseSegment(open_only, filename_.c_str()));
+        } catch (const boost::interprocess::interprocess_exception& ex) {
+            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_->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 {
+    return (impl_->base_sgmt_->all_memory_deallocated());
+}
+
+void*
+MemorySegmentMapped::getNamedAddressImpl(const char* name) {
+    offset_ptr<void>* storage =
+        impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
+    if (storage) {
+        return (storage->get());
+    }
+    return (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);
+    }
+
+    bool grown = false;
+    while (true) {
+        offset_ptr<void>* storage =
+            impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
+                name, std::nothrow)();
+        if (storage) {
+            *storage = addr;
+            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 and) close the underlying file.
+    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.
+        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

+ 261 - 0
src/lib/util/memory_segment_mapped.h

@@ -0,0 +1,261 @@
+// 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.
+    ///
+    /// 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 void* getNamedAddressImpl(const char* name);
+
+    /// \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:

+ 6 - 0
src/lib/util/tests/Makefile.am

@@ -34,6 +34,11 @@ run_unittests_SOURCES += lru_list_unittest.cc
 run_unittests_SOURCES += interprocess_sync_file_unittest.cc
 run_unittests_SOURCES += interprocess_sync_null_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 += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc
@@ -41,6 +46,7 @@ run_unittests_SOURCES += socketsession_unittest.cc
 run_unittests_SOURCES += strutil_unittest.cc
 run_unittests_SOURCES += time_utilities_unittest.cc
 run_unittests_SOURCES += range_utilities_unittest.cc
+run_unittests_SOURCES += interprocess_util.h interprocess_util.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 5 - 33
src/lib/util/tests/interprocess_sync_file_unittest.cc

@@ -12,48 +12,20 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "util/interprocess_sync_file.h"
+#include <util/interprocess_sync_file.h>
 
 #include <util/unittests/check_valgrind.h>
+#include <util/tests/interprocess_util.h>
 #include <gtest/gtest.h>
 #include <unistd.h>
 
 using namespace std;
+using isc::util::test::parentReadState;
 
 namespace isc {
 namespace util {
 
 namespace {
-unsigned char
-parentReadLockedState (int fd) {
-  unsigned char locked = 0xff;
-
-  fd_set rfds;
-  FD_ZERO(&rfds);
-  FD_SET(fd, &rfds);
-
-  // We use select() here to wait for new data on the input end of
-  // the pipe. We wait for 5 seconds (an arbitrary value) for input
-  // data, and continue if no data is available. This is done so
-  // that read() is not blocked due to some issue in the child
-  // process (and the tests continue running).
-
-  struct timeval tv;
-  tv.tv_sec = 5;
-  tv.tv_usec = 0;
-
-  const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
-  EXPECT_EQ(1, nfds);
-
-  if (nfds == 1) {
-      // Read status
-      ssize_t bytes_read = read(fd, &locked, sizeof(locked));
-      EXPECT_EQ(sizeof(locked), bytes_read);
-  }
-
-  return (locked);
-}
-
 TEST(InterprocessSyncFileTest, TestLock) {
     InterprocessSyncFile sync("test");
     InterprocessSyncLocker locker(sync);
@@ -99,7 +71,7 @@ TEST(InterprocessSyncFileTest, TestLock) {
             // Parent reads from pipe
             close(fds[1]);
 
-            const unsigned char locked = parentReadLockedState(fds[0]);
+            const unsigned char locked = parentReadState(fds[0]);
 
             close(fds[0]);
 
@@ -163,7 +135,7 @@ TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
             // Parent reads from pipe
             close(fds[1]);
 
-            const unsigned char locked = parentReadLockedState(fds[0]);
+            const unsigned char locked = parentReadState(fds[0]);
 
             close(fds[0]);
 

+ 48 - 0
src/lib/util/tests/interprocess_util.cc

@@ -0,0 +1,48 @@
+// 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 <gtest/gtest.h>
+
+#include <sys/select.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+namespace test {
+
+unsigned char
+parentReadState(int fd) {
+  unsigned char result = 0xff;
+
+  fd_set rfds;
+  FD_ZERO(&rfds);
+  FD_SET(fd, &rfds);
+
+  struct timeval tv = {5, 0};
+
+  const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+  EXPECT_EQ(1, nfds);
+
+  if (nfds == 1) {
+      // Read status
+      const ssize_t bytes_read = read(fd, &result, sizeof(result));
+      EXPECT_EQ(sizeof(result), bytes_read);
+  }
+
+  return (result);
+}
+
+}
+}
+}

+ 31 - 0
src/lib/util/tests/interprocess_util.h

@@ -0,0 +1,31 @@
+// 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.
+
+namespace isc {
+namespace util {
+namespace test {
+/// \brief A helper utility for a simple synchronization with another process.
+///
+/// It waits for incoming data on a given file descriptor up to 5 seconds
+/// (arbitrary choice), read one byte data, and return it to the caller.
+/// On any failure it returns 0xff (255), so the sender process should use
+/// a different value to pass.
+unsigned char parentReadState(int fd);
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:

+ 92 - 0
src/lib/util/tests/memory_segment_common_unittest.cc

@@ -0,0 +1,92 @@
+// 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.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+void
+checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
+    // NULL name is not allowed.
+    EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+
+    // If the name does not exist, NULL should be returned.
+    EXPECT_EQ(static_cast<void*>(NULL),
+              segment.getNamedAddress("test address"));
+
+    // Now set it
+    void* ptr32 = segment.allocate(sizeof(uint32_t));
+    const uint32_t test_val = 42;
+    *static_cast<uint32_t*>(ptr32) = test_val;
+    EXPECT_FALSE(segment.setNamedAddress("test address", ptr32));
+
+    // NULL name isn't allowed.
+    EXPECT_THROW(segment.setNamedAddress(NULL, ptr32), InvalidParameter);
+
+    // we can now get it; the stored value should be intact.
+    EXPECT_EQ(ptr32, segment.getNamedAddress("test address"));
+    EXPECT_EQ(test_val, *static_cast<const uint32_t*>(ptr32));
+
+    // Override it.
+    void* ptr16 = segment.allocate(sizeof(uint16_t));
+    const uint16_t test_val16 = 4200;
+    *static_cast<uint16_t*>(ptr16) = test_val16;
+    EXPECT_FALSE(segment.setNamedAddress("test address", ptr16));
+    EXPECT_EQ(ptr16, segment.getNamedAddress("test address"));
+    EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(ptr16));
+
+    // Clear it.  Then we won't be able to find it any more.
+    EXPECT_TRUE(segment.clearNamedAddress("test address"));
+    EXPECT_EQ(static_cast<void*>(NULL),
+              segment.getNamedAddress("test address"));
+
+    // duplicate attempt of clear will result in false as it doesn't exist.
+    EXPECT_FALSE(segment.clearNamedAddress("test address"));
+
+    // Setting NULL is okay.
+    EXPECT_FALSE(segment.setNamedAddress("null address", NULL));
+    EXPECT_EQ(static_cast<void*>(NULL),
+              segment.getNamedAddress("null address"));
+
+    // If the underlying implementation performs explicit check against
+    // out-of-segment address, confirm the behavior.
+    if (!out_of_segment_ok) {
+        uint8_t ch = 'A';
+        EXPECT_THROW(segment.setNamedAddress("local address", &ch),
+                     MemorySegmentError);
+    }
+
+    // clean them up all
+    segment.deallocate(ptr32, sizeof(uint32_t));
+    EXPECT_FALSE(segment.allMemoryDeallocated()); // not fully deallocated
+    segment.deallocate(ptr16, sizeof(uint16_t));  // not yet
+    EXPECT_FALSE(segment.allMemoryDeallocated());
+    EXPECT_TRUE(segment.clearNamedAddress("null address"));
+    // null name isn't allowed:
+    EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+    EXPECT_TRUE(segment.allMemoryDeallocated()); // now everything is gone
+}
+
+}
+}
+}

+ 36 - 0
src/lib/util/tests/memory_segment_common_unittest.h

@@ -0,0 +1,36 @@
+// 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.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+/// \brief Implementation dependent checks on memory segment named addresses.
+///
+/// This function contains a set of test cases for given memory segment
+/// regarding "named address" methods.  The test cases basically only depend
+/// on the base class interfaces, but if the underlying implementation does
+/// not check if the given address to setNamedAddress() belongs to the segment,
+/// out_of_segment_ok should be set to true.
+void checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok);
+
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:

+ 8 - 1
src/lib/util/tests/memory_segment_local_unittest.cc

@@ -12,7 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "util/memory_segment_local.h"
+#include <util/tests/memory_segment_common_unittest.h>
+
+#include <util/memory_segment_local.h>
 #include <exceptions/exceptions.h>
 #include <gtest/gtest.h>
 #include <memory>
@@ -106,4 +108,9 @@ TEST(MemorySegmentLocal, TestNullDeallocate) {
     EXPECT_TRUE(segment->allMemoryDeallocated());
 }
 
+TEST(MemorySegmentLocal, namedAddress) {
+    MemorySegmentLocal segment;
+    isc::util::test::checkSegmentNamedAddress(segment, true);
+}
+
 } // anonymous namespace

+ 620 - 0
src/lib/util/tests/memory_segment_mapped_unittest.cc

@@ -0,0 +1,620 @@
+// 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/tests/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::test::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) {
+    // Make the mapped file non-writable; managed_mapped_file::grow() will
+    // fail, resulting in std::bad_alloc
+    const int ret = chmod(mapped_file, 0444);
+    ASSERT_EQ(0, ret);
+
+    EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE * 2), std::bad_alloc);
+}
+
+// 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)
+{
+    void* dp = sgmt.getNamedAddress(name.c_str());
+    ASSERT_TRUE(dp);
+    EXPECT_EQ(0, std::memcmp(dp, &data[0], data.size()));
+
+    if (delete_after_check) {
+        sgmt.deallocate(dp, 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));
+    EXPECT_NE(static_cast<void*>(NULL),
+              segment_->getNamedAddress("test address"));
+    EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(
+                  segment_->getNamedAddress("test address")));
+
+    // 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));
+    EXPECT_EQ(static_cast<void*>(NULL),
+              segment_->getNamedAddress(long_name.c_str()));
+
+    // 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);
+        void* ptr_child = sgmt.getNamedAddress("test address");
+        EXPECT_TRUE(ptr_child);
+        if (ptr_child) {
+            const uint32_t val = *static_cast<const uint32_t*>(ptr_child);
+            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));
+    ptr = segment_->getNamedAddress("test address");
+    ASSERT_TRUE(ptr);
+    EXPECT_EQ(424242, *static_cast<const uint32_t*>(ptr));
+    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 = segment_->allocate(sizeof(uint32_t));
+    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);
+                EXPECT_TRUE(segment_ro.getNamedAddress("test address"));
+                *static_cast<uint32_t*>(
+                    segment_ro.getNamedAddress("test address")) = 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);
+    ptr = segment_ro.getNamedAddress("test address");
+    EXPECT_NE(static_cast<void*>(NULL), ptr);
+
+    EXPECT_THROW(segment_ro.deallocate(ptr, 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);
+}
+
+}