Unverified Commit e41be59e authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge #2836

Make the zone loading work even on top of MemorySegmentMapped. In
particular, handle the MemorySegmentGrown exception, which may cause
relocation of data.
parents 0b690e4c 68676822
......@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <util/unittests/check_valgrind.h>
#include <dns/name.h>
......
......@@ -17,6 +17,7 @@ libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
libdatasrc_memory_la_SOURCES += rrset_collection.h rrset_collection.cc
libdatasrc_memory_la_SOURCES += segment_object_holder.h
libdatasrc_memory_la_SOURCES += segment_object_holder.cc
libdatasrc_memory_la_SOURCES += logger.h logger.cc
libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
......
......@@ -1301,15 +1301,24 @@ public:
/// doesn't exist.
///
/// This method normally involves resource allocation. If it fails
/// the corresponding standard exception will be thrown.
/// \c std::bad_alloc will be thrown. Also, depending on details of the
/// specific \c MemorySegment, it can propagate the \c MemorySegmentGrown
/// exception.
///
/// This method does not provide the strong exception guarantee in its
/// strict sense; if an exception is thrown in the middle of this
/// method, the internal structure may change. However, it should
/// still retain the same property as a mapping container before this
/// method is called. For example, the result of \c find() should be
/// the same. This method provides the weak exception guarantee in its
/// normal sense.
/// strict sense; there can be new empty nodes that are superdomains of
/// the domain to be inserted as a side effect. However, the tree
/// retains internal integrity otherwise, and, in particular, the intended
/// insert operation is "resumable": if the \c insert() method is called
/// again with the same argument after resolving the cause of the
/// exception (possibly multiple times), it should now succeed. Note,
/// however, that in case of \c MemorySegmentGrown the address of the
/// `DomainTree` object may have been reallocated if it was created with
/// the same \c MemorySegment (which will often be the case in practice).
/// So the caller may have to re-get the address before calling \c insert
/// again. It can be done using the concept of "named addresses" of
/// \c MemorySegment, or the direct caller may not have to worry about it
/// if this condition is guaranteed at a higher level.
///
/// \param mem_sgmt A \c MemorySegment object for allocating memory of
/// a new node to be inserted. Must be the same segment as that used
......
......@@ -176,6 +176,15 @@ public:
/// it cannot contain more than 65535 RRSIGs. If the given RRset(s) fail
/// to meet this condition, an \c RdataSetError exception will be thrown.
///
/// This method ensures there'll be no memory leak on exception.
/// But addresses allocated from \c mem_sgmt could be relocated if
/// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
/// must be aware of that possibility and update any such addresses
/// accordingly. On successful return, this method ensures there's no
/// address relocation.
///
/// \throw util::MemorySegmentGrown The memory segment has grown, possibly
/// relocating data.
/// \throw isc::BadValue Given RRset(s) are invalid (see the description)
/// \throw RdataSetError Number of RDATAs exceed the limits
/// \throw std::bad_alloc Memory allocation fails.
......
// 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 "segment_object_holder.h"
#include <boost/lexical_cast.hpp>
#include <cassert>
namespace isc {
namespace datasrc {
namespace memory {
namespace detail {
std::string
getNextHolderName() {
static uint64_t index = 0;
++index;
// in practice we should be able to assume this, uint64 is large
// and should not overflow
assert(index != 0);
return ("Segment object holder auto name " +
boost::lexical_cast<std::string>(index));
}
}
}
}
}
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// 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
......@@ -16,39 +16,99 @@
#define DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H 1
#include <util/memory_segment.h>
#include <string>
#include <cassert>
namespace isc {
namespace datasrc {
namespace memory {
namespace detail {
// Internal function to get next yet unused name of segment holder.
// We need the names of holders to be unique per segment at any given
// momemnt. This just keeps incrementing number after a prefix with
// each call, it should be enough (we assert it does not wrap around,
// but 64bits should be enough).
//
// Also, it is not thread safe.
std::string
getNextHolderName();
// A simple holder to create and use some objects in this implementation
// in an exception safe manner. It works like std::auto_ptr but much
// more simplified.
//
// Note, however, that it doesn't take the pointer to hold on construction.
// This is because the constructor itself can throw or cause address
// reallocation inside the memory segment. If that happens various
// undesirable effects can happen, such as memory leak or unintentional access
// to the pre-reallocated address. To make it safer, we use a separate
// \c set() method, which is exception free and doesn't cause address
// reallocation. So the typical usage is to first construct the holder
// object, then the object to be held, immediately followed by a call to \c
// set(). Subsequent access to the held address should be done via the \c get()
// method. get() ensures the address is always valid in the memory segment
// even if address reallocation happens between set() and get().
//
// template parameter T is the type of object allocated by mem_sgmt.
// template parameter ARG_T is the type that will be passed to destroy()
// (deleter functor, etc). It must be copyable.
template <typename T, typename ARG_T>
class SegmentObjectHolder {
public:
SegmentObjectHolder(util::MemorySegment& mem_sgmt, T* obj, ARG_T arg) :
mem_sgmt_(mem_sgmt), obj_(obj), arg_(arg)
{}
SegmentObjectHolder(util::MemorySegment& mem_sgmt, ARG_T arg) :
mem_sgmt_(mem_sgmt), arg_(arg),
holder_name_(getNextHolderName()), holding_(true)
{
if (mem_sgmt_.setNamedAddress(holder_name_.c_str(), NULL)) {
// OK. We've grown. The caller might need to be informed, so
// we throw. But then, we don't get our destructor, so we
// release the memory right away.
mem_sgmt_.clearNamedAddress(holder_name_.c_str());
isc_throw(isc::util::MemorySegmentGrown,
"Segment grown when allocating holder");
}
}
~SegmentObjectHolder() {
if (obj_ != NULL) {
T::destroy(mem_sgmt_, obj_, arg_);
if (holding_) {
// Use release, as it removes the stored address from segment
T* obj = release();
if (obj) { // May be NULL if set wasn't called
T::destroy(mem_sgmt_, obj, arg_);
}
}
}
void set(T* obj) {
const bool grown = mem_sgmt_.setNamedAddress(holder_name_.c_str(),
obj);
// We reserve the space in the constructor, should not grow now
assert(!grown);
}
T* get() {
if (holding_) {
const util::MemorySegment::NamedAddressResult result =
mem_sgmt_.getNamedAddress(holder_name_.c_str());
assert(result.first);
return (static_cast<T*>(result.second));
} else {
return (NULL);
}
}
T* get() { return (obj_); }
T* release() {
T* ret = obj_;
obj_ = NULL;
return (ret);
if (holding_) {
T* obj = get();
mem_sgmt_.clearNamedAddress(holder_name_.c_str());
holding_ = false;
return (obj);
} else {
return (NULL);
}
}
private:
util::MemorySegment& mem_sgmt_;
T* obj_;
ARG_T arg_;
const std::string holder_name_;
bool holding_;
};
} // detail
......
......@@ -91,8 +91,8 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt,
// (with an assertion check for that).
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
mem_sgmt, ZoneTree::create(mem_sgmt, true),
boost::bind(nullDeleter, _1));
mem_sgmt, boost::bind(nullDeleter, _1));
holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
const ZoneTree::Result result =
......@@ -165,8 +165,8 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
// NSEC3Data::create().
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
mem_sgmt, ZoneTree::create(mem_sgmt, true),
boost::bind(nullDeleter, _1));
mem_sgmt, boost::bind(nullDeleter, _1));
holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
ZoneNode* origin_node = NULL;
......
......@@ -86,6 +86,15 @@ public:
/// The NSEC3 parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
/// This method ensures there'll be no memory leak on exception.
/// But addresses allocated from \c mem_sgmt could be relocated if
/// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
/// must be aware of that possibility and update any such addresses
/// accordingly. On successful return, this method ensures there's no
/// address relocation.
///
/// \throw util::MemorySegmentGrown The memory segment has grown, possibly
/// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
......@@ -102,6 +111,15 @@ public:
/// The NSEC3 hash parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
/// This method ensures there'll be no memory leak on exception.
/// But addresses allocated from \c mem_sgmt could be relocated if
/// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
/// must be aware of that possibility and update any such addresses
/// accordingly. On successful return, this method ensures there's no
/// address relocation.
///
/// \throw util::MemorySegmentGrown The memory segment has grown, possibly
/// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
......@@ -375,6 +393,15 @@ public:
public:
/// \brief Allocate and construct \c ZoneData.
///
/// This method ensures there'll be no memory leak on exception.
/// But addresses allocated from \c mem_sgmt could be relocated if
/// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
/// must be aware of that possibility and update any such addresses
/// accordingly. On successful return, this method ensures there's no
/// address relocation.
///
/// \throw util::MemorySegmentGrown The memory segment has grown, possibly
/// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
......
......@@ -131,7 +131,7 @@ ZoneDataLoader::flushNodeRRsets() {
}
// Normally rrsigsets map should be empty at this point, but it's still
// possible that an RRSIG that don't has covered RRset is added; they
// possible that an RRSIG that doesn't have covered RRset is added; they
// still remain in the map. We add them to the zone separately.
BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
updater_.add(ConstRRsetPtr(), val.second);
......@@ -182,35 +182,47 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
const Name& zone_name,
boost::function<void(LoadCallback)> rrset_installer)
{
SegmentObjectHolder<ZoneData, RRClass> holder(
mem_sgmt, ZoneData::create(mem_sgmt, zone_name), rrclass);
ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader, _1));
// Add any last RRsets that were left
loader.flushNodeRRsets();
const ZoneNode* origin_node = holder.get()->getOriginNode();
const RdataSet* rdataset = origin_node->getData();
// If the zone is NSEC3-signed, check if it has NSEC3PARAM
if (holder.get()->isNSEC3Signed()) {
if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
arg(zone_name).arg(rrclass);
while (true) { // Try as long as it takes to load and grow the segment
bool created = false;
try {
SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, rrclass);
holder.set(ZoneData::create(mem_sgmt, zone_name));
// Nothing from this point on should throw MemorySegmentGrown.
// It is handled inside here.
created = true;
ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader,
_1));
// Add any last RRsets that were left
loader.flushNodeRRsets();
const ZoneNode* origin_node = holder.get()->getOriginNode();
const RdataSet* rdataset = origin_node->getData();
// If the zone is NSEC3-signed, check if it has NSEC3PARAM
if (holder.get()->isNSEC3Signed()) {
if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
arg(zone_name).arg(rrclass);
}
}
RRsetCollection collection(*(holder.get()), rrclass);
const dns::ZoneCheckerCallbacks
callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
boost::bind(&logWarning, &zone_name, &rrclass, _1));
if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
isc_throw(ZoneValidationError,
"Errors found when validating zone: "
<< zone_name << "/" << rrclass);
}
return (holder.release());
} catch (const util::MemorySegmentGrown&) {
assert(!created);
}
}
RRsetCollection collection(*(holder.get()), rrclass);
const dns::ZoneCheckerCallbacks
callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
boost::bind(&logWarning, &zone_name, &rrclass, _1));
if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
isc_throw(ZoneValidationError,
"Errors found when validating zone: "
<< zone_name << "/" << rrclass);
}
return (holder.release());
}
// A wrapper for dns::MasterLoader used by loadZoneData() below. Essentially
......@@ -256,7 +268,7 @@ loadZoneData(util::MemorySegment& mem_sgmt,
LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_FILE).
arg(zone_name).arg(rrclass).arg(zone_file);
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(masterLoaderWrapper,
zone_file.c_str(),
zone_name, rrclass,
......
......@@ -57,7 +57,7 @@ ZoneData* loadZoneData(util::MemorySegment& mem_sgmt,
/// \c iterator.
///
/// Throws \c ZoneDataUpdater::AddError if invalid or inconsistent data
/// is present in the \c zone_file. Throws \c isc::Unexpected if empty
/// is present in the \c iterator. Throws \c isc::Unexpected if empty
/// RRsets are passed by the zone iterator. Throws \c EmptyZone if an
/// empty zone would be created due to the \c loadZoneData().
///
......
......@@ -49,14 +49,14 @@ ZoneDataUpdater::addWildcards(const Name& name) {
// Ensure a separate level exists for the "wildcarding"
// name, and mark the node as "wild".
ZoneNode* node;
zone_data_.insertName(mem_sgmt_, wname.split(1), &node);
zone_data_->insertName(mem_sgmt_, wname.split(1), &node);
node->setFlag(ZoneData::WILDCARD_NODE);
// Ensure a separate level exists for the wildcard name.
// Note: for 'name' itself we do this later anyway, but the
// overhead should be marginal because wildcard names should
// be rare.
zone_data_.insertName(mem_sgmt_, wname, &node);
zone_data_->insertName(mem_sgmt_, wname, &node);
}
}
}
......@@ -210,7 +210,7 @@ ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
const NSEC3Hash*
ZoneDataUpdater::getNSEC3Hash() {
if (hash_ == NULL) {
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
// This should never be NULL in this codepath.
assert(nsec3_data != NULL);
......@@ -231,11 +231,11 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
dynamic_cast<const T&>(
rrset->getRdataIterator()->getCurrent());
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata);
zone_data_.setNSEC3Data(nsec3_data);
zone_data_.setSigned(true);
zone_data_->setNSEC3Data(nsec3_data);
zone_data_->setSigned(true);
} else {
const NSEC3Hash* hash = getNSEC3Hash();
if (!hash->match(nsec3_rdata)) {
......@@ -247,14 +247,14 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
}
void
ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
const ConstRRsetPtr rrsig)
ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr& rrset,
const ConstRRsetPtr& rrsig)
{
if (rrset) {
setupNSEC3<generic::NSEC3>(rrset);
}
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
// This is some tricky case: an RRSIG for NSEC3 is given without the
// covered NSEC3, and we don't even know any NSEC3 related data.
......@@ -283,14 +283,14 @@ ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
void
ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
const ConstRRsetPtr rrset,
const ConstRRsetPtr rrsig)
const ConstRRsetPtr& rrset,
const ConstRRsetPtr& rrsig)
{
if (rrtype == RRType::NSEC3()) {
addNSEC3(name, rrset, rrsig);
} else {
ZoneNode* node;
zone_data_.insertName(mem_sgmt_, name, &node);
zone_data_->insertName(mem_sgmt_, name, &node);
RdataSet* rdataset_head = node->getData();
......@@ -334,7 +334,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// Ok, we just put it in.
// Convenient (and more efficient) shortcut to check RRsets at origin
const bool is_origin = (node == zone_data_.getOriginNode());
const bool is_origin = (node == zone_data_->getOriginNode());
// If this RRset creates a zone cut at this node, mark the node
// indicating the need for callback in find(). Note that we do this
......@@ -356,7 +356,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// (conceptually "signed" is a broader notion but our
// current zone finder implementation regards "signed" as
// "NSEC signed")
zone_data_.setSigned(true);
zone_data_->setSigned(true);
}
// If we are adding a new SOA at the origin, update zone's min TTL.
......@@ -365,13 +365,31 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// this should be only once in normal cases) update the TTL.
if (rrset && rrtype == RRType::SOA() && is_origin) {
// Our own validation ensures the RRset is not empty.
zone_data_.setMinTTL(
zone_data_->setMinTTL(
dynamic_cast<const generic::SOA&>(
rrset->getRdataIterator()->getCurrent()).getMinimum());
}
}
}
void
ZoneDataUpdater::addInternal(const isc::dns::Name& name,
const isc::dns::RRType& rrtype,
const isc::dns::ConstRRsetPtr& rrset,
const isc::dns::ConstRRsetPtr& rrsig)
{
// Add wildcards possibly contained in the owner name to the domain
// tree. This can only happen for the normal (non-NSEC3) tree.
// Note: this can throw an exception, breaking strong exception
// guarantee. (see also the note for the call to contextCheck()
// above).
if (rrtype != RRType::NSEC3()) {
addWildcards(name);
}
addRdataSet(name, rrtype, rrset, rrsig);
}
void
ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
const ConstRRsetPtr& sig_rrset)
......@@ -397,16 +415,22 @@ ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
arg(zone_name_);
// Add wildcards possibly contained in the owner name to the domain
// tree. This can only happen for the normal (non-NSEC3) tree.
// Note: this can throw an exception, breaking strong exception
// guarantee. (see also the note for the call to contextCheck()
// above).
if (rrtype != RRType::NSEC3()) {
addWildcards(name);
}
addRdataSet(name, rrtype, rrset, sig_rrset);
// Store the address, it may change during growth and the address inside
// would get updated.
bool added = false;
do {
try {
addInternal(name, rrtype, rrset, sig_rrset);
added = true;
} catch (const isc::util::MemorySegmentGrown&) {
// The segment has grown. So, we update the base pointer (because
// the data may have been remapped somewhere else in the process).
zone_data_ =
static_cast<ZoneData*>(
mem_sgmt_.getNamedAddress("updater_zone_data").second);
}
// Retry if it didn't add due to the growth
} while (!added);
}
} // namespace memory
......
......@@ -57,14 +57,15 @@ public:
/// The constructor.
///
/// \throw none
///
/// \param mem_sgmt The memory segment used for the zone data.
/// \param rrclass The RRclass of the zone data.
/// \param zone_name The Name of the zone under which records will be
/// added.
// \param zone_data The ZoneData object which is populated with
// record data.
/// \param zone_data The ZoneData object which is populated with
/// record data.
/// \throw InvalidOperation if there's already a zone data updater
/// on the given memory segment. Currently, at most one zone data
/// updater may exist on the same memory segment.
ZoneDataUpdater(util::MemorySegment& mem_sgmt,
isc::dns::RRClass rrclass,
const isc::dns::Name& zone_name,
......@@ -72,12 +73,25 @@ public:
mem_sgmt_(mem_sgmt),
rrclass_(rrclass),
zone_name_(zone_name),
zone_data_(zone_data),
hash_(NULL)
{}
hash_(NULL),
zone_data_(&zone_data)
{
if (mem_sgmt_.getNamedAddress("updater_zone_data").first) {
isc_throw(isc::InvalidOperation, "A ZoneDataUpdater already exists"
" on this memory segment. Destroy it first.");
}
if (mem_sgmt_.setNamedAddress("updater_zone_data", zone_data_)) {
// It might have relocated during the set
zone_data_ =
static_cast<ZoneData*>(mem_sgmt_.getNamedAddress(
"updater_zone_data").second);
}
assert(zone_data_);
}
/// The destructor.
~ZoneDataUpdater() {
mem_sgmt_.clearNamedAddress("updater_zone_data");
delete hash_;
}