BIND 10 #2980: Implement Hooks Framework (part 2) - library loading and unloading

BIND 10 Development do-not-reply at isc.org
Thu Jul 4 11:54:18 UTC 2013


#2980: Implement Hooks Framework (part 2) - library loading and unloading
-------------------------------------+-------------------------------------
            Reporter:  stephen       |                        Owner:  tomek
                Type:  enhancement   |                       Status:
            Priority:  medium        |  reviewing
           Component:  Unclassified  |                    Milestone:
            Keywords:                |  Sprint-DHCP-20130717
           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:

 == Replying to comment:4 ==

 > "Bit larger" is an understatement.
 Sorry.

 > '''src/lib/hooks/hooks_component_developer.dox'''[[BR]]
 > General comment: the document should stay at the very beginning that it
 is intended for BIND10 developers, not for user-side hook developers.
 The "Introduction" section has been altered to emphasise this.

 > (I imagine that such a document will be created later). It would be good
 to add a stub now to show that such a document is planned. This also
 applies to main page (doc/devel/mainpage.dox).
 It's already been written and the first draft is awaiting review (#2982).
 (It's at first draft stage because some details - such as how to configure
 the which libraries are to be loaded - have yet to be written.)


 > "The hook points need to be defined" => " and registered."
 Done.

 > "At each hook point, the component needs to:" => "At each hook point,
 the component needs to complete the following steps to execute callouts
 registered by user-library:"
 Done.

 > Please use numbered list - it would be easier to reference to specific
 steps.
 Done.


 > Defining the Hook Points section
 > It should be clarified that hook registration and callout registration
 are two completely separate things. When I just started implementing first
 hooks, I confused them couple of times.
 This bit has been rewritten.  As this is a guide for component developers,
 callout registration is not really an issue (and where it is, they are
 pointed to the user-development guide which describes it in more detail).

 > The first example code in Defining the Hook Points has extra colon after
 #include <hooks/hooks_manager.h>.
 Do you mean the one below the #include?  That's supposed to indicate
 omitted code.

 > Yes. We need to solve that problem somehow anyway to call the proper
 version(), load() and unload() functions.
 I don't know whether that is possible because dlsym() gives no way  to
 restrict the scope of the search - it's through all symbols in the loaded
 library or in libraries referenced by that library.  As far as I know,
 version, load and unload are not functions in any Unix library, although
 we have the flexibility to rename them before release. (I hesitate to
 suggest adding a prefix like b10hooks_ until a determination has been made
 about the rebranding of BIND 10.)

 > The text should make clear that the !CalloutHandle is passed to all
 callouts installed on a given hook point. Callouts are executed in order
 (explain the order or point out to a text that explains the order) and
 mention that any argument value updated by callout1 will be seen by
 callout2.
 I've added a note about multiple callouts, but that text belongs more in
 the hooks user guide (which is the guide concerned with writing callouts).

 > The example should not have "..." instead of the call the callouts.
 Also, the comment ("call the hook code...") is incorrect. It should be
 something like "execute installed callouts".
 I think's I've addressed that.


 > '''server_hooks.h'''
 > Please fix a typo in !ServerHooks() ctor: ggetServerHooks() =>
 getServerHooks().
 Fixed.

 > I see that the !HookRegistrationFunction class disappeared. Was it moved
 somewhere or just removed? I'm ok with that if it is gone completely.
 The class was there when the !ServerHooks was not a singleton and had to
 be explicitly created by the server.  It allowed the creation of a list of
 registration functions that the server could execute once the !ServerHooks
 had been created.  With !ServerHooks now a singleton, there is no reason
 for a registration function not to access it directly.  As hooks can be
 registered on a per-module basis at startup (using the technique described
 in the server developer's guide), there was no need for this.

 > '''callout_handle.h'''
 > The comment for !CalloutHandle (the last bullet, starting with "Per-
 library handle" suggests that !CalloutHandle allows user libraries to
 register or deregister callouts. That's not the case. I think the whole
 paragraph can be safely removed.
 The paragraph referred to the !LibraryHandle accessed through the
 !CalloutHandle, which does allow such modifications.  I've updated the
 text to make it clearer.


 > '''callout_handle.cc'''
 > Comment in ~!CalloutHandle: typo in "lm_collection_, wn action".
 Fixed.

 > '''callout_handle_unittest.cc'''
 > There are no tests for getHookName().
 They are in the handles_unittest.cc file.

 == Replying to comment:5 ==

 > '''callout_manager.h'''
 > I would add a single sentence somewhere in the beginning of the class
 description that this class is part of server implementation and user
 library developers should not
 Done.

 > The comment for setNumLibraries() says that the initial value is only
 estimate...
 The initial value is set when the number of libraries is known, and the
 updated value set when the number of libraries that were loaded
 successfully is determined.  However, this method will be removed. While
 doing the work for #2981, I realised that after setting the list of
 libraries in bindctl, the change to the configuration has to be validated
 before it is committed.  As it makes sense for the validation to be "all
 or nothing" (i.e the configuration is only valid if all libraries can be
 located, opened and are at the correct version), there is no use-case for
 allowing library loading to continue if some fail to load.

 For this reason, I'd like to leave it as the code will be altered in
 #2981.


 > There's extra space between std::vector<!CalloutVector> and
 hook_vector_;
 Removed.


 > '''callout_manager.cc'''
 > checkLibraryIndex()
 > I would prefer to not have the else clause, just plain isc_throw(). That
 way the code is simpler to read...
 I've removed the "else" because in this case both the "if" and "else"
 clauses return from the method - there is no way to get out of the
 "if/else" block (which seems wrong somehow).  I would have preferred to
 have code along the lines of:
 {{{
 if (condition not satisifed) {
    throw
 }
 return
 }}}
 ... having the end of the function being the return point, but negating
 the condition makes the logic a lot less clear.


 > callCallouts()
 > I'm somewhat against doing the calloutsPresent() check. This should be
 done far earlier in the server code when the hook point is reached...
 It's up to the server author if they choose to do a calloutsPresent()
 check before they prepare the arguments.  We can't enforce it. In this
 particular case, the reason for the call is to check that hook_index is
 within range (which forms the bulk of the code in calloutsPresent()). I
 could separate that check out into a separate method (it's the same for
 both calloutsPresent() and callCallouts()) but the overhead of calling
 empty() on a vector seemed sufficiently small for it not to be worth it.

 > I think it would be better to not call calloutsPresent() at all here.
 Alternatively, if you really want to, you can call it, but then throw
 exception if someone used callCallouts() without checking
 calloutsPresent() first. In a way that would enforce proper use of
 calloutsPresent() in the server code.
 It's feasible that a server may call callCallouts() without calling
 calloutsPresent() first. For example, to gather statistics of number of
 packets received, the code could contain a hook call without arguments -
 calling the hook indicates "packet received".  Or a server might be
 structured such that each callout takes the same argument. It could put a
 pointer to a received packet in a !CalloutHandle argument list as the
 first step of processing: there is no point in cluttering up the server
 code with calls to calloutsPresent() as callCallouts() is a no-op with no
 callouts registered.

 > I'd like to repeat my previous comment about unnecessary copy of
 hook_vector_[hook_index] to callouts. That's repeated for every hook point
 for every packet, just to be safe against (unlikely) case of a library
 installing callout on a hook point during callout execution on the same
 hook point. The simpler way would be to forbid installing callouts on a
 hook point while the callouts for that hook point are processed.
 It's not just installing callouts, it's removing them as well.  It's quite
 possible that a callout may want to remove itself - it's just how flexible
 we want the system to be. As I said before, the overhead is only incurred
 if any callouts are actually called and will be be small compared with the
 overhead added by the callout. Let's leave it in and measure the overhead.

 > '''tests/common_test_class.h'''
 > I can't find Doxygen documentation for that class. The tags are in the
 file, but it doesn't appear on the Doxygen classes (or files) list.
 That's a Doxygen configuration issue - no test classes are included in the
 Doxygen documentation.

 == Replying to comment:6 ==

 > '''library_manager.h'''
 > !LibraryManager? class comment: "On unload, it calls the "unload" method
 if present, clears the callouts.
 Comment corrected.

 > "Should this happens" => "Should this happen'
 Fixed.

 > I would use stronger language in checkVersion() and point out that the
 version() method is really mandatory.
 Done.

 > I would like to see registerStandardCallouts() returing bool if any
 callouts were registered. Right now it is possible to make an empty
 library that has version() function and nothing else in it. We should at
 least print out a warning about that (or more likely an error). That's the
 only way we can catch spelling errors in user library function names, so
 it is rather important.
 What it won't catch (which is more likely) is a case where there are
 several standard callouts with one misspelled; the misspelled one won't be
 registered. I had thought of returning an "int" indicating the number of
 callouts successfully registered but when would you print it?  The only
 possibility is as a debug message, in which case the names of the callouts
 that are loaded (which is already printed) is of more use.


 > '''library_manager.cc'''
 > I would like to see LOAD_FUNCTION_NAME, UNLOAD_FUNCTION_NAME and
 VERSION_FUNCTION_NAME moved to a header. A header is a natural place as it
 will be used by a user library developer.
 Moved to the general "hooks.h" file.  Also moved there are typdefs for
 pointers to the functions.

 > checkVersion() method: It would be good to check that
 version_function_ptr and void* are of the same size.
 Whether they are or not doesn't matter - it is up to the machine and
 compiler.  There is an issue between converting from "void*" (returned by
 dlsym()) and a pointer to a function, which is documented in the reference
 in the code.  The suggested way round this is the union, which is why it
 was done.  The zeroing of both elements of the union as a way of ensuring
 that everything is set to 0 seemed faster than a memset() call (although
 this has now been changed as a result of a later comment).

 > You may also consider wrapping call to user libraries() with try ...
 catch.
 I presume you mean the framework functions (it's already done for the
 callouts).  Good catch - try/catch clause added.

 > I think HOOKS_REGISTER_STD_CALLOUT should print out function pointer. It
 doesn't give much, but that will at least give some pointers if we get
 bizarre segfaults.
 Good idea, done.

 > Question about checkVersion(), runLoad() and runUnload(). How did you
 solve the problem of dlsym() returning a pointer to a function not from
 the library? E.g. you load library1 then when you call dlsym("version")
 for library2 you can get version() from library1 (which is already
 loaded). I don't see any code for that. I admit that I don't have any
 experience with dynamically loaded libraries.
 It appears that when looking up a symbol in a library, it only looks for
 the symbol in the library being loaded and the libraries linked against
 it, not in previously loaded libraries.  I explicitly test for this in the
 !LoadMultipleLibraries test; the
 test loads a library with all the framework functions (which return
 success), then loads a library with no framework functions. If dlsym()
 searches currently loaded libraries, it should pick up the symbols from
 the first library.  The test checks that the second library fails to load,
 indicating that dlsym() didn't find the framework functions in it.

 In general, there appears to be limitations on what the basic dlsym() can
 do.  Some operating system provide more control with non-standard flags.

 > Return codes for load() and unload() should be defined in a header
 somewhere. It may be just one constant for 0 with the explanation that
 non-zero status codes are errors and are library dependent.
 The documentation (like most Unix documentation) states that 0 is a
 success status, non-zero is an error indication. As we would only be
 adding a single symbolic constant, this just adds complication for no real
 gain.

 > The code for union of pointers and dlsym() calls are repeated in 4
 places. Would it be possible to refactor them into a separate method? It's
 an honest question.
 Good suggestion.  I've created a simple !PointerConverter class to
 encapsulate the union and handle the initialization.  Member methods
 return the appropriate type of pointer.


 > loadLibrary(): This is a bit confusing. If version check fails, you run
 closeLibrary(). If runLoad() fails, you don't. I think in the first "else"
 there should be unloadLibrary() and closeLibrary().
 Well spotted.  The closeLibrary() call should not have been within the
 "else" block of the "checkVersion()" conditional statement.


 > unloadLibrary(): I think there's a bug in the code. closeLibrary() will
 be called only if runUnload() succeeded. Is that what you wanted? I think
 that we should call closeLibrary() every time, so it should be:
 > result = closeLibrary() && result;

 Good catch!  Corrected.

 > '''tests/test_libraries.h.in'''
 > "Take carse of" => "Take care of"
 Fixed.

 > '''tests/Makefile.am'''
 > There are unnecessary spaces in lines 32 and 37.
 Removed.

 > '''tests/library_manager_unittest.cc'''
 > What are the r and d parameters of executeCallCallouts() mean? I presume
 it is a short > for data and result, right?
 Yes - I've added a short explanation.

 > This is used in several tests. I understand that the callouts perform
 simple mathematical operations, but a bit of explanation would be handy.
 Also, that's a tricky approach as with all results can be achieved with
 different combinations of addition and multiplication.
 The explanation can be found in common_test_class.h.


 > library_handle.h
 > The comment about manager parameter passed to constructor being a raw
 pointer being safe. Although this may be true, there's not guarantee that
 !LibraryHandle will not be created somewhere else. You would get that
 guarantee if constructor was protected and !CalloutManager was defined a
 friend. This may possibly complicate some tests (which violate that
 guarantee).
 I'd like to leave that for a moment as it does add more complication.  It
 is possible for !LibraryHandle to be created elsewhere but the creator
 would need to supply a suitable !CalloutManager.  However, to reduce the
 risk of a dangling pointer, I've made the !LibraryHandle copy constructor
 and assignment operator private.  User-code  can only get a reference to
 an existing object, they can't take a copy of it.

 > '''hooks/'hook_messages.mes'''
 > The different between HOOKS_CALLOUT_REMOVED and HOOKS_DEREGISTER_CALLOUT
 are not clear (I understand the difference, but it not clearly explained).
 Two comments about log message naming. Firstly, some hooks have noun_verb
 names, which others have verb_nound. Secondly, some are in present and
 some in past tense. I don't have any preference, but I think it should be
 consistent.
 Improved the explanation and updated the message symbols to be noun_verb.
 This has meant that the messages are not in alphabetic order; I've left it
 like that for ease of review and will reorder them before merging.

 > tests/basic_callout_library.cc
 > Please update the naming to reflect what the callouts are for. lm_one is
 a wrong name. It is not library manager, it is a callout. Also, it would
 be great if the name reflected what the callout actually does, so
 callout_add seems like a good name.
 They are only tests and their functionality is incidental - it is only
 something to prove that the callout worked.  However, I've renamed the
 hook points "hookpt_one", "hookpt_two" and "hookpt_three".

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


More information about the bind10-tickets mailing list