BIND 10 #1966: logger formatter does too much in the destructor

BIND 10 Development do-not-reply at isc.org
Tue May 15 07:32:20 UTC 2012


#1966: logger formatter does too much in the destructor
-------------------------------------+-------------------------------------
                   Reporter:         |                 Owner:
  jinmei                             |                Status:  new
                       Type:         |             Milestone:  Next-Sprint-
  defect                             |  Proposed
                   Priority:         |            Resolution:
  medium                             |             Sensitive:  0
                  Component:         |           Sub-Project:  Core
  logging                            |  Estimated Difficulty:  0
                   Keywords:         |           Total Hours:  0
            Defect Severity:  N/A    |
Feature Depending on Ticket:         |
        Add Hours to Ticket:  0      |
                  Internal?:  0      |
-------------------------------------+-------------------------------------

Comment (by jinmei):

 Replying to [comment:4 vorner]:
 > This will require both less convenient use and expensive change of all
 our logging code. And I don't see the benefit.

 > Generally, throwing from destructor can cause trouble. But it's because
 C++
 > doesn't really handle the case when two exceptions are being thrown at
 once
 > (because there are problems with explicit memory deallocation and
 generally
 > exceptions and explicit deallocation cause trouble). But I don't think
 there
 > can be two exceptions at once in this case.

 I'm not sure if we are talking about the same things, but in my
 understanding there are two specific reasons why we should avoid
 throwing from a destructor:

 1. If the destructor in question is called during stack unwinding due
   to another exception thrown, the nested exception immediately
   terminates the program with no other loophole.
 2. Destructors are often responsible for critical resource release
   operations.  If a destructor throws in the middle of it, we can
   never complete the cleanup.

 Maybe you meant problem 1, and didn't think it happened for the Formatter
 class?  Actually, unless I miss something it can happen for it; and,
 problem 2 can clearly happen for it.

 As for #1, consider the following expression:

 {{{#!cpp
     // where thisOneThrows is expected to return a std::string but can
 throw.
     Formatter(INFO, message, this).arg(thisOneThrows());
 }}}

 If `thisOneThrows()` throws, the `Formatters` destructor will be
 called in the middle of stack unwinding due to that exception.  Or,
 arg() itself is relatively a complicated method, internally calling
 boost and other standard library functions, could throw, too.

 Problem 2 should be more obvious from the implementation:

 {{{#!cpp
     ~ Formatter() {
         if (logger_) {
             checkExcessPlaceholders(message_, ++nextPlaceholder_);
             logger_->output(severity_, *message_);
             delete message_;
         }
     }
 }}}

 Think about what will happen for `message_` when the destructor throws
 before `delete` (if you are about to insist we could make it safe by
 tweaking the implementation, read the next paragraph first).

 But, actually, I was a bit surprised that we needed to have such a
 discussion in the first place.  IMO, it's too fragile to rely on the
 specific behavior of a class to selectively allow such a generally
 (severely) discouraged practice as throwing from a destructor; as
 we've seen, humans can easily make errors in the behavior analysis,
 and even if the analysis is correct at one point in time, a small
 future change can easily break it and trigger a surprised behavior.
 So, to me, this is something we must always meet without even
 considering whether it can be an exceptional case to the general
 convention.  We in fact experienced one small surprise that was
 addressed in #1964.  One might want to argue it's a special case of
 intentionally triggered test scenario, but to me it looks like a tip
 of the iceberg.

 As for convenience, I admit it would surely be less convenient, but I
 don't think it's that much.  The implementation of "output() and
 endarg()" should be mostly trivial (I hope we agree on this point), so
 the cost and inconvenience are the caller side.  We'll have to do:

 {{{#!cpp
         // instead of this:
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
                   .arg(rrclass);

         // we do either:
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
                   .endarg(rrclass);

         // or this:
         LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_MEM_DATASRC_DISABLED)
                   .arg(rrclass).output();
 }}}

 The amount of code to be updated is admittedly pretty big, but
 replacement should be quite straightforward.  For future log
 messages, I believe we often copy-paste-modify existing ones, so
 the inconvenience will be even smaller (the developer might even be
 unaware of the trick).  Other potential inconvenience is the risk
 that we forget endarg() or output() and lose the corresponding log
 message.  For that I assume we'll soon enable the logger-check and can
 at least detect by our buildbots.

 Regarding the cost...I really don't see how it could be more expensive
 than the current implementation, both in terms of memory and speed.
 Besides, at least regarding the speed, logging itself is relatively a
 costly operation, and isn't expected to be so frequently called in a
 performance sensitive path.

 So, over all, this seems to me to be really a good deal for such a big
 thing like making a destructor safer.

 All that said...if others are still unhappy about the inconvenience of
 the alternative, I'd at least suggest making the destructor exception
 free in the most trivial way:

 {{{#!cpp
     ~Formatter() {
         if (logger_) {
             try {
                 checkExcessPlaceholders(message_, ++nextPlaceholder_);
                 logger_->output(severity_, *message_);
             } catch (...) {}
             delete message_;
         }
     }
 }}}

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


More information about the bind10-tickets mailing list