BIND 10 #1747: refactor auth::Query so it's reusable

BIND 10 Development do-not-reply at isc.org
Tue Mar 13 06:04:29 UTC 2012


#1747: refactor auth::Query so it's reusable
-------------------------------------+-------------------------------------
                   Reporter:         |                 Owner:  jinmei
  jinmei                             |                Status:  reviewing
                       Type:  task   |             Milestone:
                   Priority:  high   |  Sprint-20120320
                  Component:         |            Resolution:
  b10-auth                           |             Sensitive:  0
                   Keywords:         |           Sub-Project:  DNS
            Defect Severity:  N/A    |  Estimated Difficulty:  4
Feature Depending on Ticket:  auth   |           Total Hours:  0
  performance                        |
        Add Hours to Ticket:  0      |
                  Internal?:  0      |
-------------------------------------+-------------------------------------

Comment (by jinmei):

 Replying to [comment:5 jelte]:

 First, regarding this point:

 > Regarding these vectors; process() is not exception-safe (just like it
 was not and is not safe because it modifies response); they can be
 modified. However, these vectors are reset both in createResponse and
 before process() starts to do any work, which i think for now is good
 enough.

 In my definition of exception safety it's safe in that it doesn't
 cause such disruption as memory leak on exception; but it doesn't
 provide the strong exception guarantee (either complete success or
 fall back to the initial state on exception).  I interpret you meant
 this, and that's true.  And, in fact, it's probably almost impossible
 to provide the strong guarantee for the response message (unless we
 prepare a temporary copy beforehand, which would be too costly), so I
 think it's acceptable.

 Regarding the internal buffers, I think it's relatively easy to
 guarantee that: We can use a small helper "scope" object

 {{{#!c++
 class QueryCleaner {
 public:
     QueryCleaner(auth::Query& query) : query_(query) {}
     ~QueryCleaner() { query_.reset(); } // we need to make reset public
 private:
     auth::Query& query_;
 };
 }}}

 And we encapsulate the query object before calling process(), and let
 `QueryCleaner` clean it up either on success or exception:

 {{{#!c++
         if (memory_client_ && memory_client_class_ ==
 question->getClass()) {
             const RRType& qtype = question->getType();
             const Name& qname = question->getName();
             QueryCleaner cleaner(query_);
             query_.process(*memory_client_, qname, qtype, message,
 dnssec_ok);
         } else {
 }}}

 But realistically it's quite unlikely we see exception from process()
 with the in-memory data source, so if you want to defer it to a
 separate task, it's okay for me.  At least as long as the vectors are
 cleared at the beginning of process() there shouldn't be anything
 wrong even if an exception happens.

 Comments on the implementation follow:

 '''misc'''

 - I made a few minor editorial/style fixes to the branch.
 - do we need a changelog?  maybe not because it's mostly an internal
   refactoring, but I'm asking anyway since it still changes the
   interface given in a public header file.

 '''higher level points'''

 - Query now seems to be copyable.  Is that okay?  There's probably no
   particular reason it cannot be so, but if we don't have a reason we
   want to make a copy of it, it's probably safer to make it
   noncopyable (with noting these) to avoid making an unexpected copy.
 - Why is Query::qname_  (and the qname parameter of process()) a real
   object?  This will cause a relatively expensive copy for every call
   to process().  It's also now mutable (while it still doesn't seem to
   have to be so), which makes the code more fragile (we could
   accidentally override it in the middle of process()).  If there's no
   particular reason, I'd suggest changing it to 'const Name*'
   process() and initialize() could take 'const Name&' and set qname_
   to its address.
 - Same for qtype_ and dnssec_.  Also, especially for qname_ and
   qtype_, I'd be nervous about their initial values.  While I
   understand if we really need to keep a real object there's not many
   choices and sometimes we end up using arbitrary initial value.  But
   such ad hoc values often lead to bugs - I basically want to keep
   things "undefined" when they don't have real meaning.  Maybe we can
   consider using boost::any (not sure about its performance
   impact)...continue to the next point:
 - Considering these, I wonder whether we need to keep the query
   parameters within the Query class.  From a quick look they are only
   for the convenience of the support private methods - we could also
   pass these parameters to these methods instead of storing them in
   Query and have the private methods refer to them (right)?  If
   passing around many parameters causes readability issue, we can
   think about introducing a helper structure, say QueryParam, which is
   essentially a read only tuple of the query parameters.  The Query
   class could either hold it as a pointer (and initialize reset it to
   NULL except while doing process()) or pass that object to private
   methods (then there'll be only one additional parameter).  The
   QueryParam can be constructed for every query, so if we need to make
   a copy of Name/RRType we don't have to use placeholder initial
   objects for them.

 - I guess we might want to trim excess vector capacity in case it
   happen to hold unusually large number of items:
 {{{#!c++
 const size_t MAX_RESERVE_RRSETS = 64; // note: arbitrary choice
 if (answers_.size() > MAX_RESERVE_RRSETS) {
     vector<ConstRRsetPtr> new_answers;
     new_answers.reserve(MAX_RESERVE_RRSETS);
     answers_.swap(new_answers);
 }
 }}}

 '''query.cc'''

 - We can remove most of the boost::const_pointer_cast (in fact we can
   consolidate them within `RRsetInserter`).  (Of course, the need for
   it itself is bad even if it's used "once" and we should eventually
   solve this, but that's another issue).  I believe this fix is
   trivial so I made it to the branch directly.

 - maybe a matter of taste, but I'd let processDSAtChild() call
   createResponse() internally and keep the caller doing nothing.
   Especially for the case of DELEGATION, since this switch is pretty
   big (which I think would also require refactoring, but that's
   another issue), it makes me nervous to see the break because it's
   not so obvious from the code around the "DELEGATION" case what
   happens after 'break'.  Note also that with the current convention
   we'd also have update the documentation in the header:
 {{{#!c++
     /// It returns true if this server has authority of the child;
 otherwise
     /// it returns false.  In the former case, the caller is expected to
     /// terminate the query processing, because it should have been
 completed
     /// within this method.
 }}}
   because the "In the former case,..." is not very accurate any more.

 - This createResponse() doesn't seem to be necessary:
 {{{#!c++
         response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
         response_->setRcode(Rcode::REFUSED());
         createResponse();
         return;
 }}}

 - If we copy objects in initialize(), we could just pass references to
   process() and initialize(), then we can save a couple of copies
   (although the compiler may optimize these one-shot copies).  But see
   also the design level discussions above.

 '''query.h'''

 - I'd say "message" instead of "packet" (my usual suggestion in such a
   context:-)
 {{{#!c++
     /// This will take each RRset collected in answers_, authorities_, and
     /// additionals_, and add them to their corresponding sections in
     /// the response packet.
 }}}

 - Maybe "Default constructor" is better than "Empty constructor"?
 {{{#!c++
     /// Empty Constructor.
     ///
     /// Query parameters will be set by the call to process()
     ///
     /// This constructor never throws an exception.
     ///
 }}}
   Also, technically, it could throw in constructing qname_.

-- 
Ticket URL: <http://bind10.isc.org/ticket/1747#comment:7>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development


More information about the bind10-tickets mailing list