Browse Source

Merge branch 'trac2850_2'

Mukund Sivaraman 12 years ago
parent
commit
650c6ce282
38 changed files with 1926 additions and 403 deletions
  1. 3 0
      configure.ac
  2. 9 8
      src/lib/datasrc/client_list.cc
  3. 6 2
      src/lib/datasrc/memory/Makefile.am
  4. 10 0
      src/lib/datasrc/memory/zone_table_segment.cc
  5. 229 31
      src/lib/datasrc/memory/zone_table_segment.h
  6. 28 9
      src/lib/datasrc/memory/zone_table_segment_local.cc
  7. 55 14
      src/lib/datasrc/memory/zone_table_segment_local.h
  8. 386 0
      src/lib/datasrc/memory/zone_table_segment_mapped.cc
  9. 150 0
      src/lib/datasrc/memory/zone_table_segment_mapped.h
  10. 27 19
      src/lib/datasrc/memory/zone_writer_local.cc
  11. 45 16
      src/lib/datasrc/memory/zone_writer.h
  12. 0 95
      src/lib/datasrc/memory/zone_writer_local.h
  13. 2 1
      src/lib/datasrc/tests/client_list_unittest.cc
  14. 8 2
      src/lib/datasrc/tests/memory/Makefile.am
  15. 6 6
      src/lib/datasrc/tests/memory/memory_client_unittest.cc
  16. 2 2
      src/lib/datasrc/tests/memory/memory_segment_test.h
  17. 3 3
      src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
  18. 2 2
      src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
  19. 3 4
      src/lib/datasrc/tests/memory/zone_data_unittest.cc
  20. 2 2
      src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
  21. 5 5
      src/lib/datasrc/tests/memory/zone_finder_unittest.cc
  22. 10 15
      src/lib/datasrc/tests/memory/zone_loader_util.cc
  23. 590 0
      src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
  24. 31 48
      src/lib/datasrc/tests/memory/zone_table_segment_test.h
  25. 38 16
      src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
  26. 3 3
      src/lib/datasrc/tests/memory/zone_table_unittest.cc
  27. 81 22
      src/lib/datasrc/tests/memory/zone_writer_unittest.cc
  28. 6 8
      src/lib/datasrc/tests/zone_finder_context_unittest.cc
  29. 3 3
      src/lib/datasrc/tests/zone_loader_unittest.cc
  30. 15 5
      src/lib/util/memory_segment.h
  31. 7 6
      src/lib/util/memory_segment_local.cc
  32. 2 2
      src/lib/util/memory_segment_local.h
  33. 68 7
      src/lib/util/memory_segment_mapped.cc
  34. 2 2
      src/lib/util/memory_segment_mapped.h
  35. 8 2
      src/lib/util/random/random_number_generator.h
  36. 13 11
      src/lib/util/tests/memory_segment_common_unittest.cc
  37. 42 24
      src/lib/util/tests/memory_segment_mapped_unittest.cc
  38. 26 8
      src/lib/util/tests/random_number_generator_unittest.cc

+ 3 - 0
configure.ac

@@ -891,6 +891,9 @@ if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes"; th
     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)
 
 # Add some default CPP flags needed for Boost, identified by the AX macro.

+ 9 - 8
src/lib/datasrc/client_list.cc

@@ -153,13 +153,13 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
                               << zname << "/" << rrclass_);
                 }
                 assert(load_action); // in this loop this should be always true
-                boost::scoped_ptr<memory::ZoneWriter> writer;
                 try {
-                    writer.reset(new_data_sources.back().ztable_segment_->
-                                 getZoneWriter(load_action, zname, rrclass_));
-                    writer->load();
-                    writer->install();
-                    writer->cleanup();
+                    memory::ZoneWriter writer(
+                        *new_data_sources.back().ztable_segment_,
+                        load_action, zname, rrclass_);
+                    writer.load();
+                    writer.install();
+                    writer.cleanup();
                 } catch (const ZoneLoaderException& e) {
                     LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR)
                         .arg(zname).arg(rrclass_).arg(name).arg(e.what());
@@ -348,8 +348,9 @@ ConfigurableClientList::getCachedZoneWriter(const Name& name) {
     }
     return (ZoneWriterPair(ZONE_SUCCESS,
                            ZoneWriterPtr(
-                               result.info->ztable_segment_->
-                               getZoneWriter(load_action, name, rrclass_))));
+                               new memory::ZoneWriter(
+                                   *result.info->ztable_segment_,
+                                   load_action, name, rrclass_))));
 }
 
 // NOTE: This function is not tested, it would be complicated. However, the

+ 6 - 2
src/lib/datasrc/memory/Makefile.am

@@ -22,11 +22,15 @@ libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
 libdatasrc_memory_la_SOURCES += zone_table_segment.h zone_table_segment.cc
 libdatasrc_memory_la_SOURCES += zone_table_segment_local.h zone_table_segment_local.cc
+
+if USE_SHARED_MEMORY
+libdatasrc_memory_la_SOURCES += zone_table_segment_mapped.h zone_table_segment_mapped.cc
+endif
+
 libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc
 libdatasrc_memory_la_SOURCES += zone_data_loader.h zone_data_loader.cc
 libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
-libdatasrc_memory_la_SOURCES += zone_writer.h
-libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
+libdatasrc_memory_la_SOURCES += zone_writer.h zone_writer.cc
 libdatasrc_memory_la_SOURCES += load_action.h
 libdatasrc_memory_la_SOURCES += util_internal.h
 

+ 10 - 0
src/lib/datasrc/memory/zone_table_segment.cc

@@ -12,8 +12,14 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include "config.h"
+
 #include <datasrc/memory/zone_table_segment.h>
 #include <datasrc/memory/zone_table_segment_local.h>
+#ifdef USE_SHARED_MEMORY
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#endif
+#include <datasrc/memory/zone_writer.h>
 
 #include <string>
 
@@ -30,6 +36,10 @@ ZoneTableSegment::create(const RRClass& rrclass, const std::string& type) {
     // Until that it becomes a real issue we won't be too smart.
     if (type == "local") {
         return (new ZoneTableSegmentLocal(rrclass));
+#ifdef USE_SHARED_MEMORY
+    } else if (type == "mapped") {
+        return (new ZoneTableSegmentMapped(rrclass));
+#endif
     }
     isc_throw(UnknownSegmentType, "Zone table segment type not supported: "
               << type);

+ 229 - 31
src/lib/datasrc/memory/zone_table_segment.h

@@ -40,8 +40,8 @@ namespace datasrc {
 namespace memory {
 class ZoneWriter;
 
-/// \brief Exception thrown when unknown or unsupported type of zone table
-/// segment is specified.
+/// \brief Exception thrown when unknown or unsupported type of
+/// ZoneTableSegment is asked to be created.
 class UnknownSegmentType : public Exception {
 public:
     UnknownSegmentType(const char* file, size_t line, const char* what) :
@@ -49,12 +49,36 @@ public:
     {}
 };
 
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons). When this exception is thrown, a
+/// strong exception safety guarantee is provided, and the
+/// \c ZoneTableSegment is usable as before.
+class ResetFailed : public isc::Exception {
+public:
+    ResetFailed(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons), and it was not able to preserve the
+/// state of the \c ZoneTableSegment. When this exception is thrown,
+/// only basic exception safety guarantee is provided and the
+/// \c ZoneTableSegment must be expected as cleared.
+class ResetFailedAndSegmentCleared : public isc::Exception {
+public:
+    ResetFailedAndSegmentCleared(const char* file, size_t line,
+                                 const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
 /// \brief Memory-management independent entry point that contains a
 /// pointer to a zone table in memory.
 ///
-/// An instance of this type lives inside a ZoneTableSegment
-/// implementation. It contains an offset pointer to the zone table (a
-/// map from domain names to zone locators) in memory.
+/// An instance of this type lives inside a \c ZoneTableSegment
+/// implementation. It contains an offset pointer to the \c ZoneTable (a
+/// map from domain names to zone locators) in the \c ZoneTableSegment.
 struct ZoneTableHeader {
 public:
     ZoneTableHeader(ZoneTable* zone_table) :
@@ -74,14 +98,20 @@ private:
     boost::interprocess::offset_ptr<ZoneTable> table_;
 };
 
-/// \brief Manages a ZoneTableHeader, an entry point into a table of
+/// \brief Manages a \c ZoneTableHeader, an entry point into a table of
 /// zones
 ///
 /// This class specifies an interface for derived implementations which
-/// return a pointer to an object of type ZoneTableHeader, an entry
+/// return a pointer to an object of type \c ZoneTableHeader, an entry
 /// point into a table of zones regardless of the underlying memory
-/// management implementation. Derived classes would implement the
-/// interface for specific memory-implementation behavior.
+/// management implementation. Derived classes implement the interface
+/// for the specific memory-implementation behavior.
+///
+/// Note: At some point in the future, methods such as \c reset(),
+/// \c clear(), \c resetHeader(), \c getHeader(), \c isWritable(),
+/// \c isUsable() may become non-virtual methods. Such a change should
+/// not affect any code that uses this class, but please be aware of
+/// such plans.
 class ZoneTableSegment {
 protected:
     /// \brief Protected constructor
@@ -89,24 +119,63 @@ protected:
     /// An instance implementing this interface is expected to be
     /// created by the factory method (\c create()), so this constructor
     /// is protected.
-    ZoneTableSegment(isc::dns::RRClass)
+    ZoneTableSegment(const isc::dns::RRClass&)
     {}
 public:
     /// \brief Destructor
     virtual ~ZoneTableSegment() {}
 
-    /// \brief Return the ZoneTableHeader for the zone table segment.
+    /// \brief Return a string name for the \c ZoneTableSegment
+    /// implementation.
+    ///
+    /// \throw None This method's implementations must be
+    /// exception-free.
+    virtual const std::string& getImplType() const = 0;
+
+    /// \brief Return the \c ZoneTableHeader for the zone table segment.
+    ///
+    /// As long as \c isUsable() returns true, this method must always
+    /// succeed without throwing an exception.  If \c isUsable() returns
+    /// false, a derived class implementation can throw
+    /// \c isc::InvalidOperation depending on its implementation
+    /// details. Applications are generally expected to call this
+    /// method only when \c isUsable() returns true (either by making
+    /// sure explicitly or by some other indirect means).
+    ///
+    /// \throw isc::InvalidOperation may be thrown by some
+    /// implementations if this method is called without calling
+    /// \c reset() successfully first.
     virtual ZoneTableHeader& getHeader() = 0;
 
-    /// \brief const version of \c getHeader().
+    /// \brief \c const version of \c getHeader().
+    ///
+    /// See the non- \c const version for documentation.
     virtual const ZoneTableHeader& getHeader() const = 0;
 
     /// \brief Return the MemorySegment for the zone table segment.
+    ///
+    /// \throw isc::InvalidOperation may be thrown by some
+    /// implementations if this method is called without calling
+    /// \c reset() successfully first.
     virtual isc::util::MemorySegment& getMemorySegment() = 0;
 
-    /// \brief Create an instance depending on the memory segment model
+    /// \brief Return true if the segment is writable.
+    ///
+    /// The user of the zone table segment will load or update zones
+    /// into the segment only for writable ones.  The precise definition
+    /// of "writability" differs in different derived classes (see
+    /// derived class documentation).  In general, however, the user
+    /// should only rely on this interface rather than assume a specific
+    /// definition for a specific type of segment.
     ///
-    /// This is a factory method to create a derived ZoneTableSegment
+    /// \throw None This method's implementations must be
+    /// exception-free.
+    virtual bool isWritable() const = 0;
+
+    /// \brief Create an instance depending on the requested memory
+    /// segment implementation type.
+    ///
+    /// This is a factory method to create a derived \c ZoneTableSegment
     /// object based on the \c config passed. The method returns a
     /// dynamically-allocated object. The caller is responsible for
     /// destroying it with \c ZoneTableSegment::destroy().
@@ -115,33 +184,162 @@ public:
     /// \c config is not known or not supported in this implementation.
     ///
     /// \param rrclass The RR class of the zones to be maintained in the table.
-    /// \param type The memory segment type used for the zone table segment.
-    /// \return Returns a ZoneTableSegment object of the specified type.
+    /// \param type The memory segment type to be used.
+    /// \return Returns a \c ZoneTableSegment object of the specified type.
     static ZoneTableSegment* create(const isc::dns::RRClass& rrclass,
                                     const std::string& type);
 
-    /// \brief Destroy a ZoneTableSegment
+    /// \brief Destroy a \c ZoneTableSegment
     ///
-    /// This method destroys the passed ZoneTableSegment. It must be
-    /// passed a segment previously created by \c ZoneTableSegment::create().
+    /// This method destroys the passed \c ZoneTableSegment. It must be
+    /// passed a segment previously created by
+    /// \c ZoneTableSegment::create().
     ///
     /// \param segment The segment to destroy.
     static void destroy(ZoneTableSegment* segment);
 
-    /// \brief Create a zone write corresponding to this segment
+    /// \brief The mode using which to create a MemorySegment.
+    ///
+    /// Here, a \c MemorySegment (see its class documentation) is an
+    /// interface to a storage area, and provides operations to allocate
+    /// and deallocate from that storage area, and also to look up
+    /// addresses in that area. The storage area can be a buffer in
+    /// memory, a file on disk, or some kind of shared memory depending
+    /// on the \c MemorySegment implementation being used.  In every
+    /// case in the documentation below, when we mention \c
+    /// MemorySegment, we mean both the \c MemorySegment object which
+    /// interfaces to the storage area and the contents of the
+    /// associated storage area.
+    ///
+    /// - CREATE: If the \c MemorySegment's storage area doesn't exist,
+    ///           create it. If it exists, overwrite it with a new
+    ///           storage area (which does not remember old data). In
+    ///           both cases, create a \c MemorySegment for it in
+    ///           read+write mode.
     ///
-    /// This creates a new write that can be used to update zones
-    /// inside this zone table segment.
+    /// - READ_WRITE: If the \c MemorySegment's storage area doesn't
+    ///               exist, create it. If it exists, use the existing
+    ///               storage area as-is (keeping the existing data
+    ///               intact). In both cases, create a \c MemorySegment
+    ///               for it in read+write mode.
+    ///
+    /// - READ_ONLY: If the \c MemorySegment's storage area doesn't
+    ///              exist, throw an exception. If it exists, create a
+    ///              \c MemorySegment for it in read-only mode.
+    enum MemorySegmentOpenMode {
+        CREATE,
+        READ_WRITE,
+        READ_ONLY
+    };
+
+    /// \brief Close the current \c MemorySegment (if open) and open the
+    /// requested one.
+    ///
+    /// When we talk about "opening" a \c MemorySegment, it means to
+    /// construct a usable \c MemorySegment object that interfaces to
+    /// the actual memory storage area. "Closing" is the opposite
+    /// operation of opening.
+    ///
+    /// In case opening the new \c MemorySegment fails for some reason,
+    /// one of the following documented (further below) exceptions may
+    /// be thrown. In case failures occur, implementations of this
+    /// method must strictly provide the associated behavior as follows
+    /// and in the exception documentation below.  Code that uses
+    /// \c ZoneTableSegment would depend on such assurances.
+    ///
+    /// First, in case a \c ZoneTableSegment was reset successfully
+    /// before and is currently usable (\c isUsable() returns true), and
+    /// an invalid configuration is passed in \c params to \c reset(),
+    /// the isc::InvalidParameter exception must be thrown. In this
+    /// case, a strong exception safety guarantee must be provided, and
+    /// the \c ZoneTableSegment must be usable as before.
+    ///
+    /// In case a \c ZoneTableSegment was reset successfully before and
+    /// is currently usable (\c isUsable() returns true), and the attempt
+    /// to reset to a different \c MemorySegment storage area fails,
+    /// the \c ResetFailed exception must be thrown. In this
+    /// case, a strong exception safety guarantee must be provided, and
+    /// the \c ZoneTableSegment must be usable as before.
+    ///
+    /// In case a \c ZoneTableSegment was reset successfully before and
+    /// is currently usable (\c isUsable() returns true), and the attempt
+    /// to reset to the same \c MemorySegment storage area fails, the
+    /// \c ResetFailedAndSegmentCleared exception must be thrown. In
+    /// this case, only basic exception safety guarantee is provided and
+    /// the \c ZoneTableSegment must be expected as cleared.
+    ///
+    /// In case a \c ZoneTableSegment was not reset successfully before
+    /// and is currently not usable (\c isUsable() returns false), and
+    /// the attempt to reset fails, the \c ResetFailed exception must be
+    /// thrown. In this unique case, a strong exception safety guarantee
+    /// is provided by default, as the \c ZoneTableSegment was clear
+    /// previously, and remains cleared.
+    ///
+    /// In all other cases, \c ZoneTableSegment contents can be expected
+    /// as reset.
+    ///
+    /// See \c MemorySegmentOpenMode for a definition of "storage area"
+    /// and the various modes in which a \c MemorySegment can be opened.
+    ///
+    /// \c params should contain an implementation-defined
+    /// configuration. See the specific \c ZoneTableSegment
+    /// implementation class for details of what to pass in this
+    /// argument.
+    ///
+    /// \throw isc::InvalidParameter if the configuration in \c params
+    /// has incorrect syntax, but there is a strong exception safety
+    /// guarantee and the \c ZoneTableSegment is usable or unusable as
+    /// before.
+    ///
+    /// \throw ResetFailed if there was a problem in opening the new
+    /// memory store, but there is a strong exception safety guarantee
+    /// and the \c ZoneTableSegment is usable or unusable as before.
+    ///
+    /// \throw ResetFailedAndSegmentCleared if there was a problem in
+    /// opening the new memory store, but there is only a basic
+    /// exception safety guarantee and the \c ZoneTableSegment is not
+    /// usable without a further successful \c reset().
+    ///
+    /// \throw isc::NotImplemented Some implementations may choose to
+    /// not implement this method. In this case, there must be a strong
+    /// exception safety guarantee and the \c ZoneTableSegment is usable
+    /// or unusable as before.
+    ///
+    /// \param mode The open mode (see the MemorySegmentOpenMode
+    /// documentation).
+    /// \param params An element containing implementation-specific
+    /// config (see the description).
+    virtual void reset(MemorySegmentOpenMode mode,
+                       isc::data::ConstElementPtr params) = 0;
+
+    /// \brief Close the currently configured \c MemorySegment (if
+    /// open).
+    ///
+    /// See the \c reset() method's documentation for a definition of
+    /// "open" and "close".
+    ///
+    /// Implementations of this method should close any currently
+    /// configured \c MemorySegment and clear the `ZoneTableSegment` to
+    /// a freshly constructed state.
+    ///
+    /// \throw isc::NotImplemented Some implementations may choose to
+    /// not implement this method. In this case, there must be a strong
+    /// exception safety guarantee and the \c ZoneTableSegment is usable
+    /// or unusable as before.
+    virtual void clear() = 0;
+
+    /// \brief Return true if the \c ZoneTableSegment has been
+    /// successfully \c reset().
+    ///
+    /// Note that after calling \c clear(), this method will return
+    /// false until the segment is reset successfully again.
+    virtual bool isUsable() const = 0;
+
+    /// \brief Reset the table header address.
     ///
-    /// \param loadAction Callback to provide the actual data.
-    /// \param origin The origin of the zone to reload.
-    /// \param rrclass The class of the zone to reload.
-    /// \return New instance of a zone writer. The ownership is passed
-    ///     onto the caller and the caller needs to \c delete it when
-    ///     it's done with the writer.
-    virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
-                                      const dns::Name& origin,
-                                      const dns::RRClass& rrclass) = 0;
+    /// This method must recalculate the \c ZoneTableHeader address, so
+    /// that it is valid when requested using the \c getHeader() method.
+    virtual void resetHeader() = 0;
 };
 
 } // namespace memory

+ 28 - 9
src/lib/datasrc/memory/zone_table_segment_local.cc

@@ -13,7 +13,6 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <datasrc/memory/zone_table_segment_local.h>
-#include "zone_writer_local.h"
 
 using namespace isc::dns;
 using namespace isc::util;
@@ -24,6 +23,7 @@ namespace memory {
 
 ZoneTableSegmentLocal::ZoneTableSegmentLocal(const RRClass& rrclass) :
     ZoneTableSegment(rrclass),
+    impl_type_("local"),
     header_(ZoneTable::create(mem_sgmt_, rrclass))
 {
 }
@@ -38,6 +38,33 @@ ZoneTableSegmentLocal::~ZoneTableSegmentLocal() {
     assert(mem_sgmt_.allMemoryDeallocated());
 }
 
+const std::string&
+ZoneTableSegmentLocal::getImplType() const {
+    return (impl_type_);
+}
+
+void
+ZoneTableSegmentLocal::reset(MemorySegmentOpenMode,
+                             isc::data::ConstElementPtr)
+{
+    isc_throw(isc::NotImplemented,
+              "ZoneTableSegmentLocal::reset() is not implemented and "
+              "should not be used.");
+}
+
+void
+ZoneTableSegmentLocal::clear()
+{
+    isc_throw(isc::NotImplemented,
+              "ZoneTableSegmentLocal::clear() is not implemented and "
+              "should not be used.");
+}
+
+void
+ZoneTableSegmentLocal::resetHeader() {
+    // This method does not have to do anything in this implementation.
+}
+
 // After more methods' definitions are added here, it would be a good
 // idea to move getHeader() and getMemorySegment() definitions to the
 // header file.
@@ -56,14 +83,6 @@ ZoneTableSegmentLocal::getMemorySegment() {
      return (mem_sgmt_);
 }
 
-ZoneWriter*
-ZoneTableSegmentLocal::getZoneWriter(const LoadAction& load_action,
-                                     const dns::Name& name,
-                                     const dns::RRClass& rrclass)
-{
-    return (new ZoneWriterLocal(this, load_action, name, rrclass));
-}
-
 } // namespace memory
 } // namespace datasrc
 } // namespace isc

+ 55 - 14
src/lib/datasrc/memory/zone_table_segment_local.h

@@ -18,19 +18,22 @@
 #include <datasrc/memory/zone_table_segment.h>
 #include <util/memory_segment_local.h>
 
+#include <string>
+
 namespace isc {
 namespace datasrc {
 namespace memory {
 
-/// \brief Local implementation of ZoneTableSegment class
+/// \brief Local implementation of \c ZoneTableSegment class
 ///
 /// This class specifies a concrete implementation for a
-/// MemorySegmentLocal based ZoneTableSegment. Please see the
-/// ZoneTableSegment class documentation for usage.
+/// \c MemorySegmentLocal -based \c ZoneTableSegment. Please see the
+/// \c ZoneTableSegment class documentation for usage.
 class ZoneTableSegmentLocal : public ZoneTableSegment {
-    // This is so that ZoneTableSegmentLocal can be instantiated from
-    // ZoneTableSegment::create().
+    // This is so that \c ZoneTableSegmentLocal can be instantiated from
+    // \c ZoneTableSegment::create().
     friend class ZoneTableSegment;
+
 protected:
     /// \brief Protected constructor
     ///
@@ -38,26 +41,64 @@ protected:
     /// (\c ZoneTableSegment::create()), so this constructor is
     /// protected.
     ZoneTableSegmentLocal(const isc::dns::RRClass& rrclass);
+
 public:
     /// \brief Destructor
     virtual ~ZoneTableSegmentLocal();
 
-    /// \brief Return the ZoneTableHeader for the local zone table
-    /// segment implementation.
+    /// \brief Returns "local" as the implementation type.
+    virtual const std::string& getImplType() const;
+
+    /// \brief This method does not have to do anything in this
+    /// implementation. It has an empty definition.
+    virtual void resetHeader();
+
+    /// \brief Return the \c ZoneTableHeader for this local zone table
+    /// segment.
     virtual ZoneTableHeader& getHeader();
 
-    /// \brief const version of \c getHeader().
+    /// \brief \c const version of \c getHeader().
     virtual const ZoneTableHeader& getHeader() const;
 
-    /// \brief Return the MemorySegment for the local zone table segment
-    /// implementation (a MemorySegmentLocal instance).
+    /// \brief Return the \c MemorySegment for the local zone table
+    /// segment implementation (a \c MemorySegmentLocal instance).
     virtual isc::util::MemorySegment& getMemorySegment();
 
-    /// \brief Concrete implementation of ZoneTableSegment::getZoneWriter
-    virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
-                                      const dns::Name& origin,
-                                      const dns::RRClass& rrclass);
+    /// \brief Return true if the segment is writable.
+    ///
+    /// Local segments are always writable. This implementation always
+    /// returns true.
+    virtual bool isWritable() const {
+        return (true);
+    }
+
+    /// \brief This method is not implemented.
+    ///
+    /// Resetting a local \c ZoneTableSegment is not supported at this
+    /// time.
+    ///
+    /// \throw isc::NotImplemented
+    virtual void reset(MemorySegmentOpenMode mode,
+                       isc::data::ConstElementPtr params);
+
+    /// \brief This method is not implemented.
+    ///
+    /// Clearing a local \c ZoneTableSegment is not supported at this
+    /// time.
+    ///
+    /// \throw isc::NotImplemented
+    virtual void clear();
+
+    /// \brief Return true if the segment is usable.
+    ///
+    /// Local segments are always usable. This implementation always
+    /// returns true.
+    virtual bool isUsable() const {
+        return (true);
+    }
+
 private:
+    std::string impl_type_;
     isc::util::MemorySegmentLocal mem_sgmt_;
     ZoneTableHeader header_;
 };

+ 386 - 0
src/lib/datasrc/memory/zone_table_segment_mapped.cc

@@ -0,0 +1,386 @@
+// 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 <datasrc/memory/zone_table_segment_mapped.h>
+
+#include <memory>
+
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::util;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace { // unnamed namespace
+
+// The name with which the zone table checksum is associated in the segment.
+const char* const ZONE_TABLE_CHECKSUM_NAME = "zone_table_checksum";
+
+// The name with which the zone table header is associated in the segment.
+const char* const ZONE_TABLE_HEADER_NAME = "zone_table_header";
+
+} // end of unnamed namespace
+
+ZoneTableSegmentMapped::ZoneTableSegmentMapped(const RRClass& rrclass) :
+    ZoneTableSegment(rrclass),
+    impl_type_("mapped"),
+    rrclass_(rrclass),
+    cached_header_(NULL)
+{
+}
+
+ZoneTableSegmentMapped::~ZoneTableSegmentMapped() {
+    sync();
+}
+
+const std::string&
+ZoneTableSegmentMapped::getImplType() const {
+    return (impl_type_);
+}
+
+bool
+ZoneTableSegmentMapped::processChecksum(MemorySegmentMapped& segment,
+                                        bool create,
+                                        std::string& error_msg)
+{
+    const MemorySegment::NamedAddressResult result =
+        segment.getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+    if (result.first) {
+        if (create) {
+            // There must be no previously saved checksum.
+            error_msg = "There is already a saved checksum in the segment "
+                 "opened in create mode";
+            return (false);
+        } else {
+            // The segment was already shrunk when it was last
+            // closed. Check that its checksum is consistent.
+            assert(result.second);
+            size_t* checksum = static_cast<size_t*>(result.second);
+            const size_t saved_checksum = *checksum;
+            // First, clear the checksum so that getCheckSum() returns a
+            // consistent value.
+            *checksum = 0;
+            const size_t new_checksum = segment.getCheckSum();
+            if (saved_checksum != new_checksum) {
+                error_msg = "Saved checksum doesn't match segment data";
+                return (false);
+            }
+        }
+    } else {
+        // Allocate space for a checksum (which is saved during close).
+        void* checksum = NULL;
+        while (!checksum) {
+            try {
+                checksum = segment.allocate(sizeof(size_t));
+            } catch (const MemorySegmentGrown&) {
+                // Do nothing and try again.
+            }
+        }
+        *static_cast<size_t*>(checksum) = 0;
+        const bool grew = segment.setNamedAddress(ZONE_TABLE_CHECKSUM_NAME,
+                                                  checksum);
+        assert(!grew);
+    }
+
+    return (true);
+}
+
+bool
+ZoneTableSegmentMapped::processHeader(MemorySegmentMapped& segment,
+                                      bool create,
+                                      std::string& error_msg)
+{
+    const MemorySegment::NamedAddressResult result =
+        segment.getNamedAddress(ZONE_TABLE_HEADER_NAME);
+    if (result.first) {
+        if (create) {
+            // There must be no previously saved checksum.
+            error_msg = "There is already a saved ZoneTableHeader in the "
+                 "segment opened in create mode";
+            return (false);
+        } else {
+            assert(result.second);
+        }
+    } else {
+        void* ptr = NULL;
+        while (!ptr) {
+            try {
+                ptr = segment.allocate(sizeof(ZoneTableHeader));
+            } catch (const MemorySegmentGrown&) {
+                // Do nothing and try again.
+            }
+        }
+        try {
+            ZoneTableHeader* new_header = new(ptr)
+                ZoneTableHeader(ZoneTable::create(segment, rrclass_));
+            const bool grew = segment.setNamedAddress(ZONE_TABLE_HEADER_NAME,
+                                                      new_header);
+            assert(!grew);
+        } catch (const MemorySegmentGrown&) {
+            // This is extremely unlikely and we just throw a fatal
+            // exception here without attempting to recover.
+
+            throw std::bad_alloc();
+        }
+    }
+
+    return (true);
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadWrite(const std::string& filename,
+                                      bool create)
+{
+    const MemorySegmentMapped::OpenMode mode = create ?
+         MemorySegmentMapped::CREATE_ONLY :
+         MemorySegmentMapped::OPEN_OR_CREATE;
+    // In case there is a problem, we throw. We want the segment to be
+    // automatically destroyed then.
+    std::auto_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(filename, mode));
+
+    std::string error_msg;
+    if ((!processChecksum(*segment, create, error_msg)) ||
+        (!processHeader(*segment, create, error_msg))) {
+         if (mem_sgmt_) {
+              isc_throw(ResetFailed,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         } else {
+              isc_throw(ResetFailedAndSegmentCleared,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         }
+    }
+
+    return (segment.release());
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadOnly(const std::string& filename) {
+    // In case the checksum or table header is missing, we throw. We
+    // want the segment to be automatically destroyed then.
+    std::auto_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(filename));
+    // There must be a previously saved checksum.
+    MemorySegment::NamedAddressResult result =
+        segment->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+    if (!result.first) {
+         const std::string error_msg =
+             "There is no previously saved checksum in a "
+             "mapped segment opened in read-only mode";
+         if (mem_sgmt_) {
+              isc_throw(ResetFailed,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         } else {
+              isc_throw(ResetFailedAndSegmentCleared,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         }
+    }
+
+    // We can't verify the checksum here as we can't set the checksum to
+    // 0 for checksum calculation in a read-only segment. So we continue
+    // without verifying the checksum.
+
+    // There must be a previously saved ZoneTableHeader.
+    result = segment->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+    if (result.first) {
+        assert(result.second);
+    } else {
+         const std::string error_msg =
+             "There is no previously saved ZoneTableHeader in a "
+             "mapped segment opened in read-only mode.";
+         if (mem_sgmt_) {
+              isc_throw(ResetFailed,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         } else {
+              isc_throw(ResetFailedAndSegmentCleared,
+                        "Error in resetting zone table segment to use "
+                        << filename << ": " << error_msg);
+         }
+    }
+
+    return (segment.release());
+}
+
+void
+ZoneTableSegmentMapped::reset(MemorySegmentOpenMode mode,
+                              isc::data::ConstElementPtr params)
+{
+    if (!params || params->getType() != Element::map) {
+        isc_throw(isc::InvalidParameter,
+                  "Configuration does not contain a map");
+    }
+
+    if (!params->contains("mapped-file")) {
+        isc_throw(isc::InvalidParameter,
+                  "Configuration does not contain a \"mapped-file\" key");
+    }
+
+    ConstElementPtr mapped_file = params->get("mapped-file");
+    if ((!mapped_file) || (mapped_file->getType() != Element::string)) {
+        isc_throw(isc::InvalidParameter,
+                  "Value of \"mapped-file\" is not a string");
+    }
+
+    const std::string filename = mapped_file->stringValue();
+
+    if (mem_sgmt_ && (filename == current_filename_)) {
+        // This reset() is an attempt to re-open the currently open
+        // mapped file. We cannot do this in many mode combinations
+        // unless we close the existing mapped file. So just close it.
+        clear();
+    } else {
+        sync();
+    }
+
+    // In case current_filename_ below fails, we want the segment to be
+    // automatically destroyed.
+    std::auto_ptr<MemorySegmentMapped> segment;
+
+    switch (mode) {
+    case CREATE:
+        segment.reset(openReadWrite(filename, true));
+        break;
+
+    case READ_WRITE:
+        segment.reset(openReadWrite(filename, false));
+        break;
+
+    case READ_ONLY:
+        segment.reset(openReadOnly(filename));
+        break;
+
+    default:
+        isc_throw(isc::InvalidOperation,
+                  "Invalid MemorySegmentOpenMode passed to reset()");
+    }
+
+    current_filename_ = filename;
+    current_mode_ = mode;
+    mem_sgmt_.reset(segment.release());
+
+    // Given what we setup above, resetHeader() must not throw at this
+    // point. If it does, all bets are off.
+    resetHeader();
+}
+
+void
+ZoneTableSegmentMapped::sync() {
+    // Synchronize checksum, etc.
+    if (mem_sgmt_ && isWritable()) {
+        // If there is a previously opened segment, and it was opened in
+        // read-write mode, update its checksum.
+        mem_sgmt_->shrinkToFit();
+        const MemorySegment::NamedAddressResult result =
+            mem_sgmt_->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+        assert(result.first);
+        assert(result.second);
+        size_t* checksum = static_cast<size_t*>(result.second);
+        // First, clear the checksum so that getCheckSum() returns a
+        // consistent value.
+        *checksum = 0;
+        const size_t new_checksum = mem_sgmt_->getCheckSum();
+        // Now, update it into place.
+        *checksum = new_checksum;
+    }
+}
+
+void
+ZoneTableSegmentMapped::clear() {
+    if (mem_sgmt_) {
+        sync();
+        mem_sgmt_.reset();
+    }
+}
+
+void
+ZoneTableSegmentMapped::resetHeader() {
+    // We cannot perform resetHeader() lazily during getHeader() as
+    // getHeader() has to work on const objects too. So we do it here
+    // now.
+
+    if (!isUsable()) {
+        isc_throw(isc::InvalidOperation,
+                  "resetHeader() called without calling reset() first");
+    }
+
+    const MemorySegment::NamedAddressResult result =
+        mem_sgmt_->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+    if (!result.first) {
+        isc_throw(isc::Unexpected,
+                  "Unable to look up the address of the table header in "
+                  "getHeader()");
+    }
+
+    cached_header_ = static_cast<ZoneTableHeader*>(result.second);
+}
+
+template<typename T>
+T*
+ZoneTableSegmentMapped::getHeaderHelper() const {
+    if (!isUsable()) {
+        isc_throw(isc::InvalidOperation,
+                  "getHeader() called without calling reset() first");
+    }
+
+    assert(cached_header_);
+    return (cached_header_);
+}
+
+ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() {
+    return (*getHeaderHelper<ZoneTableHeader>());
+}
+
+const ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() const {
+    return (*getHeaderHelper<const ZoneTableHeader>());
+}
+
+MemorySegment&
+ZoneTableSegmentMapped::getMemorySegment() {
+    if (!isUsable()) {
+        isc_throw(isc::InvalidOperation,
+                  "getMemorySegment() called without calling reset() first");
+    }
+    return (*mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isUsable() const {
+    // If mem_sgmt_ is not empty, then it is usable.
+    return (mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isWritable() const {
+    if (!isUsable()) {
+        // If reset() was never performed for this segment, or if the
+        // most recent reset() had failed, then the segment is not
+        // writable.
+        return (false);
+    }
+
+    return ((current_mode_ == CREATE) || (current_mode_ == READ_WRITE));
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc

+ 150 - 0
src/lib/datasrc/memory/zone_table_segment_mapped.h

@@ -0,0 +1,150 @@
+// 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 ZONE_TABLE_SEGMENT_MAPPED_H
+#define ZONE_TABLE_SEGMENT_MAPPED_H
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <util/memory_segment_mapped.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief Mapped-file based implementation of \c ZoneTableSegment class
+///
+/// This class specifies a concrete implementation for a memory-mapped
+/// \c ZoneTableSegment. Please see the \c ZoneTableSegment class
+/// documentation for usage.
+class ZoneTableSegmentMapped : public ZoneTableSegment {
+    // This is so that \c ZoneTableSegmentMapped can be instantiated
+    // from \c ZoneTableSegment::create().
+    friend class ZoneTableSegment;
+
+protected:
+    /// \brief Protected constructor
+    ///
+    /// Instances are expected to be created by the factory method
+    /// (\c ZoneTableSegment::create()), so this constructor is
+    /// protected.
+    ZoneTableSegmentMapped(const isc::dns::RRClass& rrclass);
+
+public:
+    /// \brief Destructor
+    virtual ~ZoneTableSegmentMapped();
+
+    /// \brief Returns "mapped" as the implementation type.
+    virtual const std::string& getImplType() const;
+
+    /// \brief Resets the \c ZoneTableHeader address from the named
+    /// address in the mapped file. This method should be called once
+    /// before calls to \c getHeader() if the mapped \c MemorySegment
+    /// has grown.
+    virtual void resetHeader();
+
+    /// \brief Return the \c ZoneTableHeader for this mapped zone table
+    /// segment.
+    ///
+    /// \throws isc::InvalidOperation if this method is called without a
+    /// successful \c reset() call first.
+    virtual ZoneTableHeader& getHeader();
+
+    /// \brief const version of \c getHeader().
+    virtual const ZoneTableHeader& getHeader() const;
+
+    /// \brief Return the \c MemorySegment for the memory-mapped zone
+    /// table segment implementation (a \c MemorySegmentMapped
+    /// instance).
+    ///
+    /// \throws isc::InvalidOperation if this method is called without a
+    /// successful \c reset() call first.
+    virtual isc::util::MemorySegment& getMemorySegment();
+
+    /// \brief Returns if the segment is writable.
+    ///
+    /// Segments successfully opened in CREATE or READ_WRITE modes are
+    /// writable. Segments opened in READ_ONLY mode are not writable.
+    /// If the \c ZoneTableSegment was cleared for some reason, it is
+    /// not writable until it is reset successfully.
+    virtual bool isWritable() const;
+
+    /// \brief Close the current \c MemorySegment (if open) and open the
+    /// requested one.
+    ///
+    /// See \c MemorySegmentOpenMode for a definition of "storage area"
+    /// and the various modes in which a \c MemorySegment can be opened.
+    ///
+    /// \c params should be a map containing a "mapped-file" key that
+    /// points to a string value containing the filename of a mapped
+    /// file. E.g.,
+    ///
+    ///  {"mapped-file": "/var/bind10/mapped-files/zone-sqlite3.mapped.0"}
+    ///
+    /// Please see the \c ZoneTableSegment API documentation for the
+    /// behavior in case of exceptions.
+    ///
+    /// \throws isc::Unexpected when it's unable to lookup a named
+    /// address that it expected to be present. This is extremely
+    /// unlikely, and it points to corruption.
+    ///
+    /// \param mode The open mode (see the \c MemorySegmentOpenMode
+    /// documentation in \c ZoneTableSegment class).
+    /// \param params An element containing config for the mapped file
+    /// (see the description).
+    virtual void reset(MemorySegmentOpenMode mode,
+                       isc::data::ConstElementPtr params);
+
+    /// \brief Close the currently configured \c MemorySegment (if
+    /// open). See the base class for a definition of "open" and
+    /// "close".
+    virtual void clear();
+
+    /// \brief Return true if the segment is usable.
+    ///
+    /// See the base class for the description.
+    virtual bool isUsable() const;
+
+private:
+    void sync();
+
+    bool processChecksum(isc::util::MemorySegmentMapped& segment, bool create,
+                         std::string& error_msg);
+    bool processHeader(isc::util::MemorySegmentMapped& segment, bool create,
+                       std::string& error_msg);
+
+    isc::util::MemorySegmentMapped* openReadWrite(const std::string& filename,
+                                                  bool create);
+    isc::util::MemorySegmentMapped* openReadOnly(const std::string& filename);
+
+    template<typename T> T* getHeaderHelper() const;
+
+private:
+    std::string impl_type_;
+    isc::dns::RRClass rrclass_;
+    MemorySegmentOpenMode current_mode_;
+    std::string current_filename_;
+    // Internally holds a MemorySegmentMapped. This is NULL on
+    // construction, and is set by the \c reset() method.
+    boost::scoped_ptr<isc::util::MemorySegmentMapped> mem_sgmt_;
+    ZoneTableHeader* cached_header_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // ZONE_TABLE_SEGMENT_MAPPED_H

+ 27 - 19
src/lib/datasrc/memory/zone_writer_local.cc

@@ -12,9 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "zone_writer_local.h"
-#include "zone_data.h"
-#include "zone_table_segment_local.h"
+#include <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_table_segment.h>
 
 #include <memory>
 
@@ -24,65 +24,73 @@ namespace isc {
 namespace datasrc {
 namespace memory {
 
-ZoneWriterLocal::ZoneWriterLocal(ZoneTableSegmentLocal* segment,
-                                 const LoadAction& load_action,
-                                 const dns::Name& origin,
-                                 const dns::RRClass& rrclass) :
+ZoneWriter::ZoneWriter(ZoneTableSegment& segment,
+                       const LoadAction& load_action,
+                       const dns::Name& origin,
+                       const dns::RRClass& rrclass) :
     segment_(segment),
     load_action_(load_action),
     origin_(origin),
     rrclass_(rrclass),
     zone_data_(NULL),
     state_(ZW_UNUSED)
-{}
+{
+    if (!segment.isWritable()) {
+        isc_throw(isc::InvalidOperation,
+                  "Attempt to construct ZoneWriter for a read-only segment");
+    }
+}
 
-ZoneWriterLocal::~ZoneWriterLocal() {
+ZoneWriter::~ZoneWriter() {
     // Clean up everything there might be left if someone forgot, just
     // in case.
     cleanup();
 }
 
 void
-ZoneWriterLocal::load() {
+ZoneWriter::load() {
     if (state_ != ZW_UNUSED) {
         isc_throw(isc::InvalidOperation, "Trying to load twice");
     }
 
-    zone_data_ = load_action_(segment_->getMemorySegment());
+    zone_data_ = load_action_(segment_.getMemorySegment());
 
-    if (zone_data_ == NULL) {
+    if (!zone_data_) {
         // Bug inside load_action_.
         isc_throw(isc::InvalidOperation, "No data returned from load action");
     }
 
+    segment_.resetHeader();
+
     state_ = ZW_LOADED;
 }
 
 void
-ZoneWriterLocal::install() {
+ZoneWriter::install() {
     if (state_ != ZW_LOADED) {
         isc_throw(isc::InvalidOperation, "No data to install");
     }
 
-
-    ZoneTable* table(segment_->getHeader().getTable());
-    if (table == NULL) {
+    ZoneTable* table(segment_.getHeader().getTable());
+    if (!table) {
         isc_throw(isc::Unexpected, "No zone table present");
     }
     const ZoneTable::AddResult result(table->addZone(
-                                          segment_->getMemorySegment(),
+                                          segment_.getMemorySegment(),
                                           rrclass_, origin_, zone_data_));
 
     state_ = ZW_INSTALLED;
     zone_data_ = result.zone_data;
+
+    segment_.resetHeader();
 }
 
 void
-ZoneWriterLocal::cleanup() {
+ZoneWriter::cleanup() {
     // We eat the data (if any) now.
 
     if (zone_data_ != NULL) {
-        ZoneData::destroy(segment_->getMemorySegment(), zone_data_, rrclass_);
+        ZoneData::destroy(segment_.getMemorySegment(), zone_data_, rrclass_);
         zone_data_ = NULL;
         state_ = ZW_CLEANED;
     }

+ 45 - 16
src/lib/datasrc/memory/zone_writer.h

@@ -15,7 +15,11 @@
 #ifndef MEM_ZONE_WRITER_H
 #define MEM_ZONE_WRITER_H
 
-#include "load_action.h"
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/load_action.h>
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
 
 namespace isc {
 namespace datasrc {
@@ -23,22 +27,33 @@ namespace memory {
 
 /// \brief Does an update to a zone.
 ///
-/// This abstract base class represents the work of a reload of a zone.
-/// The work is divided into three stages -- load(), install() and cleanup().
-/// They should be called in this order for the effect to take place.
+/// This represents the work of a (re)load of a zone.  The work is divided
+/// into three stages -- load(), install() and cleanup().  They should
+/// be called in this order for the effect to take place.
 ///
 /// We divide them so the update of zone data can be done asynchronously,
 /// in a different thread. The install() operation is the only one that needs
 /// to be done in a critical section.
 ///
-/// Each derived class implementation must provide the strong exception
-/// guarantee for each public method. That is, when any of the methods
-/// throws, the entire state should stay the same as before the call
-/// (how to achieve that may be implementation dependant).
+/// This class provides strong exception guarantee for each public
+/// method. That is, when any of the methods throws, the entire state
+/// stays the same as before the call.
 class ZoneWriter {
 public:
-    /// \brief Virtual destructor.
-    virtual ~ZoneWriter() {};
+    /// \brief Constructor
+    ///
+    /// \throw isc::InvalidOperation if \c segment is read-only.
+    ///
+    /// \param segment The zone table segment to store the zone into.
+    /// \param load_action The callback used to load data.
+    /// \param name The name of the zone.
+    /// \param rrclass The class of the zone.
+    ZoneWriter(ZoneTableSegment& segment,
+               const LoadAction& load_action, const dns::Name& name,
+               const dns::RRClass& rrclass);
+
+    /// \brief Destructor.
+    ~ZoneWriter();
 
     /// \brief Get the zone data into memory.
     ///
@@ -56,7 +71,7 @@ public:
     /// \note After successful load(), you have to call cleanup() some time
     ///     later.
     /// \throw isc::InvalidOperation if called second time.
-    virtual void load() = 0;
+    void load();
 
     /// \brief Put the changes to effect.
     ///
@@ -68,12 +83,12 @@ public:
     /// The operation is expected to be fast and is meant to be used inside
     /// a critical section.
     ///
-    /// This may throw in rare cases, depending on the concrete implementation.
-    /// If it throws, you still need to call cleanup().
+    /// This may throw in rare cases.  If it throws, you still need to
+    /// call cleanup().
     ///
     /// \throw isc::InvalidOperation if called without previous load() or for
     ///     the second time or cleanup() was called already.
-    virtual void install() = 0;
+    void install();
 
     /// \brief Clean up resources.
     ///
@@ -81,8 +96,22 @@ public:
     /// one loaded by load() in case install() was not called or was not
     /// successful, or the one replaced in install().
     ///
-    /// Generally, this should never throw.
-    virtual void cleanup() = 0;
+    /// \throw none
+    void cleanup();
+
+private:
+    ZoneTableSegment& segment_;
+    const LoadAction load_action_;
+    const dns::Name origin_;
+    const dns::RRClass rrclass_;
+    ZoneData* zone_data_;
+    enum State {
+        ZW_UNUSED,
+        ZW_LOADED,
+        ZW_INSTALLED,
+        ZW_CLEANED
+    };
+    State state_;
 };
 
 }

+ 0 - 95
src/lib/datasrc/memory/zone_writer_local.h

@@ -1,95 +0,0 @@
-// Copyright (C) 2012  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 MEM_ZONE_WRITER_LOCAL_H
-#define MEM_ZONE_WRITER_LOCAL_H
-
-#include "zone_writer.h"
-
-#include <dns/rrclass.h>
-#include <dns/name.h>
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-
-class ZoneData;
-class ZoneTableSegmentLocal;
-
-/// \brief Writer implementation which loads data locally.
-///
-/// This implementation prepares a clean zone data and lets one callback
-/// to fill it and another to install it somewhere. The class does mostly
-/// nothing (and delegates the work to the callbacks), just stores little bit
-/// of state between the calls.
-class ZoneWriterLocal : public ZoneWriter {
-public:
-    /// \brief Constructor
-    ///
-    /// \param segment The zone table segment to store the zone into.
-    /// \param load_action The callback used to load data.
-    /// \param install_action The callback used to install the loaded zone.
-    /// \param rrclass The class of the zone.
-    ZoneWriterLocal(ZoneTableSegmentLocal* segment,
-                    const LoadAction& load_action, const dns::Name& name,
-                    const dns::RRClass& rrclass);
-
-    /// \brief Destructor
-    ~ZoneWriterLocal();
-
-    /// \brief Loads the data.
-    ///
-    /// This calls the load_action (passed to constructor) and stores the
-    /// data for future use.
-    ///
-    /// \throw isc::InvalidOperation if it is called the second time in
-    ///     lifetime of the object.
-    /// \throw Whatever the load_action throws, it is propagated up.
-    virtual void load();
-
-    /// \brief Installs the zone.
-    ///
-    /// It modifies the zone table accessible through the segment (passed to
-    /// constructor).
-    ///
-    /// \throw isc::InvalidOperation if it is called the second time in
-    ///     lifetime of the object or if load() was not called previously or if
-    ///     cleanup() was already called.
-    virtual void install();
-
-    /// \brief Clean up memory.
-    ///
-    /// Cleans up the memory used by load()ed zone if not yet installed, or
-    /// the old zone replaced by install().
-    virtual void cleanup();
-private:
-    ZoneTableSegmentLocal* segment_;
-    LoadAction load_action_;
-    dns::Name origin_;
-    dns::RRClass rrclass_;
-    ZoneData* zone_data_;
-    enum State {
-        ZW_UNUSED,
-        ZW_LOADED,
-        ZW_INSTALLED,
-        ZW_CLEANED
-    };
-    State state_;
-};
-
-}
-}
-}
-
-#endif

+ 2 - 1
src/lib/datasrc/tests/client_list_unittest.cc

@@ -166,7 +166,8 @@ public:
         // Load the data into the zone table.
         if (enabled) {
             boost::scoped_ptr<memory::ZoneWriter> writer(
-                dsrc_info.ztable_segment_->getZoneWriter(
+                new memory::ZoneWriter(
+                    *dsrc_info.ztable_segment_,
                     cache_conf->getLoadAction(rrclass_, zone),
                     zone, rrclass_));
             writer->load();

+ 8 - 2
src/lib/datasrc/tests/memory/Makefile.am

@@ -4,6 +4,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
@@ -30,14 +31,19 @@ run_unittests_SOURCES += zone_table_unittest.cc
 run_unittests_SOURCES += zone_data_unittest.cc
 run_unittests_SOURCES += zone_finder_unittest.cc
 run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
-run_unittests_SOURCES += memory_segment_test.h
+run_unittests_SOURCES += memory_segment_mock.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
 run_unittests_SOURCES += rrset_collection_unittest.cc
 run_unittests_SOURCES += zone_data_loader_unittest.cc
 run_unittests_SOURCES += zone_data_updater_unittest.cc
-run_unittests_SOURCES += zone_table_segment_test.h
+run_unittests_SOURCES += zone_table_segment_mock.h
 run_unittests_SOURCES += zone_table_segment_unittest.cc
+
+if USE_SHARED_MEMORY
+run_unittests_SOURCES += zone_table_segment_mapped_unittest.cc
+endif
+
 run_unittests_SOURCES += zone_writer_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 6 - 6
src/lib/datasrc/tests/memory/memory_client_unittest.cc

@@ -37,8 +37,8 @@
 
 #include <testutils/dnsmessage_test.h>
 
-#include "memory_segment_test.h"
-#include "zone_table_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
 
 #include <gtest/gtest.h>
 
@@ -169,7 +169,7 @@ public:
 class MemoryClientTest : public ::testing::Test {
 protected:
     MemoryClientTest() : zclass_(RRClass::IN()),
-                         ztable_segment_(new test::ZoneTableSegmentTest(
+                         ztable_segment_(new test::ZoneTableSegmentMock(
                              zclass_, mem_sgmt_)),
                          client_(new InMemoryClient(ztable_segment_, zclass_))
     {}
@@ -179,7 +179,7 @@ protected:
         EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
     }
     const RRClass zclass_;
-    test::MemorySegmentTest mem_sgmt_;
+    test::MemorySegmentMock mem_sgmt_;
     shared_ptr<ZoneTableSegment> ztable_segment_;
     boost::scoped_ptr<InMemoryClient> client_;
 };
@@ -305,12 +305,12 @@ TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
         mem_sgmt_.setThrowCount(i);
         EXPECT_THROW({
             shared_ptr<ZoneTableSegment> ztable_segment(
-                new test::ZoneTableSegmentTest(
+                new test::ZoneTableSegmentMock(
                     zclass_, mem_sgmt_));
 
             // Include the InMemoryClient construction too here. Now,
             // even allocations done from InMemoryClient constructor
-            // fail (due to MemorySegmentTest throwing) and we check for
+            // fail (due to MemorySegmentMock throwing) and we check for
             // leaks when this happens.
             InMemoryClient client2(ztable_segment, zclass_);
             loadZoneIntoTable(*ztable_segment, Name("example.org"), zclass_,

+ 2 - 2
src/lib/datasrc/tests/memory/memory_segment_test.h

@@ -33,9 +33,9 @@ namespace test {
 // allocate() will succeed, and the 3rd call will fail with an exception.
 // This segment object can be used after the exception is thrown, and the
 // count is internally reset to 0.
-class MemorySegmentTest : public isc::util::MemorySegmentLocal {
+class MemorySegmentMock : public isc::util::MemorySegmentLocal {
 public:
-    MemorySegmentTest() : throw_count_(0) {}
+    MemorySegmentMock() : throw_count_(0) {}
     virtual void* allocate(std::size_t size) {
         if (throw_count_ > 0) {
             if (--throw_count_ == 0) {

+ 3 - 3
src/lib/datasrc/tests/memory/rrset_collection_unittest.cc

@@ -15,13 +15,13 @@
 
 #include <datasrc/memory/rrset_collection.h>
 
-#include "memory_segment_test.h"
-
 #include <datasrc/memory/zone_data_loader.h>
 #include <datasrc/memory/segment_object_holder.h>
 #include <dns/rrttl.h>
 #include <dns/rdataclass.h>
 
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
 #include <gtest/gtest.h>
 
 using namespace isc::dns;
@@ -52,7 +52,7 @@ public:
     const RRClass rrclass;
     const Name origin;
     std::string zone_file;
-    test::MemorySegmentTest mem_sgmt;
+    test::MemorySegmentMock mem_sgmt;
     SegmentObjectHolder<ZoneData, RRClass> zone_data_holder;
     RRsetCollection collection;
 };

+ 2 - 2
src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc

@@ -22,7 +22,7 @@
 #include <dns/name.h>
 #include <dns/rrclass.h>
 
-#include "memory_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
 
 #include <gtest/gtest.h>
 
@@ -41,7 +41,7 @@ protected:
         EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
     }
     const RRClass zclass_;
-    test::MemorySegmentTest mem_sgmt_;
+    test::MemorySegmentMock mem_sgmt_;
     ZoneData* zone_data_;
 };
 

+ 3 - 4
src/lib/datasrc/tests/memory/zone_data_unittest.cc

@@ -16,8 +16,6 @@
 #include <datasrc/memory/rdata_serialization.h>
 #include <datasrc/memory/rdataset.h>
 
-#include "memory_segment_test.h"
-
 #include <dns/rdataclass.h>
 
 #include <exceptions/exceptions.h>
@@ -30,6 +28,7 @@
 #include <dns/rrttl.h>
 
 #include <testutils/dnsmessage_test.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
 
 #include <gtest/gtest.h>
 
@@ -73,7 +72,7 @@ protected:
         EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated());
     }
 
-    MemorySegmentTest mem_sgmt_;
+    MemorySegmentMock mem_sgmt_;
     NSEC3Data* nsec3_data_;
     const generic::NSEC3PARAM param_rdata_, param_rdata_nosalt_,
         param_rdata_largesalt_;
@@ -88,7 +87,7 @@ protected:
 // Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
 template <typename RdataType>
 void
-checkNSEC3Data(MemorySegmentTest& mem_sgmt,
+checkNSEC3Data(MemorySegmentMock& mem_sgmt,
                const Name& zone_name,
                const RdataType& expect_rdata)
 {

+ 2 - 2
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc

@@ -25,7 +25,7 @@
 #include <dns/rrset.h>
 #include <dns/rrttl.h>
 
-#include "memory_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
 
 #include <gtest/gtest.h>
 
@@ -66,7 +66,7 @@ protected:
 
     const Name zname_;
     const RRClass zclass_;
-    test::MemorySegmentTest mem_sgmt_;
+    test::MemorySegmentMock mem_sgmt_;
     ZoneData* zone_data_;
     boost::scoped_ptr<ZoneDataUpdater> updater_;
 };

+ 5 - 5
src/lib/datasrc/tests/memory/zone_finder_unittest.cc

@@ -12,8 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <datasrc/tests/memory/memory_segment_test.h>
-#include <datasrc/tests/memory/zone_table_segment_test.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
 #include <datasrc/tests/memory/zone_loader_util.h>
 
 // NOTE: this faked_nsec3 inclusion (and all related code below)
@@ -221,7 +221,7 @@ protected:
     const RRClass class_;
     const Name origin_;
     // The zone finder to torture by tests
-    MemorySegmentTest mem_sgmt_;
+    MemorySegmentMock mem_sgmt_;
     memory::ZoneData* zone_data_;
     memory::InMemoryZoneFinder zone_finder_;
     ZoneDataUpdater updater_;
@@ -1613,7 +1613,7 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
 TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
     const Name name("example.com.");
     shared_ptr<ZoneTableSegment> ztable_segment(
-         new ZoneTableSegmentTest(class_, mem_sgmt_));
+         new ZoneTableSegmentMock(class_, mem_sgmt_));
     loadZoneIntoTable(*ztable_segment, name, class_,
                       TEST_DATA_DIR "/2504-test.zone");
     InMemoryClient client(ztable_segment, class_);
@@ -1775,7 +1775,7 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
 
      const Name name("example.com.");
      shared_ptr<ZoneTableSegment> ztable_segment(
-          new ZoneTableSegmentTest(class_, mem_sgmt_));
+          new ZoneTableSegmentMock(class_, mem_sgmt_));
      loadZoneIntoTable(*ztable_segment, name, class_,
                        TEST_DATA_DIR "/2503-test.zone");
      InMemoryClient client(ztable_segment, class_);

+ 10 - 15
src/lib/datasrc/tests/memory/zone_loader_util.cc

@@ -24,9 +24,6 @@
 
 #include <cc/data.h>
 
-#include <boost/bind.hpp>
-#include <boost/scoped_ptr.hpp>
-
 #include <string>
 
 namespace isc {
@@ -43,12 +40,11 @@ loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
             "{\"cache-enable\": true,"
             " \"params\": {\"" + zname.toText() + "\": \"" + zone_file +
             "\"}}"), true);
-    boost::scoped_ptr<memory::ZoneWriter> writer(
-        zt_sgmt.getZoneWriter(cache_conf.getLoadAction(zclass, zname),
-                              zname, zclass));
-    writer->load();
-    writer->install();
-    writer->cleanup();
+    memory::ZoneWriter writer(zt_sgmt, cache_conf.getLoadAction(zclass, zname),
+                              zname, zclass);
+    writer.load();
+    writer.install();
+    writer.cleanup();
 }
 
 namespace {
@@ -75,12 +71,11 @@ void
 loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
                   const dns::RRClass& zclass, ZoneIterator& iterator)
 {
-    boost::scoped_ptr<memory::ZoneWriter> writer(
-        zt_sgmt.getZoneWriter(IteratorLoader(zclass, zname, iterator),
-                              zname, zclass));
-    writer->load();
-    writer->install();
-    writer->cleanup();
+    memory::ZoneWriter writer(zt_sgmt, IteratorLoader(zclass, zname, iterator),
+                              zname, zclass);
+    writer.load();
+    writer.install();
+    writer.cleanup();
 }
 
 } // namespace test

+ 590 - 0
src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc

@@ -0,0 +1,590 @@
+// 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 <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#include <util/random/random_number_generator.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <gtest/gtest.h>
+#include <boost/format.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/interprocess/file_mapping.hpp>
+
+#include <memory>
+#include <cerrno>
+
+#include <sys/stat.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+using namespace isc::data;
+using namespace isc::util;
+using namespace isc::util::random;
+using namespace std;
+using boost::scoped_ptr;
+
+namespace {
+
+const char* const mapped_file  = TEST_DATA_BUILDDIR "/test.mapped";
+const char* const mapped_file2 = TEST_DATA_BUILDDIR "/test2.mapped";
+
+class ZoneTableSegmentMappedTest : public ::testing::Test {
+protected:
+    ZoneTableSegmentMappedTest() :
+        ztable_segment_(
+            ZoneTableSegment::create(RRClass::IN(), "mapped")),
+        config_params_(
+            Element::fromJSON(
+                "{\"mapped-file\": \"" + std::string(mapped_file) + "\"}")),
+        config_params2_(
+            Element::fromJSON(
+                "{\"mapped-file\": \"" + std::string(mapped_file2) + "\"}"))
+    {
+        EXPECT_NE(static_cast<void*>(NULL), ztable_segment_.get());
+        // Verify that a ZoneTableSegmentMapped is created.
+        ZoneTableSegmentMapped* mapped_segment =
+            dynamic_cast<ZoneTableSegmentMapped*>(ztable_segment_.get());
+        EXPECT_NE(static_cast<void*>(NULL), mapped_segment);
+
+        createTestData();
+    }
+
+    ~ZoneTableSegmentMappedTest() {
+        ZoneTableSegment::destroy(ztable_segment_.release());
+        boost::interprocess::file_mapping::remove(mapped_file);
+        boost::interprocess::file_mapping::remove(mapped_file2);
+    }
+
+    typedef std::pair<std::string, int> TestDataElement;
+
+    void createTestData() {
+        UniformRandomIntegerGenerator gen(0, INT_MAX);
+        for (int i = 0; i < 256; ++i) {
+            const string name(boost::str(boost::format("name%d") % i));
+            const int value = gen();
+            test_data_.push_back(TestDataElement(name, value));
+        }
+    }
+
+    void setupMappedFiles();
+    void addData(MemorySegment& segment);
+    bool verifyData(const MemorySegment& segment);
+
+    // Ideally, this should be something similar to a
+    // SegmentObjectHolder, not an auto_ptr.
+    std::auto_ptr<ZoneTableSegment> ztable_segment_;
+    const ConstElementPtr config_params_;
+    const ConstElementPtr config_params2_;
+    std::vector<TestDataElement> test_data_;
+};
+
+bool
+fileExists(const char* path) {
+    struct stat sb;
+    const int status = stat(path, &sb);
+    if (status != 0) {
+        EXPECT_EQ(ENOENT, errno);
+        return (false);
+    }
+    return (true);
+}
+
+void
+deleteChecksum(MemorySegment& segment) {
+    segment.clearNamedAddress("zone_table_checksum");
+}
+
+void
+corruptChecksum(MemorySegment& segment) {
+    const MemorySegment::NamedAddressResult result =
+        segment.getNamedAddress("zone_table_checksum");
+    ASSERT_TRUE(result.first);
+
+    size_t checksum = *static_cast<size_t*>(result.second);
+    ++checksum;
+    *static_cast<size_t*>(result.second) = checksum;
+}
+
+void
+deleteHeader(MemorySegment& segment) {
+    segment.clearNamedAddress("zone_table_header");
+}
+
+void
+ZoneTableSegmentMappedTest::addData(MemorySegment& segment) {
+    // For purposes of this test, we assume that the following
+    // allocations do not resize the mapped segment. For this, we have
+    // to keep the size of test data reasonably small in
+    // createTestData().
+
+    // One by one, add all the elements in test_data_.
+    for (int i = 0; i < test_data_.size(); ++i) {
+        void* ptr = segment.allocate(sizeof(int));
+        ASSERT_TRUE(ptr);
+        *static_cast<int*>(ptr) = test_data_[i].second;
+        const bool grew = segment.setNamedAddress(test_data_[i].first.c_str(),
+                                                  ptr);
+        ASSERT_FALSE(grew);
+    }
+}
+
+bool
+ZoneTableSegmentMappedTest::verifyData(const MemorySegment& segment) {
+    // One by one, verify all the elements in test_data_ exist and have
+    // the expected values.
+    for (int i = 0; i < test_data_.size(); ++i) {
+        const MemorySegment::NamedAddressResult result =
+            segment.getNamedAddress(test_data_[i].first.c_str());
+        if (!result.first) {
+            return (false);
+        }
+        if (*static_cast<int*>(result.second) != test_data_[i].second) {
+            return (false);
+        }
+    }
+
+    return (true);
+}
+
+void
+ZoneTableSegmentMappedTest::setupMappedFiles() {
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params2_);
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Now, clear the segment, closing the underlying mapped file.
+    ztable_segment_->clear();
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getImplType) {
+    EXPECT_EQ("mapped", ztable_segment_->getImplType());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getHeaderUninitialized) {
+    // This should throw as we haven't called reset() yet.
+    EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getMemorySegmentUninitialized) {
+    // This should throw as we haven't called reset() yet.
+    EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isUsableUninitialized) {
+    // isUsable() must return false by default, when the segment has not
+    // been reset() yet.
+    EXPECT_FALSE(ztable_segment_->isUsable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isWritableUninitialized) {
+    // isWritable() must return false by default, when the segment has
+    // not been reset() yet.
+    EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetBadConfig) {
+    // Open a mapped file in create mode.
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+    // Populate it with some data.
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // All the following resets() with invalid configuration must
+    // provide a strong exception guarantee that the segment is still
+    // usable as before.
+
+    // NULL is passed in config params
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               ConstElementPtr());
+    }, isc::InvalidParameter);
+
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Not a map
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("42"));
+    }, isc::InvalidParameter);
+
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Empty map
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("{}"));
+    }, isc::InvalidParameter);
+
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // No "mapped-file" key
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("{\"foo\": \"bar\"}"));
+    }, isc::InvalidParameter);
+
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Value of "mapped-file" key is not a string
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("{\"mapped-file\": 42}"));
+    }, isc::InvalidParameter);
+
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, reset) {
+    // By default, the mapped file doesn't exist, so we cannot open it
+    // in READ_ONLY mode (which does not create the file).
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+    }, MemorySegmentOpenError);
+
+    // The following should still throw, unaffected by the failed open.
+    EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+    EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+    // isUsable() and isWritable() must still return false, because the
+    // segment has not been successfully reset() yet.
+    EXPECT_FALSE(ztable_segment_->isUsable());
+    EXPECT_FALSE(ztable_segment_->isWritable());
+
+    // READ_WRITE mode must create the mapped file if it doesn't exist
+    // (and must not result in an exception).
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+
+    // The following method calls should no longer throw:
+    EXPECT_NO_THROW(ztable_segment_->getHeader());
+    EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+    // Let's try to re-open the mapped file in READ_ONLY mode. It should
+    // not fail now.
+    ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_FALSE(ztable_segment_->isWritable());
+
+    // Re-creating the mapped file should erase old data and should not
+    // trigger any exceptions inside reset() due to old data (such as
+    // named addresses).
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+
+    // When we reset() with an invalid paramter and it fails, then the
+    // segment should still be usable.
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("{}"));
+    }, isc::InvalidParameter);
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // The following should not throw.
+    EXPECT_NO_THROW(ztable_segment_->getHeader());
+    EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+    // READ_WRITE with an existing map file ought to work too. This
+    // would use existing named addresses. This actually re-opens the
+    // currently open map.
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreate) {
+    // At this point, the underlying file must not exist.
+    ASSERT_FALSE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in create mode.
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+    ASSERT_TRUE(ztable_segment_->isUsable());
+    ASSERT_TRUE(ztable_segment_->isWritable());
+
+    // Add the data.
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Close the segment.
+    ztable_segment_->clear();
+
+    // At this point, the underlying file must still exist.
+    ASSERT_TRUE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in create mode again.
+    ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+    // The old data should be gone.
+    EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadWrite) {
+    // At this point, the underlying file must not exist.
+    ASSERT_FALSE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in read+write mode.
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    ASSERT_TRUE(ztable_segment_->isUsable());
+    ASSERT_TRUE(ztable_segment_->isWritable());
+
+    // Add the data.
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Close the segment.
+    ztable_segment_->clear();
+
+    // At this point, the underlying file must still exist.
+    ASSERT_TRUE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in read+write mode again.
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    // The old data should still be available.
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadOnly) {
+    // At this point, the underlying file must not exist.
+    ASSERT_FALSE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in read+write mode.
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    ASSERT_TRUE(ztable_segment_->isUsable());
+    ASSERT_TRUE(ztable_segment_->isWritable());
+
+    // Add the data.
+    addData(ztable_segment_->getMemorySegment());
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Close the segment.
+    ztable_segment_->clear();
+
+    // At this point, the underlying file must still exist.
+    ASSERT_TRUE(fileExists(mapped_file));
+
+    // Open the underlying mapped file in read-only mode again.
+    ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+
+    // The old data should still be available.
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // But trying to allocate new data should result in an exception as
+    // the segment is read-only!
+    EXPECT_THROW(addData(ztable_segment_->getMemorySegment()),
+                 MemorySegmentError);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clearUninitialized) {
+    // Clearing a segment that has not been reset() is a nop, as clear()
+    // returns it to a fresh uninitialized state anyway.
+    EXPECT_NO_THROW(ztable_segment_->clear());
+
+    // The following should still throw, because the segment has not
+    // been successfully reset() yet.
+    EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+    EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+    // isWritable() must still return false, because the segment has not
+    // been successfully reset() yet.
+    EXPECT_FALSE(ztable_segment_->isUsable());
+    EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clear) {
+    // First, open an underlying mapped file in read+write mode (doesn't
+    // exist yet)
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // The following method calls should no longer throw:
+    EXPECT_NO_THROW(ztable_segment_->getHeader());
+    EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+    // Now, clear the segment.
+    ztable_segment_->clear();
+
+    EXPECT_FALSE(ztable_segment_->isUsable());
+    EXPECT_FALSE(ztable_segment_->isWritable());
+    // The following method calls should now throw.
+    EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+    EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedCorruptedChecksum) {
+    setupMappedFiles();
+
+    // Open mapped file 1 in read-write mode
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    // Corrupt mapped file 2.
+    scoped_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(mapped_file2,
+                                 MemorySegmentMapped::OPEN_OR_CREATE));
+    EXPECT_TRUE(verifyData(*segment));
+    corruptChecksum(*segment);
+    segment.reset();
+
+    // Resetting to mapped file 2 in read-write mode should fail
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params2_);
+    }, ResetFailed);
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // Check for the old data in the segment to make sure it is still
+    // available and correct.
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingChecksum) {
+    setupMappedFiles();
+
+    // Open mapped file 1 in read-write mode
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    // Corrupt mapped file 2.
+    scoped_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(mapped_file2,
+                                 MemorySegmentMapped::OPEN_OR_CREATE));
+    EXPECT_TRUE(verifyData(*segment));
+    deleteChecksum(*segment);
+    segment.reset();
+
+    // Resetting to mapped file 2 in read-only mode should fail
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+    }, ResetFailed);
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // Check for the old data in the segment to make sure it is still
+    // available and correct.
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingHeader) {
+    setupMappedFiles();
+
+    // Open mapped file 1 in read-write mode
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    // Corrupt mapped file 2.
+    scoped_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(mapped_file2,
+                                 MemorySegmentMapped::OPEN_OR_CREATE));
+    EXPECT_TRUE(verifyData(*segment));
+    deleteHeader(*segment);
+    segment.reset();
+
+    // Resetting to mapped file 2 in read-only mode should fail
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+    }, ResetFailed);
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // Check for the old data in the segment to make sure it is still
+    // available and correct.
+    EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreateOverCorruptedFile) {
+    setupMappedFiles();
+
+    // Corrupt mapped file 1.
+    scoped_ptr<MemorySegmentMapped> segment
+        (new MemorySegmentMapped(mapped_file,
+                                 MemorySegmentMapped::OPEN_OR_CREATE));
+    EXPECT_TRUE(verifyData(*segment));
+    corruptChecksum(*segment);
+    segment.reset();
+
+    // Resetting to mapped file 1 in CREATE mode over a corrupted file
+    // should pass.
+    EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+                                           config_params_));
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // Check for the old data in the segment. It should not be present
+    // (as we opened the segment in CREATE mode).
+    EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+
+    // Now try the same with missing checksum.
+    setupMappedFiles();
+
+    // Corrupt mapped file 1.
+    segment.reset(new MemorySegmentMapped(mapped_file,
+                                          MemorySegmentMapped::OPEN_OR_CREATE));
+    EXPECT_TRUE(verifyData(*segment));
+    deleteChecksum(*segment);
+    segment.reset();
+
+    // Resetting to mapped file 1 in CREATE mode over a file missing
+    // checksum should pass.
+    EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+                                           config_params_));
+
+    EXPECT_TRUE(ztable_segment_->isUsable());
+    EXPECT_TRUE(ztable_segment_->isWritable());
+    // Check for the old data in the segment. It should not be present
+    // (as we opened the segment in CREATE mode).
+    EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetHeaderUninitialized) {
+    // This should throw as we haven't called reset() yet.
+    EXPECT_THROW(ztable_segment_->resetHeader(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetHeader) {
+    // First, open an underlying mapped file in read+write mode (doesn't
+    // exist yet)
+    ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+    // Check if a valid ZoneTable is found.
+    {
+        const ZoneTableHeader& header = ztable_segment_->getHeader();
+        const ZoneTable* table = header.getTable();
+        EXPECT_EQ(0, table->getZoneCount());
+    }
+
+    // Grow the segment by allocating something large.
+    EXPECT_THROW(ztable_segment_->getMemorySegment().allocate(1<<16),
+                 MemorySegmentGrown);
+
+    // Reset the header address. This should not throw now.
+    EXPECT_NO_THROW(ztable_segment_->resetHeader());
+
+    // Check if a valid ZoneTable is found.
+    {
+        const ZoneTableHeader& header = ztable_segment_->getHeader();
+        const ZoneTable* table = header.getTable();
+        EXPECT_EQ(0, table->getZoneCount());
+    }
+}
+
+} // anonymous namespace

+ 31 - 48
src/lib/datasrc/tests/memory/zone_table_segment_test.h

@@ -18,7 +18,9 @@
 #include <datasrc/memory/zone_table_segment.h>
 #include <datasrc/memory/zone_table.h>
 #include <datasrc/memory/zone_data.h>
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <string>
 
 namespace isc {
 namespace datasrc {
@@ -28,19 +30,37 @@ namespace test {
 // A special ZoneTableSegment that can be used for tests.  It can be
 // passed a MemorySegment that can be used later to test if all memory
 // was de-allocated on it.
-class ZoneTableSegmentTest : public ZoneTableSegment {
+class ZoneTableSegmentMock : public ZoneTableSegment {
 public:
-    ZoneTableSegmentTest(isc::dns::RRClass rrclass,
+    ZoneTableSegmentMock(const isc::dns::RRClass& rrclass,
                          isc::util::MemorySegment& mem_sgmt) :
         ZoneTableSegment(rrclass),
+        impl_type_("mock"),
         mem_sgmt_(mem_sgmt),
         header_(ZoneTable::create(mem_sgmt_, rrclass))
     {}
 
-    virtual ~ZoneTableSegmentTest() {
+    virtual ~ZoneTableSegmentMock() {
         ZoneTable::destroy(mem_sgmt_, header_.getTable());
     }
 
+    const std::string& getImplType() const {
+        return (impl_type_);
+    }
+
+    virtual void reset(MemorySegmentOpenMode, isc::data::ConstElementPtr) {
+        isc_throw(isc::NotImplemented, "reset() is not implemented");
+    }
+
+    virtual void clear() {
+        isc_throw(isc::NotImplemented, "clear() is not implemented");
+    }
+
+    virtual void resetHeader() {
+        // This method does not have to do anything in this
+        // implementation.
+    }
+
     virtual ZoneTableHeader& getHeader() {
         return (header_);
     }
@@ -53,55 +73,18 @@ public:
         return (mem_sgmt_);
     }
 
-    virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
-                                      const dns::Name& name,
-                                      const dns::RRClass& rrclass)
-    {
-        return (new Writer(this, load_action, name, rrclass));
+    virtual bool isUsable() const {
+        return (true);
+    }
+
+    virtual bool isWritable() const {
+        return (true);
     }
 
 private:
+    std::string impl_type_;
     isc::util::MemorySegment& mem_sgmt_;
     ZoneTableHeader header_;
-
-    // A writer for this segment. The implementation is similar
-    // to ZoneWriterLocal, but all the error handling is stripped
-    // for simplicity. Also, we do everything inside the
-    // install(), for the same reason. We just need something
-    // inside the tests, not a full-blown implementation
-    // for background loading.
-    class Writer : public ZoneWriter {
-    public:
-        Writer(ZoneTableSegmentTest* segment, const LoadAction& load_action,
-               const dns::Name& name, const dns::RRClass& rrclass) :
-            segment_(segment),
-            load_action_(load_action),
-            name_(name),
-            rrclass_(rrclass)
-        {}
-
-        void load() {}
-
-        void install() {
-            ZoneTable* table(segment_->getHeader().getTable());
-            const ZoneTable::AddResult
-                result(table->addZone(segment_->getMemorySegment(), rrclass_,
-                                      name_,
-                                      load_action_(segment_->
-                                                   getMemorySegment())));
-            if (result.zone_data != NULL) {
-                ZoneData::destroy(segment_->getMemorySegment(),
-                                  result.zone_data, rrclass_);
-            }
-        }
-
-        virtual void cleanup() {}
-    private:
-        ZoneTableSegmentTest* segment_;
-        LoadAction load_action_;
-        dns::Name name_;
-        dns::RRClass rrclass_;
-    };
 };
 
 } // namespace test

+ 38 - 16
src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc

@@ -12,9 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
 #include <datasrc/memory/zone_table_segment_local.h>
-#include <util/memory_segment_local.h>
 
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
@@ -42,6 +41,9 @@ protected:
     ZoneTableSegment* ztable_segment_;
 };
 
+TEST_F(ZoneTableSegmentTest, getImplType) {
+    EXPECT_EQ("local", ztable_segment_->getImplType());
+}
 
 TEST_F(ZoneTableSegmentTest, create) {
     // By default, a local zone table segment is created.
@@ -52,6 +54,21 @@ TEST_F(ZoneTableSegmentTest, create) {
                  UnknownSegmentType);
 }
 
+TEST_F(ZoneTableSegmentTest, reset) {
+    // reset() should throw that it's not implemented so that any
+    // accidental calls are found out.
+    EXPECT_THROW({
+        ztable_segment_->reset(ZoneTableSegment::CREATE,
+                               Element::fromJSON("{}"));
+    }, isc::NotImplemented);
+}
+
+TEST_F(ZoneTableSegmentTest, clear) {
+    // clear() should throw that it's not implemented so that any
+    // accidental calls are found out.
+    EXPECT_THROW(ztable_segment_->clear(), isc::NotImplemented);
+}
+
 // Helper function to check const and non-const methods.
 template <typename TS, typename TH, typename TT>
 void
@@ -71,6 +88,19 @@ TEST_F(ZoneTableSegmentTest, getHeader) {
     // const version.
     testGetHeader<const ZoneTableSegment, const ZoneTableHeader,
                   const ZoneTable>(ztable_segment_);
+
+    // This is a nop for local segments.
+    ztable_segment_->resetHeader();
+
+    // The following still behave as before after resetHeader().
+
+    // non-const version.
+    testGetHeader<ZoneTableSegment, ZoneTableHeader, ZoneTable>
+        (ztable_segment_);
+
+    // const version.
+    testGetHeader<const ZoneTableSegment, const ZoneTableHeader,
+                  const ZoneTable>(ztable_segment_);
 }
 
 TEST_F(ZoneTableSegmentTest, getMemorySegment) {
@@ -79,22 +109,14 @@ TEST_F(ZoneTableSegmentTest, getMemorySegment) {
     mem_sgmt.allMemoryDeallocated(); // use mem_sgmt
 }
 
-ZoneData*
-loadAction(MemorySegment&) {
-    // The function won't be called, so this is OK
-    return (NULL);
+TEST_F(ZoneTableSegmentTest, isUsable) {
+    // Local segments are always usable.
+    EXPECT_TRUE(ztable_segment_->isUsable());
 }
 
-// Test we can get a writer.
-TEST_F(ZoneTableSegmentTest, getZoneWriter) {
-    scoped_ptr<ZoneWriter>
-        writer(ztable_segment_->getZoneWriter(loadAction, Name("example.org"),
-                                              RRClass::IN()));
-    // We have to get something
-    EXPECT_NE(static_cast<void*>(NULL), writer.get());
-    // And for now, it should be the local writer
-    EXPECT_NE(static_cast<void*>(NULL),
-              dynamic_cast<ZoneWriterLocal*>(writer.get()));
+TEST_F(ZoneTableSegmentTest, isWritable) {
+    // Local segments are always writable.
+    EXPECT_TRUE(ztable_segment_->isWritable());
 }
 
 } // anonymous namespace

+ 3 - 3
src/lib/datasrc/tests/memory/zone_table_unittest.cc

@@ -12,8 +12,6 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "memory_segment_test.h"
-
 #include <exceptions/exceptions.h>
 
 #include <util/memory_segment_local.h>
@@ -26,6 +24,8 @@
 #include <datasrc/memory/zone_table.h>
 #include <datasrc/memory/segment_object_holder.h>
 
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
 #include <gtest/gtest.h>
 
 #include <new>                  // for bad_alloc
@@ -56,7 +56,7 @@ protected:
     }
     const RRClass zclass_;
     const Name zname1, zname2, zname3;
-    test::MemorySegmentTest mem_sgmt_;
+    test::MemorySegmentMock mem_sgmt_;
     ZoneTable* zone_table;
 };
 

+ 81 - 22
src/lib/datasrc/tests/memory/zone_writer_unittest.cc

@@ -12,13 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
 #include <datasrc/memory/zone_table_segment_local.h>
 #include <datasrc/memory/zone_data.h>
 
 #include <dns/rrclass.h>
 #include <dns/name.h>
 
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
+
 #include <gtest/gtest.h>
 
 #include <boost/scoped_ptr.hpp>
@@ -29,37 +32,52 @@ using boost::bind;
 using isc::dns::RRClass;
 using isc::dns::Name;
 using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::test;
 
 namespace {
 
 class TestException {};
 
-class ZoneWriterLocalTest : public ::testing::Test {
+class ZoneTableSegmentHelper : public ZoneTableSegmentMock {
 public:
-    ZoneWriterLocalTest() :
-        segment_(ZoneTableSegment::create(RRClass::IN(), "local")),
+    ZoneTableSegmentHelper(const isc::dns::RRClass& rrclass,
+                           isc::util::MemorySegment& mem_sgmt) :
+        ZoneTableSegmentMock(rrclass, mem_sgmt),
+        reset_header_called_(false)
+    {}
+
+    virtual void resetHeader() {
+        reset_header_called_ = true;
+    }
+
+    bool reset_header_called_;
+};
+
+class ZoneWriterTest : public ::testing::Test {
+protected:
+    ZoneWriterTest() :
+        segment_(new ZoneTableSegmentHelper(RRClass::IN(), mem_sgmt_)),
         writer_(new
-            ZoneWriterLocal(dynamic_cast<ZoneTableSegmentLocal*>(segment_.
-                                                                 get()),
-                            bind(&ZoneWriterLocalTest::loadAction, this, _1),
-                            Name("example.org"), RRClass::IN())),
+            ZoneWriter(*segment_,
+                       bind(&ZoneWriterTest::loadAction, this, _1),
+                       Name("example.org"), RRClass::IN())),
         load_called_(false),
         load_throw_(false),
         load_null_(false),
         load_data_(false)
     {}
-    void TearDown() {
+    virtual void TearDown() {
         // Release the writer
         writer_.reset();
     }
-protected:
-    scoped_ptr<ZoneTableSegment> segment_;
-    scoped_ptr<ZoneWriterLocal> writer_;
+    MemorySegmentMock mem_sgmt_;
+    scoped_ptr<ZoneTableSegmentHelper> segment_;
+    scoped_ptr<ZoneWriter> writer_;
     bool load_called_;
     bool load_throw_;
     bool load_null_;
     bool load_data_;
-private:
+public:
     ZoneData* loadAction(isc::util::MemorySegment& segment) {
         // Make sure it is the correct segment passed. We know the
         // exact instance, can compare pointers to them.
@@ -86,38 +104,76 @@ private:
     }
 };
 
+class ReadOnlySegment : public ZoneTableSegmentMock {
+public:
+    ReadOnlySegment(const isc::dns::RRClass& rrclass,
+                    isc::util::MemorySegment& mem_sgmt) :
+        ZoneTableSegmentMock(rrclass, mem_sgmt)
+    {}
+
+    // Returns false indicating that the segment is not usable. We
+    // override this too as ZoneTableSegment implementations may use it
+    // internally.
+    virtual bool isUsable() const {
+        return (false);
+    }
+
+    // Returns false indicating it is a read-only segment. It is used in
+    // the ZoneWriter tests.
+    virtual bool isWritable() const {
+        return (false);
+    }
+};
+
+TEST_F(ZoneWriterTest, constructForReadOnlySegment) {
+    MemorySegmentMock mem_sgmt;
+    ReadOnlySegment ztable_segment(RRClass::IN(), mem_sgmt);
+    EXPECT_THROW(ZoneWriter(ztable_segment,
+                            bind(&ZoneWriterTest::loadAction, this, _1),
+                            Name("example.org"), RRClass::IN()),
+                 isc::InvalidOperation);
+}
+
 // We call it the way we are supposed to, check every callback is called in the
 // right moment.
-TEST_F(ZoneWriterLocalTest, correctCall) {
+TEST_F(ZoneWriterTest, correctCall) {
     // Nothing called before we call it
     EXPECT_FALSE(load_called_);
+    EXPECT_FALSE(segment_->reset_header_called_);
 
     // Just the load gets called now
     EXPECT_NO_THROW(writer_->load());
     EXPECT_TRUE(load_called_);
+    EXPECT_TRUE(segment_->reset_header_called_);
     load_called_ = false;
+    segment_->reset_header_called_ = false;
 
     EXPECT_NO_THROW(writer_->install());
     EXPECT_FALSE(load_called_);
+    EXPECT_TRUE(segment_->reset_header_called_);
 
     // We don't check explicitly how this works, but call it to free memory. If
     // everything is freed should be checked inside the TearDown.
     EXPECT_NO_THROW(writer_->cleanup());
 }
 
-TEST_F(ZoneWriterLocalTest, loadTwice) {
+TEST_F(ZoneWriterTest, loadTwice) {
     // Load it the first time
     EXPECT_NO_THROW(writer_->load());
     EXPECT_TRUE(load_called_);
+    EXPECT_TRUE(segment_->reset_header_called_);
     load_called_ = false;
+    segment_->reset_header_called_ = false;
 
     // The second time, it should not be possible
     EXPECT_THROW(writer_->load(), isc::InvalidOperation);
     EXPECT_FALSE(load_called_);
+    EXPECT_FALSE(segment_->reset_header_called_);
 
     // The object should not be damaged, try installing and clearing now
     EXPECT_NO_THROW(writer_->install());
     EXPECT_FALSE(load_called_);
+    EXPECT_TRUE(segment_->reset_header_called_);
 
     // We don't check explicitly how this works, but call it to free memory. If
     // everything is freed should be checked inside the TearDown.
@@ -126,25 +182,28 @@ TEST_F(ZoneWriterLocalTest, loadTwice) {
 
 // Try loading after call to install and call to cleanup. Both is
 // forbidden.
-TEST_F(ZoneWriterLocalTest, loadLater) {
+TEST_F(ZoneWriterTest, loadLater) {
     // Load first, so we can install
     EXPECT_NO_THROW(writer_->load());
     EXPECT_NO_THROW(writer_->install());
     // Reset so we see nothing is called now
     load_called_ = false;
+    segment_->reset_header_called_ = false;
 
     EXPECT_THROW(writer_->load(), isc::InvalidOperation);
     EXPECT_FALSE(load_called_);
+    EXPECT_FALSE(segment_->reset_header_called_);
 
     // Cleanup and try loading again. Still shouldn't work.
     EXPECT_NO_THROW(writer_->cleanup());
 
     EXPECT_THROW(writer_->load(), isc::InvalidOperation);
     EXPECT_FALSE(load_called_);
+    EXPECT_FALSE(segment_->reset_header_called_);
 }
 
 // Try calling install at various bad times
-TEST_F(ZoneWriterLocalTest, invalidInstall) {
+TEST_F(ZoneWriterTest, invalidInstall) {
     // Nothing loaded yet
     EXPECT_THROW(writer_->install(), isc::InvalidOperation);
     EXPECT_FALSE(load_called_);
@@ -161,7 +220,7 @@ TEST_F(ZoneWriterLocalTest, invalidInstall) {
 // We check we can clean without installing first and nothing bad
 // happens. We also misuse the testcase to check we can't install
 // after cleanup.
-TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
+TEST_F(ZoneWriterTest, cleanWithoutInstall) {
     EXPECT_NO_THROW(writer_->load());
     EXPECT_NO_THROW(writer_->cleanup());
 
@@ -172,7 +231,7 @@ TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
 }
 
 // Test the case when load callback throws
-TEST_F(ZoneWriterLocalTest, loadThrows) {
+TEST_F(ZoneWriterTest, loadThrows) {
     load_throw_ = true;
     EXPECT_THROW(writer_->load(), TestException);
 
@@ -186,7 +245,7 @@ TEST_F(ZoneWriterLocalTest, loadThrows) {
 
 // Check the strong exception guarantee - if it throws, nothing happened
 // to the content.
-TEST_F(ZoneWriterLocalTest, retry) {
+TEST_F(ZoneWriterTest, retry) {
     // First attempt fails due to some exception.
     load_throw_ = true;
     EXPECT_THROW(writer_->load(), TestException);
@@ -214,7 +273,7 @@ TEST_F(ZoneWriterLocalTest, retry) {
 }
 
 // Check the writer defends itsefl when load action returns NULL
-TEST_F(ZoneWriterLocalTest, loadNull) {
+TEST_F(ZoneWriterTest, loadNull) {
     load_null_ = true;
     EXPECT_THROW(writer_->load(), isc::InvalidOperation);
 
@@ -226,7 +285,7 @@ TEST_F(ZoneWriterLocalTest, loadNull) {
 }
 
 // Check the object cleans up in case we forget it.
-TEST_F(ZoneWriterLocalTest, autoCleanUp) {
+TEST_F(ZoneWriterTest, autoCleanUp) {
     // Load data and forget about it. It should get released
     // when the writer itself is destroyed.
     EXPECT_NO_THROW(writer_->load());

+ 6 - 8
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -36,7 +36,6 @@
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 #include <boost/shared_ptr.hpp>
-#include <boost/scoped_ptr.hpp>
 
 #include <fstream>
 #include <sstream>
@@ -44,7 +43,6 @@
 
 using namespace std;
 using boost::shared_ptr;
-using boost::scoped_ptr;
 
 using namespace isc::data;
 using namespace isc::util;
@@ -79,12 +77,12 @@ createInMemoryClient(RRClass zclass, const Name& zname) {
             string(TEST_ZONE_FILE) + "\"}}"), true);
     shared_ptr<ZoneTableSegment> ztable_segment(
         ZoneTableSegment::create(zclass, cache_conf.getSegmentType()));
-    scoped_ptr<memory::ZoneWriter> writer(
-        ztable_segment->getZoneWriter(cache_conf.getLoadAction(zclass, zname),
-                                      zname, zclass));
-    writer->load();
-    writer->install();
-    writer->cleanup();
+    memory::ZoneWriter writer(*ztable_segment,
+                              cache_conf.getLoadAction(zclass, zname),
+                              zname, zclass);
+    writer.load();
+    writer.install();
+    writer.cleanup();
     shared_ptr<InMemoryClient> client(new InMemoryClient(ztable_segment,
                                                          zclass));
 

+ 3 - 3
src/lib/datasrc/tests/zone_loader_unittest.cc

@@ -319,9 +319,9 @@ protected:
                                   rrclass_, cache_conf.getSegmentType()));
         if (filename) {
             boost::scoped_ptr<memory::ZoneWriter> writer(
-                ztable_segment_->getZoneWriter(cache_conf.getLoadAction(
-                                                   rrclass_, zone),
-                                               zone, rrclass_));
+                new memory::ZoneWriter(*ztable_segment_,
+                                       cache_conf.getLoadAction(rrclass_, zone),
+                                       zone, rrclass_));
             writer->load();
             writer->install();
             writer->cleanup();

+ 15 - 5
src/lib/util/memory_segment.h

@@ -17,6 +17,8 @@
 
 #include <exceptions/exceptions.h>
 
+#include <utility>
+
 #include <stdlib.h>
 
 namespace isc {
@@ -116,6 +118,8 @@ public:
     /// requested storage.
     /// \throw MemorySegmentGrown The memory segment doesn't have sufficient
     /// space for the requested size and has grown internally.
+    /// \throw MemorySegmentError An attempt was made to allocate
+    /// storage on a read-only memory segment.
     ///
     /// \param size The size of the memory requested in bytes.
     /// \return Returns pointer to the memory allocated.
@@ -153,7 +157,7 @@ public:
     /// \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;
+    virtual bool allMemoryDeallocated() = 0;
 
     /// \brief Associate specified address in the segment with a given name.
     ///
@@ -176,7 +180,8 @@ public:
     /// 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.
+    /// to \c getNamedAddress() will return NamedAddressResult(true, 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
@@ -228,6 +233,9 @@ public:
         return (setNamedAddressImpl(name, addr));
     }
 
+    /// \brief Type definition for result returned by getNamedAddress()
+    typedef std::pair<bool, void*> NamedAddressResult;
+
     /// \brief Return the address in the segment that has the given name.
     ///
     /// This method returns the memory address in the segment corresponding
@@ -245,8 +253,10 @@ public:
     ///
     /// \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) {
+    /// \return An std::pair containing a bool (set to true if the name
+    /// was found, or false otherwise) and the address associated with
+    /// the name (which is undefined if the name was not found).
+    NamedAddressResult getNamedAddress(const char* name) const {
         // This public method implements common validation.  The actual
         // work specific to the derived segment is delegated to the
         // corresponding protected method.
@@ -288,7 +298,7 @@ protected:
     virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
 
     /// \brief Implementation of getNamedAddress beyond common validation.
-    virtual void* getNamedAddressImpl(const char* name) = 0;
+    virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0;
 
     /// \brief Implementation of clearNamedAddress beyond common validation.
     virtual bool clearNamedAddressImpl(const char* name) = 0;

+ 7 - 6
src/lib/util/memory_segment_local.cc

@@ -47,17 +47,18 @@ MemorySegmentLocal::deallocate(void* ptr, size_t size) {
 }
 
 bool
-MemorySegmentLocal::allMemoryDeallocated() const {
+MemorySegmentLocal::allMemoryDeallocated() {
     return (allocated_size_ == 0 && named_addrs_.empty());
 }
 
-void*
-MemorySegmentLocal::getNamedAddressImpl(const char* name) {
-    std::map<std::string, void*>::iterator found = named_addrs_.find(name);
+MemorySegment::NamedAddressResult
+MemorySegmentLocal::getNamedAddressImpl(const char* name) const {
+    std::map<std::string, void*>::const_iterator found =
+        named_addrs_.find(name);
     if (found != named_addrs_.end()) {
-        return (found->second);
+        return (NamedAddressResult(true, found->second));
     }
-    return (0);
+    return (NamedAddressResult(false, NULL));
 }
 
 bool

+ 2 - 2
src/lib/util/memory_segment_local.h

@@ -64,13 +64,13 @@ public:
     ///
     /// \return Returns <code>true</code> if all allocated memory was
     /// deallocated, <code>false</code> otherwise.
-    virtual bool allMemoryDeallocated() const;
+    virtual bool allMemoryDeallocated();
 
     /// \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);
+    virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
 
     /// \brief Local segment version of setNamedAddress.
     ///

+ 68 - 7
src/lib/util/memory_segment_mapped.cc

@@ -44,6 +44,15 @@ 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;
@@ -98,6 +107,7 @@ struct MemorySegmentMapped::Impl {
         // 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
@@ -108,6 +118,7 @@ struct MemorySegmentMapped::Impl {
         lock_(new boost::interprocess::file_lock(filename.c_str()))
     {
         checkWriter();
+        reserveMemory();
     }
 
     // Constructor for existing segment, either read-only or read-write
@@ -123,6 +134,37 @@ struct MemorySegmentMapped::Impl {
         } else {
             checkWriter();
         }
+        reserveMemory();
+    }
+
+    void reserveMemory() {
+        if (!read_only_) {
+            // Reserve a named address for use during
+            // setNamedAddress(). Though this will almost always succeed
+            // on the first try during construction, it may require
+            // multiple attempts later during a call from
+            // allMemoryDeallocated() when the segment has been in use
+            // for a while.
+            while (true) {
+                const offset_ptr<void>* reserved_storage =
+                    base_sgmt_->find_or_construct<offset_ptr<void> >(
+                        RESERVED_NAMED_ADDRESS_STORAGE_NAME, std::nothrow)();
+
+                if (reserved_storage) {
+                    break;
+                }
+
+                growSegment();
+            }
+        }
+    }
+
+    void freeReservedMemory() {
+        if (!read_only_) {
+            const bool deleted = base_sgmt_->destroy<offset_ptr<void> >
+                (RESERVED_NAMED_ADDRESS_STORAGE_NAME);
+            assert(deleted);
+        }
     }
 
     // Internal helper to grow the underlying mapped segment.
@@ -227,6 +269,7 @@ MemorySegmentMapped::MemorySegmentMapped(const std::string& filename,
 
 MemorySegmentMapped::~MemorySegmentMapped() {
     if (impl_->base_sgmt_ && !impl_->read_only_) {
+        impl_->freeReservedMemory();
         impl_->base_sgmt_->flush(); // note: this is exception free
     }
     delete impl_;
@@ -275,18 +318,22 @@ MemorySegmentMapped::deallocate(void* ptr, size_t) {
 }
 
 bool
-MemorySegmentMapped::allMemoryDeallocated() const {
-    return (impl_->base_sgmt_->all_memory_deallocated());
+MemorySegmentMapped::allMemoryDeallocated() {
+    impl_->freeReservedMemory();
+    const bool result = impl_->base_sgmt_->all_memory_deallocated();
+    impl_->reserveMemory();
+
+    return (result);
 }
 
-void*
-MemorySegmentMapped::getNamedAddressImpl(const char* name) {
+MemorySegment::NamedAddressResult
+MemorySegmentMapped::getNamedAddressImpl(const char* name) const {
     offset_ptr<void>* storage =
         impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
     if (storage) {
-        return (storage->get());
+        return (NamedAddressResult(true, storage->get()));
     }
-    return (NULL);
+    return (NamedAddressResult(false, NULL));
 }
 
 bool
@@ -299,13 +346,27 @@ MemorySegmentMapped::setNamedAddressImpl(const char* name, void* addr) {
         isc_throw(MemorySegmentError, "address is out of segment: " << addr);
     }
 
+    // Temporarily save the passed addr into pre-allocated offset_ptr in
+    // case there are any relocations caused by allocations.
+    offset_ptr<void>* reserved_storage =
+        impl_->base_sgmt_->find<offset_ptr<void> >(
+            RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+    assert(reserved_storage);
+    *reserved_storage = addr;
+
     bool grown = false;
     while (true) {
         offset_ptr<void>* storage =
             impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
                 name, std::nothrow)();
         if (storage) {
-            *storage = addr;
+            // Move the address from saved offset_ptr into the
+            // newly-allocated storage.
+            reserved_storage =
+                impl_->base_sgmt_->find<offset_ptr<void> >(
+                    RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+            assert(reserved_storage);
+            *storage = *reserved_storage;
             return (grown);
         }
 

+ 2 - 2
src/lib/util/memory_segment_mapped.h

@@ -175,7 +175,7 @@ public:
     /// read-only mode; in that case MemorySegmentError will be thrown.
     virtual void deallocate(void* ptr, size_t size);
 
-    virtual bool allMemoryDeallocated() const;
+    virtual bool allMemoryDeallocated();
 
     /// \brief Mapped segment version of setNamedAddress.
     ///
@@ -195,7 +195,7 @@ public:
     /// \brief Mapped segment version of getNamedAddress.
     ///
     /// This version never throws.
-    virtual void* getNamedAddressImpl(const char* name);
+    virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
 
     /// \brief Mapped segment version of clearNamedAddress.
     ///

+ 8 - 2
src/lib/util/random/random_number_generator.h

@@ -18,6 +18,7 @@
 #include <algorithm>
 #include <cmath>
 #include <numeric>
+#include <vector>
 
 #include <exceptions/exceptions.h>
 
@@ -59,7 +60,9 @@ public:
     ///
     /// \param min The minimum number in the range
     /// \param max The maximum number in the range
-    UniformRandomIntegerGenerator(int min, int max):
+    /// \param seed A seed for the RNG. If 0 is passed, the current time
+    /// is used.
+    UniformRandomIntegerGenerator(int min, int max, unsigned int seed = 0):
         min_(std::min(min, max)), max_(std::max(min, max)),
         dist_(min_, max_), generator_(rng_, dist_)
     {
@@ -72,7 +75,10 @@ public:
         }
 
         // Init with the current time
-        rng_.seed(time(NULL));
+        if (seed == 0) {
+            seed = time(NULL);
+        }
+        rng_.seed(seed);
     }
 
     /// \brief Generate uniformly distributed integer

+ 13 - 11
src/lib/util/tests/memory_segment_common_unittest.cc

@@ -30,9 +30,8 @@ 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"));
+    // If the name does not exist, false should be returned.
+    EXPECT_FALSE(segment.getNamedAddress("test address").first);
 
     // Now set it
     void* ptr32 = segment.allocate(sizeof(uint32_t));
@@ -44,29 +43,32 @@ checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
     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));
+    MemorySegment::NamedAddressResult result =
+        segment.getNamedAddress("test address");
+    EXPECT_TRUE(result.first);
+    EXPECT_EQ(test_val, *static_cast<const uint32_t*>(result.second));
 
     // 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));
+    result = segment.getNamedAddress("test address");
+    EXPECT_TRUE(result.first);
+    EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
 
     // 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"));
+    EXPECT_FALSE(segment.getNamedAddress("test address").first);
 
     // 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"));
+    result = segment.getNamedAddress("null address");
+    EXPECT_TRUE(result.first);
+    EXPECT_FALSE(result.second);
 
     // If the underlying implementation performs explicit check against
     // out-of-segment address, confirm the behavior.

+ 42 - 24
src/lib/util/tests/memory_segment_mapped_unittest.cc

@@ -287,12 +287,14 @@ 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()));
+    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(dp, data.size());
+        sgmt.deallocate(result.second, data.size());
         sgmt.clearNamedAddress(name.c_str());
     }
 }
@@ -309,10 +311,10 @@ TEST_F(MemorySegmentMappedTest, namedAddress) {
     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")));
+    MemorySegment::NamedAddressResult result =
+        segment_->getNamedAddress("test address");
+    ASSERT_TRUE(result.first);
+    EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
 
     // try to set an unusually long name.  We re-create the file so
     // creating the name would cause allocation failure and trigger internal
@@ -323,8 +325,9 @@ TEST_F(MemorySegmentMappedTest, namedAddress) {
     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()));
+    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.
@@ -410,10 +413,12 @@ TEST_F(MemorySegmentMappedTest, multiProcess) {
         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);
+        const MemorySegment::NamedAddressResult result =
+            sgmt.getNamedAddress("test address");
+        ASSERT_TRUE(result.first);
+        EXPECT_TRUE(result.second);
+        if (result.second) {
+            const uint32_t val = *static_cast<const uint32_t*>(result.second);
             EXPECT_EQ(424242, val);
             // tell the parent whether it succeeded. 0 means it did,
             // 0xff means it failed.
@@ -425,9 +430,11 @@ TEST_F(MemorySegmentMappedTest, multiProcess) {
     // 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 MemorySegment::NamedAddressResult result =
+        segment_->getNamedAddress("test address");
+    ASSERT_TRUE(result.first);
+    ASSERT_TRUE(result.second);
+    EXPECT_EQ(424242, *static_cast<const uint32_t*>(result.second));
     const char some_data = 0;
     EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
                        sizeof(some_data)));
@@ -460,7 +467,14 @@ TEST_F(MemorySegmentMappedTest, shrink) {
     EXPECT_EQ(shrinked_size, segment_->getSize());
 
     // Check that the segment is still usable after shrink.
-    void* p = segment_->allocate(sizeof(uint32_t));
+    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));
 }
 
@@ -477,9 +491,11 @@ TEST_F(MemorySegmentMappedTest, violateReadOnly) {
     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;
+                const MemorySegment::NamedAddressResult result =
+                    segment_ro.getNamedAddress("test address");
+                ASSERT_TRUE(result.first);
+                ASSERT_TRUE(result.second);
+                *static_cast<uint32_t*>(result.second) = 0;
             }, "");
     }
 
@@ -487,10 +503,12 @@ TEST_F(MemorySegmentMappedTest, violateReadOnly) {
     // 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);
+    const MemorySegment::NamedAddressResult result =
+        segment_ro.getNamedAddress("test address");
+    ASSERT_TRUE(result.first);
+    EXPECT_NE(static_cast<void*>(NULL), result.second);
 
-    EXPECT_THROW(segment_ro.deallocate(ptr, 4), MemorySegmentError);
+    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

+ 26 - 8
src/lib/util/tests/random_number_generator_unittest.cc

@@ -14,14 +14,16 @@
 
 #include <config.h>
 
+#include <util/random/random_number_generator.h>
+
 #include <gtest/gtest.h>
 #include <boost/shared_ptr.hpp>
 
-#include <algorithm>
 #include <iostream>
-#include <vector>
+#include <climits>
 
-#include <util/random/random_number_generator.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 namespace isc {
 namespace util {
@@ -42,9 +44,9 @@ public:
     }
     virtual ~UniformRandomIntegerGeneratorTest(){}
 
-    int gen() { return gen_(); }
-    int max() const { return max_; }
-    int min() const { return min_; }
+    int gen() { return (gen_()); }
+    int max() const { return (max_); }
+    int min() const { return (min_); }
 
 private:
     UniformRandomIntegerGenerator gen_;
@@ -82,7 +84,22 @@ TEST_F(UniformRandomIntegerGeneratorTest, IntegerRange) {
     vector<int>::iterator it = unique(numbers.begin(), numbers.end());
 
     // make sure the numbers are in range [min, max]
-    ASSERT_EQ(it - numbers.begin(), max() - min() + 1); 
+    ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
+}
+
+TEST_F(UniformRandomIntegerGeneratorTest, withSeed) {
+    // Test that two generators with the same seed return the same
+    // sequence.
+    UniformRandomIntegerGenerator gen1(0, INT_MAX, getpid());
+    vector<int> numbers;
+    for (int i = 0; i < 1024; ++i) {
+        numbers.push_back(gen1());
+    }
+
+    UniformRandomIntegerGenerator gen2(0, INT_MAX, getpid());
+    for (int i = 0; i < 1024; ++i) {
+        EXPECT_EQ(numbers[i], gen2());
+    }
 }
 
 /// \brief Test Fixture Class for weighted random number generator
@@ -99,7 +116,8 @@ public:
 TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
     vector<double> probabilities;
 
-    // If no probabilities is provided, the smallest integer will always be generated
+    // If no probabilities is provided, the smallest integer will always
+    // be generated
     WeightedRandomIntegerGenerator gen(probabilities, 123);
     for (int i = 0; i < 100; ++i) {
         ASSERT_EQ(gen(), 123);