diff options
Diffstat (limited to 'src/lib/hooks/parking_lots.h')
-rw-r--r-- | src/lib/hooks/parking_lots.h | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/src/lib/hooks/parking_lots.h b/src/lib/hooks/parking_lots.h new file mode 100644 index 0000000..e4e82bd --- /dev/null +++ b/src/lib/hooks/parking_lots.h @@ -0,0 +1,426 @@ +// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PARKING_LOTS_H +#define PARKING_LOTS_H + +#include <exceptions/exceptions.h> +#include <boost/any.hpp> +#include <boost/make_shared.hpp> +#include <boost/shared_ptr.hpp> + +#include <functional> +#include <iostream> +#include <sstream> +#include <list> +#include <unordered_map> +#include <mutex> +#include <thread> + +namespace isc { +namespace hooks { + +/// @brief Parking lot for objects, e.g. packets, for a hook point. +/// +/// Callouts may instruct the servers to "park" processed packets, i.e. suspend +/// their processing until explicitly unparked. This is useful in cases when +/// callouts need to perform asynchronous operations related to the packet +/// processing and the packet must not be further processed until the +/// asynchronous operations are completed. While the packet is parked, the +/// new packets can be processed, so the server remains responsive to the +/// new requests. +/// +/// Parking lots are created per hook point, so the callouts installed on the +/// particular hook point only have access to the parking lots dedicated to +/// them. +/// +/// The parking lot object supports 5 actions: "park", "reference", +/// "dereference", "unpark", and "drop". +/// +/// In the typical case, the server parks the object and the callouts reference +/// and unpark the objects. Therefore, the @ref ParkingLot object is not passed +/// directly to the callouts. Instead, a ParkingLotHandle object is provided +/// to the callout, which only provides access to "reference", "dereference", +/// and "unpark" operations. +/// +/// Parking an object is performed, proactively by the server, before callouts +/// are invoked. Referencing (and dereferencing) an object is performed by the +/// callouts before the @c CalloutHandle::NEXT_STEP_PARK is returned to the +/// server. +/// +/// Trying to reference (or deference) and unparked object will result +/// in error. Referencing (reference counting) is an important part of the +/// parking mechanism, which allows multiple callouts, installed on the same +/// hook point, to perform asynchronous operations and guarantees that the +/// object remains parked until all those asynchronous operations complete. +/// Each such callout must call @c unpark() when it desires the object to +/// be unparked, but the object will only be unparked when all callouts call +/// this function, i.e. when all callouts signal completion of their respective +/// asynchronous operations. +/// +/// Dereferencing, decrements the reference count without invoking the unpark +/// callback. This allows hook callouts to proactively reference the object +/// in a callout and then cancel the reference should further processing +/// deem it the reference unnecessary. +/// +/// The types of the parked objects provided as T parameter of respective +/// functions are most often shared pointers. One should not use references +/// to parked objects nor references to shared pointers to avoid premature +/// destruction of the parked objects. +class ParkingLot { +public: + /// @brief Parks an object. + /// + /// @tparam Type of the parked object. + /// @param parked_object object to be parked, e.g. pointer to a packet. + /// @param unpark_callback callback function to be invoked when the object + /// is unparked. + /// @throw InvalidOperation if this object has already been parked. + template<typename T> + void park(T parked_object, std::function<void()> unpark_callback) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it != parking_.end()) { + isc_throw(InvalidOperation, "object is already parked!"); + } + + // Add the object to the parking lot. At this point refcount = 0. + ParkingInfo pinfo(parked_object, unpark_callback); + parking_[makeKey(parked_object)] = pinfo; + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It may only be called after the object + /// has been parked + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return the integer number of references for this object. + template<typename T> + int reference(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + isc_throw(InvalidOperation, "cannot reference an object" + " that has not been parked."); + } + + // Bump and return the reference count + return (++it->second.refcount_); + } + + /// @brief Decreases the reference counter for the parked object. + /// + /// This method is called by the callouts to decrease the reference count + /// on a parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object whose count should be reduced. + /// @return the integer number of references for this object. + template<typename T> + int dereference(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + isc_throw(InvalidOperation, "cannot dereference an object" + " that has not been parked."); + } + + // Decrement and return the reference count. + return (--it->second.refcount_); + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be unparked. + /// @param force boolean value indicating if the reference counting should + /// be ignored and the object should be unparked immediately. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template<typename T> + bool unpark(T parked_object, bool force = false) { + // Initialize as the empty function. + std::function<void()> cb; + { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it == parking_.end()) { + // No such parked object. + return (false); + } + + if (force) { + it->second.refcount_ = 0; + } else { + --it->second.refcount_; + } + + if (it->second.refcount_ <= 0) { + // Unpark the packet and set the callback. + cb = it->second.unpark_callback_; + parking_.erase(it); + } + } + + // Invoke the callback if not empty. + if (cb) { + cb(); + } + + // Parked object found, so return true to indicate that the + // operation was successful. It doesn't necessarily mean + // that the object was unparked, but at least the reference + // count was decreased. + return (true); + } + + /// @brief Removes parked object without calling a callback. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be removed. + /// @return false if the object couldn't be removed because there is + /// no such object, true otherwise. + template<typename T> + bool drop(T parked_object) { + std::lock_guard<std::mutex> lock(mutex_); + auto it = find(parked_object); + if (it != parking_.end()) { + // Parked object found. + parking_.erase(it); + return (true); + } + + // No such object. + return (false); + } + + /// @brief Returns the current number of objects. + size_t size() { + std::lock_guard<std::mutex> lock(mutex_); + return (parking_.size()); + } + +public: + + /// @brief Holds information about parked object. + struct ParkingInfo { + /// @brief The parked object. + boost::any parked_object_; + + /// @brief The pointer to callback. + std::function<void()> unpark_callback_; + + /// @brief The current reference count. + int refcount_; + + /// @brief Constructor. + /// + /// Default constructor. + ParkingInfo() : refcount_(0) {} + + /// @brief Constructor. + /// + /// @param parked_object object being parked. + /// @param callback pointer to the callback. + ParkingInfo(const boost::any& parked_object, + std::function<void()> callback = 0) + : parked_object_(parked_object), unpark_callback_(callback), + refcount_(0) {} + + /// @brief Update parking information. + /// + /// @param parked_object parked object. + /// @param callback pointer to the callback. + void update(const boost::any& parked_object, + std::function<void()> callback) { + parked_object_ = parked_object; + unpark_callback_ = callback; + } + }; + +private: + + /// @brief Map which stores parked objects. + typedef std::unordered_map<std::string, ParkingInfo> ParkingInfoList; + + /// @brief Type of the iterator in the list of parked objects. + typedef ParkingInfoList::iterator ParkingInfoListIterator; + + /// @brief Container holding parked objects for this parking lot. + ParkingInfoList parking_; + + /// @brief Construct the key for a given parked object. + /// + /// @tparam T parked object type. + /// @param parked_object object from which the key should be constructed. + /// @return string containing the object's key. + template<typename T> + std::string makeKey(T parked_object) { + std::stringstream ss; + ss << boost::any_cast<T>(parked_object); + return (ss.str()); + } + + /// @brief Search for the information about the parked object. + /// + /// @tparam T parked object type. + /// @param parked_object object for which to search. + /// @return Iterator pointing to the parked object, or @c parking_.end() + /// if no such object found. + template<typename T> + ParkingInfoListIterator find(T parked_object) { + return (parking_.find(makeKey(parked_object))); + } + + /// @brief The mutex to protect parking lot internal state. + /// + /// All public methods must enter of lock guard with the mutex + /// before any access to the @c parking_ member. + std::mutex mutex_; +}; + +/// @brief Type of the pointer to the parking lot. +typedef boost::shared_ptr<ParkingLot> ParkingLotPtr; + +/// @brief Provides a limited view to the @c ParkingLot. +/// +/// The handle is provided to the callouts which can reference and unpark +/// parked objects. The callouts should not park objects, therefore this +/// operation is not available. +/// +/// The types of the parked objects provided as T parameter of respective +/// functions are most often shared pointers. One should not use references +/// to parked objects nor references to shared pointers to avoid premature +/// destruction of the parked objects. +class ParkingLotHandle { +public: + + /// @brief Constructor. + /// + /// @param parking_lot pointer to the parking lot for which the handle is + /// created. + ParkingLotHandle(const ParkingLotPtr& parking_lot) + : parking_lot_(parking_lot) { + } + + /// @brief Increases reference counter for the parked object. + /// + /// This method is called by the callouts to increase a reference count + /// on the object to be parked. It must be called before the object is + /// actually parked. + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return new reference count as an integer + template<typename T> + int reference(T parked_object) { + return (parking_lot_->reference(parked_object)); + } + + /// @brief Decreases the reference counter for the parked object. + /// + /// This method is called by the callouts to decrease the reference count + /// of a parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object object which will be parked. + /// @return new reference count as an integer + template<typename T> + int dereference(T parked_object) { + return (parking_lot_->dereference(parked_object)); + } + + /// @brief Signals that the object should be unparked. + /// + /// If the specified object is parked in this parking lot, the reference + /// count is decreased as a result of this method. If the reference count + /// is 0, the object is unparked and the callback is invoked. Typically, the + /// callback points to a function which resumes processing of a packet. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be unparked. + /// @return false if the object couldn't be unparked because there is + /// no such object, true otherwise. + template<typename T> + bool unpark(T parked_object) { + return (parking_lot_->unpark(parked_object)); + } + + /// @brief Removes parked object without calling a callback. + /// + /// It ignores any reference counts on the parked object. + /// + /// @tparam Type of the parked object. + /// @param parked_object parked object to be removed. + /// @return false if the object couldn't be removed because there is + /// no such object, true otherwise. + template<typename T> + bool drop(T parked_object) { + return (parking_lot_->drop(parked_object)); + } + +private: + + /// @brief Parking lot to which this handle points. + ParkingLotPtr parking_lot_; + +}; + +/// @brief Pointer to the parking lot handle. +typedef boost::shared_ptr<ParkingLotHandle> ParkingLotHandlePtr; + +/// @brief Collection of parking lots for various hook points. +class ParkingLots { +public: + + /// @brief Removes all parked objects. + /// + /// It doesn't invoke callbacks associated with the removed objects. + void clear() { + std::lock_guard<std::mutex> lock(mutex_); + parking_lots_.clear(); + } + + /// @brief Returns pointer to the parking lot for a hook points. + /// + /// If the parking lot for the specified hook point doesn't exist, it is + /// created. + /// + /// @param hook_index index of the hook point with which the parking + /// lot is associated. + /// @return Pointer to the parking lot. + ParkingLotPtr getParkingLotPtr(const int hook_index) { + std::lock_guard<std::mutex> lock(mutex_); + if (parking_lots_.count(hook_index) == 0) { + parking_lots_[hook_index] = boost::make_shared<ParkingLot>(); + } + return (parking_lots_[hook_index]); + } + +private: + + /// @brief Container holding parking lots for various hook points. + std::unordered_map<int, ParkingLotPtr> parking_lots_; + + /// @brief The mutex to protect parking lots internal state. + std::mutex mutex_; +}; + +/// @brief Type of the pointer to the parking lots. +typedef boost::shared_ptr<ParkingLots> ParkingLotsPtr; + +} // end of namespace hooks +} // end of namespace isc + +#endif |