[bind10-dev] Comments on proposed hooks framework

Francisco Obispo fobispo at isc.org
Mon May 13 17:34:07 UTC 2013


I think my email never made it to the list,

Here it is again:

Hi Tomek,

Perl's Moose has a very nice feature called: Roles.

I believe BIND10 could benefit from some of the features it provides, for which I'm sure there are C++ equivalent ways of doing them.

Roles allow you to extend an object or module's behavior by inserting code at specific places such as:

* before a method is called
* after a method is called
* around a method call.

"before" and "after" allows you to execute code on those specific points, without affecting the original call, that is, if you had a method call ->example it would run like this

 "before" extension->example(args)
  original->example(args)
 "after" extension->example(args)	

"around" is a bit tricker, it allows you to intercept the original method call and work around it, such as:

 "around" extension->example(args){

	// do some stuff

	// call the original method
       original->example(args);

  }


You can even skip the original call if you want to, as long as the "around" extension returns something useful to the caller.

I find myself using more and more, roles, since it allows me to implement features, without having to go back at the original code and change them.

There is a specific way/policy in which methods are called, and I believe they have to do with the order in which they are registered, if multiple modules register hooks to a specific method, they will be called as a cascade.

There a lot to it, but if you're interested, please read:

 http://search.cpan.org/~ether/Moose-2.0802/lib/Moose/Manual/Roles.pod

Most likely this can be done with C++ Templates or a framework.

Best regards,



Francisco Obispo 
Director of Applications and Services - ISC
email: fobispo at isc.org
Phone: +1 650 423 1374 || INOC-DBA *3557* NOC
PGP KeyID = B38DB1BE








On May 13, 2013, at 6:44 AM, Stephen Morris <stephen at isc.org> wrote:

> On 10/05/13 18:57, Tomek Mrugalski wrote:
> 
>> 1. register() function I don't think we need register() function at
>> all. Mukund said that the registration could be done in libload(),
>> but I'd like to go a step further: not require the user library to
>> register its hooks at all. It could be done by the server side.
>> 
>> I was hoping that the library would have the following methods
>> exported:
>> 
>> version() libload() libunload() dhcp6_buffer_rcvd() 
>> dhcp6_pkt_parsed() :
> 
> There are two points here:
> 
> 1. Whether to require the user to register callbacks.
> 
> Having the user-written library export functions of the appropriate
> name or requiring the hook library register them are equally valid
> ways of working.
> 
> Although Tomek has a point that simply providing functions of the
> appropriate name is simpler for the author of the hooks, I don't think
> that having a requirement for registration makes it hugely more
> difficult.  People who are writing hooks will be programmers and will
> be familiar the concept of registration: besides, the only requirement
> is for one registration call per function supplied.
> 
> 
> 2. Use of register()
> 
> If we do go for registration, the idea was that version() and register()
> would be mandatory, whereas libload()/libunload() were optional.  I
> don't mind whether we use this scheme, or require that the registration
> functionality is in libload().
> 
> On both of these issues, I would appreciate some consensus.  Could I
> please have feedback as to which way to go?
> 
> 
>> 2. The position parameter. When the user library calls add(), there
>> is the last parameter called position. It is intended to specify
>> calling order, 0 implies first, 1 implies second etc. It will not
>> work. What if there are 2 libraries and both want to be called
>> first? What if there is a single library that wants to be called
>> 5th? I think we should get rid of that parameter altogether. If
>> there are objections and we want to keep it (Michal mentioned an
>> example of a wrapper library that wants to call decrypt before and
>> encrypt after), then it should be renamed to requested_order and
>> have values: FIRST, LAST, DEFAULT (which implies the order in which
>> the libraries are loaded).
> 
> In his reply to that point
> (https://lists.isc.org/pipermail/bind10-dev/2013-May/004618.html),
> Michal commented:
> 
> 
>> I proposed to include priority, not position. So you would say to want to be
>> called very early (0) or very late (1000) or somewhere in the middle. The
>> advantage is that you can position relatively against the hooks you already
>> know.
> 
> On reflection - and for the first implementation at least - I think that
> it would be best to have per-library lists, so it will be up to a
> library to register multiple callbacks for a hook point in the correct
> order.
> 
> If required, we could later add a "guru" interface into the system that
> would allow libraries to manipulate the library callback order. (This
> would address Michal's example of a wrapper library.)
> 
> 
>> 3. return status. Two of return codes will not work: SUCCESS_SKIP
>> and COMPLETE_SKIP. Server can't skip certain operations and then
>> continue as if nothing happened.
>> :
>> Fortunately, there's a simple alternative. We already plan to do 
>> pre-action and post-action hooks. So if the callout wants to
>> override a decision (e.g. select a different subnet), then it can
>> set it in the post-action hook.
> 
> The idea was the XXX_SKIP would only be valid where it was meaningful
> for them to be.  By returning a SKIP code, the callout is signalling
> that it has replaced the relevant action f the calling program.  So in
> the case of the selectSubnet callout, the hook would only return a SKIP
> code if it had selected the subnet itself. (Michal makes this point in
> his reply.)
> 
> However, I concede that it does introduce some confusion.  I suggest
> removing the SKIP codes: if a hook can allow the callout to signal
> "skip" functionality, it can do so via a parameter.
> 
> 
>> 4. Server-side registration of hook types. I don't like presented 
>> apporach (enum + separate calls to registerHook that essentially 
>> register only string-to-enum mapping). It would be better to
>> define enum, an then static array of struct {enum type, string
>> name}; (that is later used to populate two maps, because we'll
>> likely need both type to name and name to type translations).
> 
> The idea of using an enum was to address performance issues.  The code
> will be full of constructs along the lines of:
> 
> if (hook is present) {
>   // Incur overhead of setting parameters only if we will use them
>   set parameters
>   call hook
> }
> 
> Registration of the available hooks was proposed to catch any typos in
> the calling library when they register their callbacks.  This allowed
> for the association of an integer with the hook name, and thus the idea
> of using the integer to locate the callout when calling a hook rather
> than the hook name.  Comparing an integer against limits is cheaper than
> a string comparison, so reducing overhead.
> 
> Thinking it through, one problem of the proposed approach is that the
> hooks for a particular server will all have to be known and defined in
> one place.  This might make management a bit awkward if some of the
> hooks are located in libraries.  Instead, I now propose to allocate the
> hook numbers dynamically, e.g.
> 
> int nsas_choose_ns = 0;
> 
> void nsas_register_hooks(GlobalHandle& handle) {
>   nsas_choose_ns = handle.registerHook("nsas_choose_nameserver");
> }
> 
> Libraries provide hook registration functions which must be called by
> the main program when registering the hooks.  It does mean that the main
> program would need to know about the library hook registration
> functions, but it wouldn't need as detailed a knowledge of all the hooks.
> 
> 
>> 5. Calling a callout in the current form does not allow to set
>> const parameters. They are essential.
>> :
> 
> Use of boost::any allows this.  If the parameters are stored as a
> collection of boost::any objects, the "const"ness of the object is
> preserved: attempting to cast to the wrong type, or to a non-const
> object of the correct type, will cause an exception to be thrown.
> 
> (Having say that, there is nothing to prevent the hook using const_cast
> to modify the data.)
> 
> 
>> 6. remove() parameter "position" does not make sense. See comment
>> #2. There are too many possible combinations. We should pick
>> remove(const char* hook_name) or remove(CalloutPtr callout).
> 
> Accepted.  This is a logical consequence of the change proposed to point
> (2).
> 
> 
>> 7. removeall() if definitely a bad idea. One library cannot tweak 
>> unregister other library's callouts!
> 
> With the change proposed in point (2), removeAll() would now only remove
> all callbacks on a given hook for the library issuing the call.
> 
> 
>> 8. It is confusing what is available to the user library and what
>> is part of the server implementation. If we want to continue with
>> this registration design, then we should not expose
>> GlobalCalloutHandle at all. It should be hidden in server
>> implementation. The only API user library allowed to use would be:
>> 
>> hook_add(CalloutPtr calloutFunction, const char* hook_name, 
>> RequestedCallOrder order);
>> 
>> Where RequestedCallOrder is one of FIRST, LAST, DEFAULT.
> 
> Fair point: I suggest GlobalCalloutHandle is split into two classes: one
> handling the server-side part of the hooks manipulation, and one the
> library side.  The library-side part has the add/remove methods for the
> hooks, and set/get methods for library-specific context (but see below
> regarding these).
> 
> 
>> 9. This design does not specify library registration order. This is
>> not very important, but we should specify it for completeness. I
>> think the procedure can be as follows: configuration specifies a
>> user libraries directory. Libraries are loaded from that directory
>> in alphabetic order.
>> 
>> Users then could specify the loading order in similar way as init
>> scripts are loaded in /etc/rc?.d/.
> 
> We can do that, or we can simply set the library order in the
> configuration, the order of the libraries in the configuration being the
> order in which they are loaded.
> 
> 
>> 10. get/set. We also need a method getNames() or similar to get
>> list of available parameters. 
> 
> We can do that.
> 
> Looking back at the interface for the get/set, is set() really needed?
> I'm thinking that it would be better to pass everything as pointers:
> 
> CalloutHandle::parameter(const char* name, T*& param_ptr);
> 
> That way, the callout can directly manipulate the objects residing the
> in the calling program.  It avoids the need for copy constructors and
> assignment operators to be defined for the objects being passed to the
> callouts.  If the
> 
> 
>> We need to have the ability to pass
>> const arguments to user library (see my previous comment about
>> passing pieces of configuration for example use case).
> 
> See above with regards to boost::any.
> 
> Stephen
> _______________________________________________
> bind10-dev mailing list
> bind10-dev at lists.isc.org
> https://lists.isc.org/mailman/listinfo/bind10-dev

Francisco Obispo 
Director of Applications and Services - ISC
email: fobispo at isc.org
Phone: +1 650 423 1374 || INOC-DBA *3557* NOC
PGP KeyID = B38DB1BE



More information about the bind10-dev mailing list