io_service_signal.h 9.95 KB
Newer Older
1
// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
2
//
3 4 5
// 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/.
6 7 8 9

#ifndef IO_SERVICE_SIGNAL_H
#define IO_SERVICE_SIGNAL_H

10 11
#include <asiolink/io_service.h>
#include <asiolink/interval_timer.h>
12 13 14
#include <exceptions/exceptions.h>

#include <map>
15
#include <stdint.h>
16 17

namespace isc {
18
namespace process {
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

/// @brief Exception thrown if IOSignal encounters an error.
class IOSignalError : public isc::Exception {
public:
    IOSignalError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) { };
};

/// @brief Defines a unique identifier type for IOSignal.
typedef uint64_t IOSignalId;

/// @brief Defines a handler function for an IOSignal.
/// IOSignalHandlers should contain the application level logic that would
/// ordinarily be an OS signal handler.
typedef boost::function<void(IOSignalId sequence_id)> IOSignalHandler;

/// @brief Implements an asynchronous "signal" for IOService driven processing
///
/// This class allows a OS signal such as SIGHUP to propagated to an IOService
/// as a ready event with a callback. While boost::asio provides a signal class,
/// it requires linking in additional boost libraries that as of yet we do not
/// need. Therefore, this class was implemented to allow IOService-based
/// processes to handle signals as IOService events.
///
/// The mechanics of IOSignal are straight forward. Upon construction it is
/// given the target IOService, the value of the signal to send (i.e. SIGINT,
/// SIGHUP...), and an IOSignalHandler.  The IOSignalHandler should contain
/// the logic the caller would normally execute in its OS signal handler. Each
/// IOSignal instance has a unique identifier called its sequence_id.
///
/// Internally, IOSignal creates a 1 ms, one-shot timer, on the given
/// IOService.  When the timer expires its event handler invokes the caller's
/// IOSignalHandler passing it the sequence_id of the IOSignal.
///
/// Sending IOSignals is done through an IOSignalQueue.  This class is used to
/// create the signals, house them until they are delivered, and dequeue them
/// so they can be been handled.  To generate an IOSignal when an OS signal
/// arrives, the process's OS signal handler simply calls @ref
57
/// isc::process::IOSignalQueue::pushSignal() with the appropriate values.
58
///
59
/// @note that an IOSignalQueue requires a non-null IOServicePtr to construct.
60 61 62 63 64 65
/// This ensures that the IOService cannot be destroyed before any pending
/// signals can be canceled.  It also means that a queue can only be used to
/// send signals to that IOService.  If you need to send signals to more than
/// one service, each service must have its own queue.
///
/// To dequeue the IOSignal inside the caller's IOSignalHandler, one simply
66 67 68 69 70 71 72
/// invokes @ref isc::process::IOSignalQueue::popSignal() passing it the
/// sequence_id parameter passed to the handler.  This method returns a
/// pointer to instigating IOSignal from which the value of OS signal (i.e.
/// SIGINT, SIGUSR1...) can be obtained.  Note that calling popSignal()
/// removes the IOSignalPtr from the queue, which should reduce its
/// reference count to zero upon exiting the handler (unless a delibrate
/// copy of it is made).
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
///
/// A typical IOSignalHandler might be structured as follows:
/// @code
///
///    void processSignal(IOSignalId sequence_id) {
///        // Pop the signal instance off the queue.
///        IOSignalPtr signal = io_signal_queue_->popSignal(sequence_id);
///
///        int os_signal_value = signal->getSignum();
///        :
///        // logic based on the signal value
///        :
///     }
///
/// @endcode
///
/// IOSignal handler invocation code will catch, log ,and then swallow any
/// exceptions thrown by a IOSignalHandler invocation.  This is done to protect
/// the integrity IOService context.
///
class IOSignal {
public:
    /// @brief Constructor
    ///
    /// @param io_service IOService to which to send the signal
    /// @param signum value of the signal to send
    /// @param handler the handler to run when IOService "receives" the
    /// signal
    ///
    /// @throw IOSignalError if handler is null
    IOSignal(asiolink::IOService& io_service, int signum,
              IOSignalHandler handler);

106
    /// @brief Destructor
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    ~IOSignal();

    /// @brief Static method for generating IOSignal sequence_ids.
    ///
    /// Generates and returns the next IOSignalId. This method is intentionally
    /// static in the event a process is using generating signals to more than
    /// IOService.  It assures that each IOSignal is unique with the process
    /// space.
    ///
    /// @return The next sequential value as an IOSignalId.
    static IOSignalId nextSequenceId() {
        static IOSignalId next_id_ = 0;
        return (++next_id_);
    }

    /// @brief Gets the IOSignal's sequence_id
    ///
    /// @return The sequence_id of the signal.
    IOSignalId getSequenceId() const {
        return (sequence_id_);
    }

    /// @brief Gets the OS signal value this IOSignal represents.
    ///
    /// @return The OS signal value (i.e. SIGINT, SIGUSR1...)
    int getSignum() const {
        return (signum_);
    }

    /// @brief Defines the callback used by IOSignal's internal timer.
    ///
    /// This class stores the sequence_id of the IOSignal being sent and the
    /// IOSignalHandler to invoke when delivering the signal.  The () operator
    /// is called by IOService when the timer expires.  This method invokes
    /// the IOSignalHandler passing it the sequence_id.
    class TimerCallback : public std::unary_function<void, void> {
    public:
        /// @brief Constructor
        ///
        /// @param sequence_id sequence_id of the IOSignal to handle
        /// @param handler pointer to the function to handle the IOSignal
        ///
        /// @throw IOSignalError if handler is null.
150
        TimerCallback(IOSignalId sequence_id, IOSignalHandler handler);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

        /// @brief () Operator which serves as the timer's callback
        ///
        /// It is invoked when the timer expires and calls the handler
        /// passing in the signal.
        void operator()();

    private:
        /// @brief Id of the IOSignal to which the callback pertains.
        IOSignalId sequence_id_;

        /// @brief Pointer to the function to handle the signal
        IOSignalHandler handler_;
    };

private:
    /// @brief Value which uniquely identifies each IOSignal instance.
    IOSignalId sequence_id_;

    /// @brief Numeric value of the signal to send (e.g. SIGINT, SIGUSR1...)
    int signum_;

    /// @brief Timer instance created to propagate the signal.
    asiolink::IntervalTimerPtr timer_;
};

/// @brief Defines a pointer to an IOSignal
typedef boost::shared_ptr<IOSignal> IOSignalPtr;

/// @brief Defines a map of IOSignalPtr keyed by id
typedef std::map<IOSignalId, IOSignalPtr> IOSignalMap;

/// @brief Creates and manages IOSignals
///
/// This class is used to create IOSignals, house them until they are delivered,
/// and dequeue them so they can be been handled.  IOSignals are designed to
/// used once and then destroyed.  They need to be created from within OS
/// signal handlers and persist until they have been delivered and processed.
///
/// This class is designed specifically to make managing them painless.
/// It maintains an internal map of IOSignals keyed by sequence_id. When a
/// signal is created via the pushSignal() method it is added to the map. When
/// a signal is retrevied via the popSignal() method it is removed from the map.
class IOSignalQueue {
public:
    /// @brief Constructor
    ///
    /// @param io_service the IOService to which to send signals.
    /// @throw IOSignalError if io_service is NULL.
200
    IOSignalQueue (asiolink::IOServicePtr& io_service);
201

202
    /// @brief Destructor.
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    ~IOSignalQueue();

    /// @brief Creates an IOSignal
    ///
    /// Given a signal number and a handler, it will instantiate an IOSignal
    /// and add it to the instance map.  (Remember that IOSignals are really
    /// just timers programmed during construction, so once instantiated
    /// there's nothing more required to "send" the signal other than return
    /// control to IOService::run()).
    ///
    /// @param signum OS signal value of the signal to propagate
    /// @param handler IOSignalHandler to invoke when the signal is delivererd.
    ///
    /// @return The sequence_id of the newly created signal.
    ///
    /// @throw IOSignalError if the sequence_id already exists in the map. This
    /// is virtually impossible unless things have gone very wrong.
    IOSignalId pushSignal(int signum, IOSignalHandler handler);

    /// @brief Removes an IOSignal from the map and returns it.
    ///
    /// Given a sequence_id this method will extract the IOSignal from the
    /// internal map and return.  At that point, the caller will hold the
    /// only copy of the IOSignal.
    ///
    /// @param sequence_id  sequence_id of the IOSignal to retrieve.
    ///
    /// @return A smart pointer to the IOSignal.
    ///
    /// @throw IOSignalError if there is no matching IOSignal in the map for
    /// the given sequence_id.  Other than by doubling popping, this should be
    /// very unlikley.
    IOSignalPtr popSignal(IOSignalId sequence_id);

    /// @brief Erases the contents of the queue.
    ///
    /// Any instances still in the map will be destroyed. This will cause their
    /// timers to be cancelled without any callbacks invoked. (Not sure when
    /// this might be desirable).
    void clear();

private:
    /// @brief Pointer to the IOService which will receive the signals.
246
    asiolink::IOServicePtr io_service_;
247 248 249 250 251 252 253 254 255

    /// @brief A map of the IOSignals pushed through this queue.
    IOSignalMap signals_;
};

/// @brief Defines a pointer to an IOSignalQueue.
typedef boost::shared_ptr<IOSignalQueue> IOSignalQueuePtr;


256
}; // end of isc::process namespace
257 258 259
}; // end of isc namespace

#endif // IO_SERVICE_SIGNAL_H