BIND 10 #2974: Implement Hooks Framework (part 1) - all but library loading and unloading
BIND 10 Development
do-not-reply at isc.org
Mon Jun 10 19:43:54 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-20130606
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
-------------------------------------+-------------------------------------
Changes (by stephen):
* owner: stephen => tomek
Comment:
Wow, where to start? I'll take this a bit at a time, but first I'd like to
clear up some terminology:
* !Hook/Hook Point - used interchageably, this is a point in the code at
which a call to user-written functions is made. Each hook has a name and
each hook can have any number (including 0) of user-written functions
attached to it.
* Callout - a user-written function. This is so-named because the server
"calls out" to the library to execute a user-written function.
> My idea for hooks was as discussed yesterday on jabber: For each hook
point (around 25 defined for DHCPv4 and DHCPv6) there should be a list (or
any other container that can be iterated over cheaply) of installed
callouts. The hook execution code on the server-side should look like
this:
As agreed on Jabber, the list of callouts attached to each hook is a
single place (the !CalloutManager), instead of being in per-library
objects.
> The code under review implements many features that are not needed in my
opinion. Here's my recommendation for the user library designer:
>
> * if you need to store something for the whole lifetime of the library,
allocate it in load() and deallocate in unload()
>
> * if you need to store something for packet lifetime, allocate it in
context_create callout and deallocate in context_destroy callout.
>
> * if you need to share something with other libraries, use
setArgument(). All callouts called after yours will have access to that
information
Yes, that's the philosophy behind it.
> Please move all code to the src/lib/hooks directory.
I'd come to that conclusion myself when considering the hook code, mainly
because it will need to include some logging calls. I'll do that as the
first task of #2980.
> There is no Developer's Guide documentation. Please add .dox file that
explains the big picture. It doesn't have to be long.
I'll split #2982 into user-documentation (how to write a hook) and
maintainer documentation (how the hooks framework works). Regarding the
former, I'll check with Jeremy as to whether this should be a .dox file or
an Docbook guide.
> !ServerHooks class description: "points at in the server" (please remove
'at').
Done.
> registerHook() - this method should be renamed to avoid confusion with
the hooks registered by the user library.
The current naming is that !ServerHooks contains "registerHook", which
makes a hook known to the hooks system. Libraries use "registerCallout"
which takes as argument the name of a hook.
> !ServerHooks() constructor: Do we have a naming convention for hook
names?
No - they are just strings. We can name them as needed. However, we may
want to take into account the fact that we plan for "standard callouts" to
have the same name as the hooks to which they will be attached, and that
the standard for C/C++ function names is to use lower0case.
> Since !ServerHooks is created just once and is kept around until the
server is shut down, it should be a singleton. I don't like the idea of
using shared_ptr (e.g. in !LibraryHandle constructor). This is against the
philosophy of shared_ptr that is expected to a garbage collector. No such
thing is needed for !ServerHooks.
I'll probably do that, but I'd like to defer it to #2980 to avoid making
any more changes than absolutely needed at the moment.
> getIndex()
> You are mixing exceptions and negative values for reporting errors. I'm
ok with that, but is there a reason for that?
No you're right, it should be reported via an exception and this has been
changed.
'''src/lib/util/hooks/server_hooks.cc'''[[BR]]
> We need to have a list of defined hooks.
The hooks will be different for the different modules (resolver, auth,
dhcp4 etc.) Regarding DHCP, I thought we did have a [wiki:DhcpHooks list
of DHCP hooks].
> I would prefer to have a separate header file with hook names. That
header file should have little more than a list of static consts
withexplanations.
Hooks are registered by the server, so it is up to the server author how
they do that. The updated version of !ServerHooks offers the possibility
of distributed registration functions. However, if you want to register
all the hooks up fron in one module, that's OK as well.
> It would be useful for at least 2 purposes. First,
> it would detect any misspellings at compilation time.
As hook names are strings, I'm not sure how misspellings would be
detected.
> For each hook, we need to specify at least the following:
Yes, we need documentation, agreed. But this documentation could start
getting very complex and may be better in a !DocBook. In part, there are
three audiences for documentation:
1. Administrators
2. User-library programmers
3. BIND 10 maintainers
The question is whether the documentation for (2) should be mixed with
that for (3).
> I don't like the CONTEXT_CREATE and CONTEXT_DESTROY names. They are too
vague. Which context you have in mind? I would rename them to
DHCP6_PKT_CONTEXT_CREATE ...
These hooks are general hooks managed by the hooks system. The idea is
that when a !CalloutHandle is created, CONTEXT_CREATE is automatically
called: when the handle is destroyed, CONTEXT_DESTROY is called. A
!CalloutHandle is supposed to be associated with a "request" that the
server is processing.
(I say "request" and not "packet" because in the DNS code, a packet only
exists when it is received, and when a packet is created before being
sent. In the meantime, information about the request is passed round the
system in a different class.)
> I have doubts if such callout would be useful as you don't really know
at this stage what type of packet you're about to get, so you user library
can't really prepare for it.
Possibly in DHCP, but for packets received by the DNS resolver (say), all
packets are requests (except for those that are rejected as being
invalid).
'''src/lib/util/tests/server_hooks_unittest.cc'''[[BR]]
> !DuplicateHooks test.
> "duplcate" => "duplicate"
Done.
> There is no test that checks that index returned for missing hook is -1.
Well spotted. Test for missing hook now added.
src/lib/util/hooks/library_handle.h
> !LibraryHandle description: "asssociated" - please remove one 's'.
Done.
> I have 2 comments about setContext(), getContext(), deleteContext() and
similar. First, what type of information can be stored here? This data
will be stored per-library, right? So that info in stored/shared by the
whole library. Why do we need it? Each library can handle this on its own.
That's a fair point, removed. I got carried away by the usefulness of
boost::any.
> deregisterCallout() should return bool - a status if the deregistration
was successful.
Done.
> Why does it take callout parameter? Do we allow one library to install
more than one hook for a given hook point?
Yes, it is allowed for a library to install multiple callouts on a hook.
> deregisterAll() - the description should be clear that it deregisters
all hooks from *this* library. Hooks intalled by other libraries will not
be affected. I thought that deregisterAll() would deregister all callouts
from all hook points.
Fair point, documentation updated.
> getHookIndex() - why do we need this method here? Is it because you want
to allow the library to register its own hooks?
Internally it was used to convert the -1 returned by the
ServerHooks::getIndex() to an exception. Since that object will now throw
an exception, it is not needed and has been removed.
> If move to the design I proposed, registerCallout() and
deregisterCallout() methods should be moved to !ServerHooks and be
declared static. deregisterAll(), calloutsPresent() and callCallouts()
should be removed.
!ServerHooks is an object only associated with hook names. A modified
!LibraryHandle stays to restrict the registration functions to modifying
only callouts associated with that library.
> If we remove the (not needed in my opinion) capabity for one library to
have multiple hooks, the whole structure will be a lot simpler.
It will, but the current system allows flexibility. As we don't know what
user libraries will be required to do in the future, we should not close
off options. For example, a user library may itself dynamically load
another library and want to install some of those functions as callouts.
> Here's how I imagine the callouts would work. For each hook point, there
is a list of installed callouts (from all libraries). The server prepares
data (couple call to setArgument()) once and then calls registered
callouts by iterating over the list.
That's what is now done.
> Comment for !CalloutPtr: I understand why the return type is int (there
are no enums in C),
Actually there are enums in C.
> but we need well defined return types (SUCCESS, ERROR, SKIP, etc.).
SUCCESS and COMPLETE are now public values in !CalloutHandle. I'll leave
all positive values as errors - we may decide to log a returned error
value, so don't want callouts to feel constrained as to what error codes
they return.
'''src/lib/util/hooks/library_handle.cc'''[[BR]]
> What is the reason to split checkHookIndex() and getHookIndex()? It is
inefficient to call checkHookIndex() if you need to call getHookIndex()
later anyway.
This should now be fixed.
> callCallouts() calls all callouts, stopping if the "skip" blag is set.
Skip flag was supposed to mean "skip the next operation to be conducted by
the server".
I'm afraid that I confused myself here :-(
'''src/lib/util/hooks/callout_handle.h'''
> !NoSuchArgument comment:
> "attempt is made access an argument" =>
> "attempt is made to access an argument"
Done.
> I have strong objection to the lifetime of the !CalloutHandle object
from the performance perspective. The description says that it is create
per packet and per library.
Where did it say that? (See below for an explanation.)
> How are libraries are supposed to pass data between them? If I
understand the life span of all the objects, they can't. This is a must-
have capability.
As you wrote above, libraries can pass information via arguments.
However, if two libraries are so tightly-coupled that they pass private
(i.e. nothing to do with the server) information between them, they
perhaps should be one library.
> How/when do you envision this handle to be created and later destroyed?
According to the description, !CalloutHandle would have exactly the same
lifetime as Pkt4 or Pkt6 objects, but will be multiplied by the number of
libraries installed. This has serious issues. Can you describe
!CalloutHandle object life-cycle? In particular, if you have 10 libraries
and 20 hook points. How many !CalloutHandle objects (including
initialization) you need? 200? If that is true, that's a perfromance
killer.
I think there's been a misunderstanding.
There is one !CalloutHandle per request processed by a server. When
created, internally it creates a number of "context" objects capable of
storing name/value pairs. The number of these objects is equal to the
number of libraries loaded. (Actually it is lazy creation - they are only
created when a callout in a particular library creates packet-specific
context.)
When the !CalloutHandle is passed to a callout in the first library - any
callout registered by that library, whatever the hook - the
getContext/SetContext methods will set and retrieve data from the first
context object. When a callout from the second library is called - again,
any callout registered by the library, whatever the hook -
getContext/setContext will access the second context object.
In this way each library has its own context "namespace". The first
library can pass per-request information between callouts by setting value
named "foobar", secure in the knowledge that even if a second library
(possibly written elsewhere) also uses "foobar", that use will not affect
the first library's operation.
> I think the !CalloutHandle should have modified lifespan. It is per
packet, per hook point, shared among all libraries that installed a
callout for a given hook point.
I strongly disagree. We need a way to pass information between callouts
in the same library. As I note above, if two libraries coupled enough need
to pass information between one another, the functions should perhaps be
in the same library. (Alternatively, they can use {set,set}Argument().)
> I do think that !ContextCollection, context_collection_ and all
associated methods that operate on it are not needed. The library can
store such information on its own if it is needed. See my comments about
life span above.
This would make it much more complicated to pass information between
callouts in the same library. Yes, a library could do it - but it would
have to store information keyed by some identification and every library
requiring such context would have to provide its own mechanism. That
identification would still have to be passed from hook to hook throughout
the server. (A packet address might not be enough - for example, a DNS
resolver can generate multiple separate queries when processing a request.
Added to that, unlike the DHCP server, the resolver can be processing many
separate requests at the same time. The DNS team have already identified
a need to track what downstream requests are made when a given request
appears: the hooks mechanism - with per-request context - provides a
ready-made way of doing this.)
Regarding the DHCP server itself, it processes one packet at a time. If
this approach is kept, it would be entirely possible for it to use a
singleton !CalloutHandle and just modify it at every call. (This may mean
adding some methods to clear out all data, but that is easily done.)
--
Ticket URL: <http://bind10.isc.org/ticket/2974#comment:5>
BIND 10 Development <http://bind10.isc.org>
BIND 10 Development
More information about the bind10-tickets
mailing list