callout_manager.cc 11.5 KB
Newer Older
1
// Copyright (C) 2013,2015  Internet Systems Consortium, Inc. ("ISC")
Stephen Morris's avatar
Stephen Morris committed
2 3 4 5 6 7 8 9 10 11 12 13 14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15 16
#include <config.h>

17 18
#include <hooks/callout_handle.h>
#include <hooks/callout_manager.h>
19
#include <hooks/hooks_log.h>
20
#include <hooks/pointer_converter.h>
21
#include <util/stopwatch.h>
Stephen Morris's avatar
Stephen Morris committed
22

23 24
#include <boost/static_assert.hpp>

Stephen Morris's avatar
Stephen Morris committed
25
#include <algorithm>
26
#include <climits>
Stephen Morris's avatar
Stephen Morris committed
27 28 29 30 31 32
#include <functional>
#include <utility>

using namespace std;

namespace isc {
33
namespace hooks {
Stephen Morris's avatar
Stephen Morris committed
34

35 36
// Constructor
CalloutManager::CalloutManager(int num_libraries)
37 38
    : server_hooks_(ServerHooks::getServerHooks()),
      current_hook_(-1), current_library_(-1),
39 40 41 42 43 44 45 46 47 48
      hook_vector_(ServerHooks::getServerHooks().getCount()),
      library_handle_(this), pre_library_handle_(this, 0),
      post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
{
    if (num_libraries < 0) {
        isc_throw(isc::BadValue, "number of libraries passed to the "
                  "CalloutManager must be >= 0");
    }
}

49
// Check that the index of a library is valid.  It can range from 1 - n
Stephen Morris's avatar
Stephen Morris committed
50 51 52
// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
// (post-user library callouts).  It can also be -1 to indicate an invalid
// value.
53 54 55 56 57 58 59

void
CalloutManager::checkLibraryIndex(int library_index) const {
    if (((library_index >= -1) && (library_index <= num_libraries_)) ||
        (library_index == INT_MAX)) {
        return;
    }
60 61 62 63

    isc_throw(NoSuchLibrary, "library index " << library_index <<
              " is not valid for the number of loaded libraries (" <<
              num_libraries_ << ")");
64 65
}

66
// Register a callout for the current library.
Stephen Morris's avatar
Stephen Morris committed
67 68

void
69
CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
Stephen Morris's avatar
Stephen Morris committed
70
    // Note the registration.
71
    LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
Stephen Morris's avatar
Stephen Morris committed
72 73
        .arg(current_library_).arg(name);

74 75 76
    // Sanity check that the current library index is set to a valid value.
    checkLibraryIndex(current_library_);

Stephen Morris's avatar
Stephen Morris committed
77 78
    // Get the index associated with this hook (validating the name in the
    // process).
79
    int hook_index = server_hooks_.getIndex(name);
Stephen Morris's avatar
Stephen Morris committed
80 81 82 83 84 85

    // Iterate through the callout vector for the hook from start to end,
    // looking for the first entry where the library index is greater than
    // the present index.
    for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
         i != hook_vector_[hook_index].end(); ++i) {
86
        if (i->first > current_library_) {
87 88
            // Found an element whose library index number is greater than the
            // current index, so insert the new element ahead of this one.
89 90
            hook_vector_[hook_index].insert(i, make_pair(current_library_,
                                                         callout));
Stephen Morris's avatar
Stephen Morris committed
91 92 93 94
            return;
        }
    }

95 96 97
    // Reached the end of the vector, so there is no element in the (possibly
    // empty) set of callouts with a library index greater than the current
    // library index.  Inset the callout at the end of the list.
98
    hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
Stephen Morris's avatar
Stephen Morris committed
99 100 101 102 103 104 105
}

// Check if callouts are present for a given hook index.

bool
CalloutManager::calloutsPresent(int hook_index) const {
    // Validate the hook index.
106 107 108 109
    if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
        isc_throw(NoSuchHook, "hook index " << hook_index <<
                  " is not valid for the list of registered hooks");
    }
Stephen Morris's avatar
Stephen Morris committed
110 111 112 113 114 115 116

    // Valid, so are there any callouts associated with that hook?
    return (!hook_vector_[hook_index].empty());
}

// Call all the callouts for a given hook.

117
void
Stephen Morris's avatar
Stephen Morris committed
118
CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
119

120 121 122 123 124
    // Clear the "skip" flag so we don't carry state from a previous call.
    // This is done regardless of whether callouts are present to avoid passing
    // any state from the previous call of callCallouts().
    callout_handle.setSkip(false);

125 126 127 128
    // Only initialize and iterate if there are callouts present.  This check
    // also catches the case of an invalid index.
    if (calloutsPresent(hook_index)) {

Stephen Morris's avatar
Stephen Morris committed
129 130 131 132
        // Set the current hook index.  This is used should a callout wish to
        // determine to what hook it is attached.
        current_hook_ = hook_index;

133 134 135 136 137 138 139
        // Duplicate the callout vector for this hook and work through that.
        // This step is needed because we allow dynamic registration and
        // deregistration of callouts.  If a callout attached to a hook modified
        // the list of callouts on that hook, the underlying CalloutVector would
        // change and potentially affect the iteration through that vector.
        CalloutVector callouts(hook_vector_[hook_index]);

140 141
        // This object will be used to measure execution time of each callout
        // and the total time spent in callouts for this hook point.
142 143
        util::Stopwatch stopwatch;

144
        // Mark that the callouts begin for the hook.
145
        LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
146 147
            .arg(server_hooks_.getName(current_hook_));

148 149 150 151 152 153 154 155 156
        // Call all the callouts.
        for (CalloutVector::const_iterator i = callouts.begin();
             i != callouts.end(); ++i) {
            // In case the callout tries to register or deregister a callout,
            // set the current library index to the index associated with the
            // library that registered the callout being called.
            current_library_ = i->first;

            // Call the callout
Stephen Morris's avatar
Stephen Morris committed
157
            try {
158
                stopwatch.start();
Stephen Morris's avatar
Stephen Morris committed
159
                int status = (*i->second)(callout_handle);
160
                stopwatch.stop();
Stephen Morris's avatar
Stephen Morris committed
161
                if (status == 0) {
162
                    LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
163
                              HOOKS_CALLOUT_CALLED).arg(current_library_)
164
                        .arg(server_hooks_.getName(current_hook_))
165 166
                        .arg(PointerConverter(i->second).dlsymPtr())
                        .arg(stopwatch.logFormatLastDuration());
Stephen Morris's avatar
Stephen Morris committed
167
                } else {
168
                    LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
Stephen Morris's avatar
Stephen Morris committed
169
                        .arg(current_library_)
170
                        .arg(server_hooks_.getName(current_hook_))
171 172
                        .arg(PointerConverter(i->second).dlsymPtr())
                        .arg(stopwatch.logFormatLastDuration());
Stephen Morris's avatar
Stephen Morris committed
173
                }
174
            } catch (const std::exception& e) {
175 176 177
                // If an exception occurred, the stopwatch.stop() hasn't been
                // called, so we have to call it here.
                stopwatch.stop();
Stephen Morris's avatar
Stephen Morris committed
178
                // Any exception, not just ones based on isc::Exception
179
                LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
180
                    .arg(current_library_)
181
                    .arg(server_hooks_.getName(current_hook_))
182
                    .arg(PointerConverter(i->second).dlsymPtr())
183 184
                    .arg(e.what())
                    .arg(stopwatch.logFormatLastDuration());
185 186
            }

187
        }
188

189 190
        // Mark end of callout execution. Include the total execution
        // time for callouts.
191
        LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
192 193 194
            .arg(server_hooks_.getName(current_hook_))
            .arg(stopwatch.logFormatTotalDuration());

Stephen Morris's avatar
Stephen Morris committed
195 196 197
        // Reset the current hook and library indexs to an invalid value to
        // catch any programming errors.
        current_hook_ = -1;
198 199
        current_library_ = -1;
    }
Stephen Morris's avatar
Stephen Morris committed
200 201
}

202
// Deregister a callout registered by the current library on a particular hook.
Stephen Morris's avatar
Stephen Morris committed
203 204

bool
205 206 207
CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
    // Sanity check that the current library index is set to a valid value.
    checkLibraryIndex(current_library_);
Stephen Morris's avatar
Stephen Morris committed
208 209 210

    // Get the index associated with this hook (validating the name in the
    // process).
211
    int hook_index = server_hooks_.getIndex(name);
Stephen Morris's avatar
Stephen Morris committed
212

213
    /// Construct a CalloutEntry matching the current library and the callout
Stephen Morris's avatar
Stephen Morris committed
214
    /// we want to remove.
215
    CalloutEntry target(current_library_, callout);
Stephen Morris's avatar
Stephen Morris committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237

    /// To decide if any entries were removed, we'll record the initial size
    /// of the callout vector for the hook, and compare it with the size after
    /// the removal.
    size_t initial_size = hook_vector_[hook_index].size();

    // The next bit is standard STL (see "Item 33" in "Effective STL" by
    // Scott Meyers).
    //
    // remove_if reorders the hook vector so that all items not matching
    // the predicate are at the start of the vector and returns a pointer
    // to the next element. (In this case, the predicate is that the item
    // is equal to the value of the passed callout.)  The erase() call
    // removes everything from that element to the end of the vector, i.e.
    // all the matching elements.
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
                                             hook_vector_[hook_index].end(),
                                             bind1st(equal_to<CalloutEntry>(),
                                                     target)),
                                   hook_vector_[hook_index].end());

    // Return an indication of whether anything was removed.
238 239
    bool removed = initial_size != hook_vector_[hook_index].size();
    if (removed) {
240
        LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
241
                  HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
242 243 244
    }

    return (removed);
Stephen Morris's avatar
Stephen Morris committed
245 246 247 248 249
}

// Deregister all callouts on a given hook.

bool
250
CalloutManager::deregisterAllCallouts(const std::string& name) {
Stephen Morris's avatar
Stephen Morris committed
251 252 253

    // Get the index associated with this hook (validating the name in the
    // process).
254
    int hook_index = server_hooks_.getIndex(name);
Stephen Morris's avatar
Stephen Morris committed
255

256 257 258
    /// Construct a CalloutEntry matching the current library (the callout
    /// pointer is NULL as we are not checking that).
    CalloutEntry target(current_library_, NULL);
Stephen Morris's avatar
Stephen Morris committed
259 260 261 262 263

    /// To decide if any entries were removed, we'll record the initial size
    /// of the callout vector for the hook, and compare it with the size after
    /// the removal.
    size_t initial_size = hook_vector_[hook_index].size();
264

Stephen Morris's avatar
Stephen Morris committed
265 266 267 268 269 270 271
    // Remove all callouts matching this library.
    hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
                                             hook_vector_[hook_index].end(),
                                             bind1st(CalloutLibraryEqual(),
                                                     target)),
                                   hook_vector_[hook_index].end());

272
    // Return an indication of whether anything was removed.
273 274
    bool removed = initial_size != hook_vector_[hook_index].size();
    if (removed) {
275
        LOG_DEBUG(callouts_logger, HOOKS_DBG_EXTENDED_CALLS,
276
                  HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
277 278 279 280
                                                .arg(name);
    }

    return (removed);
Stephen Morris's avatar
Stephen Morris committed
281 282 283 284
}

} // namespace util
} // namespace isc