Browse Source

[3405] Implemented Signal set class to monitor signals.

Marcin Siodelski 11 years ago
parent
commit
d4dc6b5a8e

+ 1 - 0
configure.ac

@@ -1562,6 +1562,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/testutils/Makefile
                  src/lib/testutils/testdata/Makefile
                  src/lib/util/io/Makefile
+                 src/lib/util/io/tests/Makefile
                  src/lib/util/Makefile
                  src/lib/util/python/doxygen2pydoc.py
                  src/lib/util/python/gen_wiredata.py

+ 2 - 0
src/lib/util/io/Makefile.am

@@ -1,3 +1,4 @@
+SUBDIRS = . tests
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
@@ -6,6 +7,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 lib_LTLIBRARIES = libkea-util-io.la
 libkea_util_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc
 libkea_util_io_la_SOURCES += socketsession.h socketsession.cc sockaddr_util.h
+libkea_util_io_la_SOURCES += signal_set.cc signal_set.h
 libkea_util_io_la_SOURCES += pktinfo_utilities.h
 libkea_util_io_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 

+ 144 - 0
src/lib/util/io/signal_set.cc

@@ -0,0 +1,144 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// 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.
+
+#include <util/io/signal_set.h>
+
+#include <list>
+
+using namespace isc;
+using namespace isc::util::io;
+
+namespace {
+
+std::list<int>* getSignalStates() {
+    static std::list<int> states;
+    return (&states);
+}
+
+void internalHandler(int sig) {
+    std::list<int>* states = getSignalStates();
+    for (std::list<int>::const_iterator it = states->begin();
+         it != states->end(); ++it) {
+        if (sig == *it) {
+            return;
+        }
+    }
+    states->push_back(sig);
+}
+
+}
+
+namespace isc {
+namespace util {
+namespace io {
+
+SignalSet::SignalSet(const int sig0) {
+    add(sig0);
+}
+
+SignalSet::SignalSet(const int sig0, const int sig1) {
+    add(sig0);
+    add(sig1);
+}
+
+SignalSet::SignalSet(const int sig0, const int sig1, const int sig2) {
+    add(sig0);
+    add(sig1);
+    add(sig2);
+}
+
+void
+SignalSet::add(const int sig) {
+    std::pair<Pool::iterator, bool> ret = registered_signals_.insert(sig);
+    if (!ret.second) {
+        isc_throw(SignalSetError, "attempt to register a duplicate signal "
+                  << sig);
+    }
+    struct sigaction sa;
+    sa.sa_handler = internalHandler;
+    if (sigaction(sig, &sa, 0) < 0) {
+        registered_signals_.erase(sig);
+        isc_throw(SignalSetError, "failed to register a signal handler for"
+                  " signal " << sig << ": " << strerror(errno));
+    }
+}
+
+void
+SignalSet::clear() {
+    Pool all_signals = registered_signals_;
+    for (Pool::const_iterator it = all_signals.begin();
+         it != all_signals.end(); ++it) {
+        remove(*it);
+    }
+}
+
+int
+SignalSet::getNext() const {
+    std::list<int>* states = getSignalStates();
+    if (states->empty()) {
+        return (-1);
+    }
+    return (*states->begin());
+}
+
+void
+SignalSet::handleNext(SignalHandler signal_handler) {
+    block();
+    int signum = getNext();
+    if (signum >= 0) {
+        popNext();
+        try {
+            signal_handler(signum);
+        } catch (...) {
+            unblock();
+            throw;
+        }
+    }
+    unblock();
+}
+
+void
+SignalSet::maskSignals(const int mask) const {
+    sigset_t new_set;
+    for (Pool::const_iterator it = registered_signals_.begin();
+         it != registered_signals_.end(); ++it) {
+        sigaddset(&new_set, *it);
+    }
+    sigprocmask(mask, &new_set, 0);
+}
+
+void
+SignalSet::popNext() const {
+    std::list<int>* states = getSignalStates();
+    if (!states->empty()) {
+        states->pop_front();
+    }
+}
+
+void
+SignalSet::remove(const int sig) {
+    if (registered_signals_.find(sig) != registered_signals_.end()) {
+        struct sigaction sa;
+        sa.sa_handler = SIG_DFL;
+        if (sigaction(sig, &sa, 0) < 0) {
+            isc_throw(SignalSetError, "unable to restore original signal"
+                      " handler for signal: " << sig);
+        }
+        registered_signals_.erase(sig);
+    }
+}
+
+} // end of isc::util::io
+} // end of isc::util
+} // end of isc

+ 84 - 0
src/lib/util/io/signal_set.h

@@ -0,0 +1,84 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// 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.
+
+#ifndef SIGNAL_SET_H
+#define SIGNAL_SET_H
+
+#include <exceptions/exceptions.h>
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+#include <set>
+#include <signal.h>
+
+namespace isc {
+namespace util {
+namespace io {
+
+class SignalSetError : public Exception {
+public:
+    SignalSetError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+typedef boost::function<void(int signum)> SignalHandler;
+
+class SignalSet : public boost::noncopyable {
+private:
+    typedef std::set<int> Pool;
+
+public:
+
+    SignalSet(const int sig0);
+
+    SignalSet(const int sig0, const int sig1);
+
+    SignalSet(const int sig0, const int sig1, const int sig2);
+
+    /// @throw SignalSetError if signal being added duplicates an existing
+    /// signal.
+    void add(const int sig);
+
+    void clear();
+
+    int getNext() const;
+
+    void handleNext(SignalHandler signal_handler);
+
+    void remove(const int sig);
+
+private:
+
+    void block() const {
+        maskSignals(SIG_BLOCK);
+    }
+
+    Pool::iterator erase(const int signal);
+
+    void maskSignals(const int mask) const;
+
+    void popNext() const;
+
+    void unblock() const {
+        maskSignals(SIG_UNBLOCK);
+    }
+
+    Pool registered_signals_;
+};
+
+}
+}
+}
+
+#endif // SIGNAL_SET_H
+

+ 31 - 0
src/lib/util/io/tests/Makefile.am

@@ -0,0 +1,31 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+        $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += signal_set_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD  = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
+run_unittests_LDADD += $(GTEST_LDADD)
+
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 23 - 0
src/lib/util/io/tests/run_unittests.cc

@@ -0,0 +1,23 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// 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.
+
+#include <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    int result = RUN_ALL_TESTS();
+    return (result);
+}

+ 93 - 0
src/lib/util/io/tests/signal_set_unittest.cc

@@ -0,0 +1,93 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// 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.
+
+#include <util/io/signal_set.h>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+#include <signal.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::util::io;
+
+class SignalSetTest : public ::testing::Test {
+public:
+
+    SignalSetTest()
+        : handler_calls_(0),
+          signum_ (-1) {
+    }
+
+    ~SignalSetTest() {
+        if (signal_set_) {
+            signal_set_->clear();
+        }
+    }
+
+    void handleNext() {
+        signal_set_->handleNext(boost::bind(&SignalSetTest::testHandler,
+                                            this, _1));
+    }
+
+    void testHandler(int signum) {
+        signum_ = signum;
+        ++handler_calls_;
+    }
+
+    int handler_calls_;
+    int signum_;
+    boost::shared_ptr<SignalSet> signal_set_;
+
+};
+
+TEST_F(SignalSetTest, twoSignals) {
+    // Register handlers for two signals.
+    signal_set_.reset(new SignalSet(SIGHUP, SIGINT));
+    // Send SIGHUP signal to the process.
+    ASSERT_EQ(0, raise(SIGHUP));
+    // The SIGHUP should be the next one in the queue to be handled.
+    EXPECT_EQ(SIGHUP, signal_set_->getNext());
+    // But, no handlers should have been called yet.
+    EXPECT_EQ(0, handler_calls_);
+    // Send a different signal.
+    ASSERT_EQ(0, raise(SIGINT));
+    // The SIGHUP hasn't been handled yet so it should still be the first
+    // one in the queue.
+    EXPECT_EQ(SIGHUP, signal_set_->getNext());
+    // No handlers have been called yet.
+    EXPECT_EQ(0, handler_calls_);
+    // Raise another SIGHUP before the first one has been handled. The
+    // second one should be dropped.
+    ASSERT_EQ(0, raise(SIGHUP));
+    // Execute the first handler (for SIGHUP).
+    handleNext();
+    // The handler should have been called once and the signal
+    // handled should be SIGHUP.
+    EXPECT_EQ(1, handler_calls_);
+    EXPECT_EQ(SIGHUP, signum_);
+    // Next signal to be handled should be SIGINT.
+    EXPECT_EQ(SIGINT, signal_set_->getNext());
+    handleNext();
+    EXPECT_EQ(2, handler_calls_);
+    EXPECT_EQ(SIGINT, signum_);
+    // There should be no more waiting handlers.
+    EXPECT_EQ(-1, signal_set_->getNext());
+    // Make sure that signals can be unregistered.
+    EXPECT_NO_THROW(signal_set_->remove(SIGHUP));
+    EXPECT_NO_THROW(signal_set_->remove(SIGINT));
+}
+
+} // end of anonymous namespace