BIND 10 #2974: Implement Hooks Framework (part 1) - all but library loading and unloading
BIND 10 Development
do-not-reply at isc.org
Wed Jun 12 20:21:43 UTC 2013
#2974: Implement Hooks Framework (part 1) - all but library loading and unloading
-------------------------------------+-------------------------------------
Reporter: stephen | Owner: tomek
Type: task | Status:
Priority: medium | reviewing
Component: Unclassified | Milestone:
Keywords: | Sprint-DHCP-20130620
Sensitive: 0 | Resolution:
Sub-Project: DHCP | CVSS Scoring:
Estimated Difficulty: 0 | Defect Severity: N/A
Total Hours: 0 | Feature Depending on Ticket:
| Add Hours to Ticket: 0
| Internal?: 0
-------------------------------------+-------------------------------------
Comment (by stephen):
I noticed you had partially completed the review, and as most of the
points don't require a code modification, decided to respond to them now.
From comment:6
> Ok, but please update 2980 with a simple note for this and previous
work. I don't want those things to slip through the cracks.
(About making !ServerHooks a singleton) Done.
> Fair enough. I now know what the CONTEXT_CREATE and CONTEXT_DESTROY are
for, but the next person who will look at it, will need to get through the
same discovery pains as I did. If someone asks me: what the context_create
and context_destroy callouts are for, I don't have any place to point him
to.
We could call them something like "initialize" and "rundown". The point
is that the "initialize" callout gets control before any of the "real"
hooks are called for a packet/request, and the "rundown" gets control
after all have been called. In both cases, this is regardless of what
hooks are actually called.
> Did you mean "negative" values for errors? COMPLETE now has value 1.
Whoops!. Now modified to have a value of -1.
> Not really. Two real life examples:
> 1) library A does client classification, library B does logging
> 2) library A does access control (checks against customer database,
checks who paid their bills etc.), library B does statistic
Taking (1) as an example, the point I was trying to make is that if the
server doesn't provide classification information, how does library B know
where to look for the information provided by library A?
In any case, they can do it through the argument list. We may want to set
a convention that BIND 10 guarantees that no argument name will start with
a special character (e.g. a "$"), leaving a namespace that inter-library
communication can use.
> I think we're arguing about different things, while in fact both use
cases are covered. Is the following true?...
Yes.
From comment:7
> The design does not allow for extra libraries to be loaded at runtime.
This is something we need to be ready for. You start a server, then
configure it to use one library, then configure it to use another one. You
can't assume that the number of libraries is constant.
That is handled via reconfiguration (not yet written). The intention is
for the configuration to specify an array of libraries: these are read and
the libraries loaded one at a time. On a reconfiguration, libraries are
unloaded and reloaded.
> Who is intended user of the public methods in !CallOutManager: server-
side code or user library? I assume it is server code, right? Again, I
have trouble understanding how the intended usage model looks like.
It's described in the [wiki:Bind10HooksFramework design document].
!CalloutManager is a server-side construct. !LibraryHandle - which is a
restricted interface into !CalloutManager - is made available to callouts.
> Who is going to call setLibraryIndex()? I'm concerned with the fact that
the registerCallout() may be called by the user library, right
As described in the [wiki:Bind10HooksFramework design], the library index
is set by the server-side code when calling the callouts - each callout
has the library index associated with it. However, because only the
!LibraryHandle is made available to the callout, the callout cannot see or
access that index.
LibraryHandle::registerCallout() can be called by user-written code in one
of two places:
1. in the library's load() function (if registering non-standard
callouts). In this case, the index is set by the library loading code.
1. in a callout. User callouts are called by
CalloutManager::callCallouts(), and this sets the index before calling a
user callout.
> The functions in .h file are getting bigger and bigger.
They're not that big - typically where the code is little more than an
"if" statement. If you really want, I can move them to the .cc file.
> CalloutManager::registerCallout() Isn't the
checkLibraryIndex(current_library_) check redundant? It was already
checked when setting up current_library_.
As noted, it's a sanity check to guard against a programming error. The
idea is to get a meaningful message if (for example), this call is made
after all libraries have been removed.
> Why do you require to have the callouts from the same library be grouped
together? Michal gave specific example when he wants them to be out of
order: the encryption + signing
The order of callouts is the order that the libraries are specified in the
configuration. The extra check is for dynamic addition of callouts - if
the order is specified as library A then library B, and library A
dynamically adds a callout on a hook, we guarantee that it will be called
before any callouts registered by library B for that hook.
Taking Michal's example, if both encryption and signing are in the same
library and two callouts are to be registered for a hook, the library's
load() function can register them in the order it requires. If they are
in separate libraries, they are run in the order the libraries are
specified in the configuration.
> There's 27 hook points for DHCPv6, multiplied by 16000 (current number
of transactions per second) gives you 432 thousand copies per second.
I think you're being overly dramatic. First off, I doubt if we ever will
have all 27 hooks with callouts attached to them: if you are checking
whether there is a callout present before calling it (to avoid overhead
attached to adding argument information to the !ContextHandle), the copy
will never be called in those cases. Secondly, does every packet pass
through all 27 hook points? Finally, it's a copy of a few bytes (8 bytes
* number of callouts on a 64-bit system); that overhead is likely to be
dwarfed by the code involved any callout called (and the more callouts
that are called on a hook, the more that overhead is amortised between
then).
I suggest we leave it until we do a performance measurement. We'll load a
library with null callouts on every hook point and measure the overhead
with the copy in and with it out. If there is a significant difference,
I'll remove it.
> deregisterCallout() - that's a tricky removal code. I think I understand
how it works, but isn't there a simpler way to remove something from a
vector?
I can't think of one other than effectively doing the same:
1. Working through the the vector comparing elements against my test
element (the equals_to())
1. Moving non-matching elements to lower vector indexes (the remove_if())
1. Truncating the vector (erase()).
I could use a std::list instead, but I don't think it's worth it for the
simplification of one function - especially as this particular construct
is documented in a book for which the reference is supplied.
'''src/lib/util/tests/callout_manager_unittest.cc'''[[BR]]
There are some valid points here that need code modification. These will
be addressed when all reviews have ended. However...
> !CalloutManagerTest.!CallCalloutsError: I'm somewhat uneasy with the
fact that the first library that returns an error will cause other
libraries to not be called. What if the first library is logger and it
reports error, because partition for logs is full? Then the code will stop
calling libraries for access control, host reservations etc. That would
essentially disable hooks and the server would continue to run in its
"vanilla" form. This would make a very fragile system, with easily
triggered domino effect. The first library that falls over will trip all
others as well.
That is a pertinent observation and I'm in two minds here.
On the one hand, you are correct with your example in that a failing
callout could effectively disable the hooks system. On the other, perhaps
a callout started modifying the data passed to it, failed and left the
data in an inconsistent state? That inconsistency could cause a crash in
another callout.
Allied to this is the question of whether we should have a COMPLETE status
at all? The original idea was to allow a callout to have the last word in
whether processing was complete. But a poorly-written callout - for
example, returning an unitialised status - could cause random skips.
Also, it struck me that the callout manager needs some form of logging
precisely to record what library (if any) returns an error. That will be
added in the next ticket (which adds code to load the libraries); the
reason it is being delayed is because any error message should include the
name of the library, not the index.
Perhaps the easiest way forwards is to get rid of COMPLETE and to log
errors returned by callouts but otherwise ignore them.
--
Ticket URL: <http://bind10.isc.org/ticket/2974#comment:8>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development
More information about the bind10-tickets
mailing list