From: g0dil Date: Wed, 9 Jul 2008 14:16:12 +0000 (+0000) Subject: Utils/Console: Fix singleton instantiation order (ServerManager / Scheduler) X-Git-Url: http://g0dil.de/git?p=senf.git;a=commitdiff_plain;h=40fa3e3f1e0f639c68bd15bf469e35045f94abee Utils/Console: Fix singleton instantiation order (ServerManager / Scheduler) Scheduler: Add automatic periodic resync to ClockService::abstime() and ClockService::clock() Scheduler: Add task nameing Scheduler: BUGFIX: Add missing task-dequeue in FdDispatcher::add() Scheduler: Add empty() member to all dispatchers Scheduler: Add eventTime() support to FdManager Scheduler: Add support for non-pollable file descriptor detection to FdManager and FdDispatcher Scheduler: Add FIFORunner task watchdog Scheduler: Add debugging backtrace to FIFORunner Scheduler: Change senf::Scheduler implementation to use the new dispatcher infrastructure Scheduler: BUGFIX: Fix access to (possibly) destroyed object in TimerEvent::run() Utils: Add senf::signalName() helper Utils/Daemon: Use senf::signalName() in debug messages Utils/Logger: Fix logger to correctly format the timestamp (the deadlock should be fixed now) Update scheduler callback signatures to the new API throughout the project Update documentation git-svn-id: https://svn.berlios.de/svnroot/repos/senf/trunk@892 270642c3-0616-0410-b53a-bc976706d245 --- diff --git a/Console/Readline.cc b/Console/Readline.cc index 094c8e4..f1597d4 100644 --- a/Console/Readline.cc +++ b/Console/Readline.cc @@ -198,7 +198,7 @@ prefix_ void senf::console::detail::ReadlineClientReader::v_translate(std::strin boost::replace_all(data, "\xff", "\xff\xff"); } -prefix_ void senf::console::detail::ReadlineClientReader::charEvent(Scheduler::EventId event) +prefix_ void senf::console::detail::ReadlineClientReader::charEvent(int event) { char ch; if (event != Scheduler::EV_READ || handle().read(&ch, &ch+1) <= &ch) { diff --git a/Console/Readline.hh b/Console/Readline.hh index 00372d7..09a9e61 100644 --- a/Console/Readline.hh +++ b/Console/Readline.hh @@ -72,7 +72,7 @@ namespace detail { virtual void v_enablePrompt(); virtual void v_translate(std::string & data); - void charEvent(Scheduler::EventId event); + void charEvent(int event); static ReadlineClientReader * instance_; int ch_; diff --git a/Console/Server.cc b/Console/Server.cc index dc169fa..3ef80ba 100644 --- a/Console/Server.cc +++ b/Console/Server.cc @@ -104,7 +104,7 @@ prefix_ senf::console::Server::~Server() Scheduler::instance().remove(handle_); } -prefix_ void senf::console::Server::newClient(Scheduler::EventId event) +prefix_ void senf::console::Server::newClient(int event) { ServerHandle::ClientSocketHandle client (handle_.accept()); boost::intrusive_ptr p (new Client(*this, client)); @@ -200,7 +200,7 @@ prefix_ void senf::console::detail::NoninteractiveClientReader::v_translate(std: {} prefix_ void -senf::console::detail::NoninteractiveClientReader::newData(senf::Scheduler::EventId event) +senf::console::detail::NoninteractiveClientReader::newData(int event) { if (event != senf::Scheduler::EV_READ || handle().eof()) { if (! buffer_.empty()) diff --git a/Console/Server.cci b/Console/Server.cci index e4601f6..81aaf6d 100644 --- a/Console/Server.cci +++ b/Console/Server.cci @@ -43,6 +43,12 @@ prefix_ void senf::console::detail::ServerManager::remove(ptr server) instance().servers_.erase(instance().servers_.find(server)); } +prefix_ senf::console::detail::ServerManager & senf::console::detail::ServerManager::instance() +{ + static ServerManager manager; + return manager; +} + /////////////////////////////////////////////////////////////////////////// // senf::console::detail::NonblockingSocketSink diff --git a/Console/Server.hh b/Console/Server.hh index 1756e14..b7684d5 100644 --- a/Console/Server.hh +++ b/Console/Server.hh @@ -129,7 +129,7 @@ namespace console { static Server & start(ServerHandle handle); - void newClient(Scheduler::EventId event); + void newClient(int event); void removeClient(Client & client); ServerHandle handle_; diff --git a/Console/Server.ih b/Console/Server.ih index 925fd64..3ca80a1 100644 --- a/Console/Server.ih +++ b/Console/Server.ih @@ -42,7 +42,6 @@ namespace console { namespace detail { class ServerManager - : public senf::singleton { public: typedef boost::intrusive_ptr ptr; @@ -53,6 +52,8 @@ namespace detail { static void add(ptr server); static void remove(ptr server); + static ServerManager & instance(); + typedef std::set Servers; Servers servers_; @@ -170,7 +171,7 @@ namespace detail { virtual void v_enablePrompt(); virtual void v_translate(std::string & data); - void newData(senf::Scheduler::EventId event); + void newData(int event); SchedulerBinding binding_; std::string buffer_; diff --git a/Examples/MCSniffer/MCSniffer.cc b/Examples/MCSniffer/MCSniffer.cc index 896ef3a..211ebaf 100644 --- a/Examples/MCSniffer/MCSniffer.cc +++ b/Examples/MCSniffer/MCSniffer.cc @@ -57,7 +57,7 @@ public: } private: - void dumpPacket(senf::Scheduler::EventId event) + void dumpPacket(int event) { std::string data (sock.read()); senf::EthernetPacket packet ( diff --git a/Examples/Sniffer/Sniffer.cc b/Examples/Sniffer/Sniffer.cc index fbeeab4..7f74806 100644 --- a/Examples/Sniffer/Sniffer.cc +++ b/Examples/Sniffer/Sniffer.cc @@ -82,7 +82,7 @@ public: } private: - void dumpPacket(senf::Scheduler::EventId event) + void dumpPacket(int event) { senf::EthernetPacket packet ( senf::EthernetPacket::create(senf::noinit)); diff --git a/Examples/TCPClientServer/server.cc b/Examples/TCPClientServer/server.cc index 93fb1ba..4dbf274 100644 --- a/Examples/TCPClientServer/server.cc +++ b/Examples/TCPClientServer/server.cc @@ -50,7 +50,7 @@ public: } private: - void accept(senf::Scheduler::EventId event) + void accept(int event) { senf::TCPv4ClientSocketHandle clientSock (serverSock.accept()); senf::Scheduler::instance().add( @@ -59,7 +59,7 @@ private: senf::Scheduler::EV_READ); } - void readFromClient(senf::TCPv4ClientSocketHandle clientSock, senf::Scheduler::EventId event) + void readFromClient(senf::TCPv4ClientSocketHandle clientSock, int event) { if (!clientSock) { senf::Scheduler::instance().remove(clientSock); diff --git a/Examples/UDPClientServer/udpServer.cc b/Examples/UDPClientServer/udpServer.cc index 091b6ba..d549a92 100644 --- a/Examples/UDPClientServer/udpServer.cc +++ b/Examples/UDPClientServer/udpServer.cc @@ -44,7 +44,7 @@ public: } private: - void readFromClient(senf::Scheduler::EventId event) + void readFromClient(int event) { std::string data (serverSock.read()); std::cout << "> " << data< 1000000000ll) + restart_m(); boost::posix_time::time_duration delta (time - baseAbstime_); return baseClock_ + clock_type( delta.ticks() ) * clock_type( 1000000000UL / boost::posix_time::time_duration::ticks_per_second() ); @@ -54,6 +56,8 @@ prefix_ senf::ClockService::clock_type senf::ClockService::clock_m(abstime_type prefix_ senf::ClockService::abstime_type senf::ClockService::abstime_m(clock_type clock) { + if (now() - baseClock_ > 1000000000ll) + restart_m(); #ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG return baseAbstime_ + boost::posix_time::nanoseconds(clock-baseClock_); #else diff --git a/Scheduler/ClockService.hh b/Scheduler/ClockService.hh index da50573..5b02c19 100644 --- a/Scheduler/ClockService.hh +++ b/Scheduler/ClockService.hh @@ -43,55 +43,21 @@ namespace senf { namespace detail { class ClockServiceTest; } #endif - // Implementation note: + // Implementation note: The clock value is represented as a 64bit unsigned integer number of + // nanosecods based on the CLOCK_MONOTONIC POSIX clock. // - // The clock value is represented as a 64bit unsigned integer number of nanosecods elapsed since - // the construction of the ClockService object. - // - // The implementation must provide two features: - // a) It must reliably detect clock changes - // b) In case of a clock change a reasonably accurate fallback clock value must be provided - // - // We do this using setitimer/getitimer. We setup an interval timer sending SIGALRM whenever - // CheckInverval seconds have elapsed. - // - // On every SIGALRM signal we save the current value of gettimeofday(). If this new value is - // substantially different from the currently saved value + CheckInterval, the clock has been - // changed. - // - // Whenever the current clock value is requested using now(), the current gettimeofday() value - // is compared with the saved value. If the difference is substantially more than CheckInterval, - // the clock has been changed. - // - // This provides clock skew detection. If clock skew is detected, we need to move base_ by the - // amount the time has been changed. To do this we need an as accurate as possible approximation - // of the expected current time value. We need to differentiate two cases: - // - // a) Clock skew detected within now() - // - // In this case, we use getitimer() to find the time remaining in the timer. Using this value - // and the saved gettimeofday() value we can adjust base_ accordingly. - // - // b) Clock skew detected in the signal handler - // - // In this case we use the saved gettimeofday() value + CheckInterval to adjust base_. + // To allow conversion between clock value and absolute time, the ClockService samples the + // absolute current time and the clock value when the conversion is performed. This is done at + // most once per second on a if-needed basis. /** \brief Reliable high precision monotonous clock source The ClockService provides a highly accurate monotonous clock source based on gettimeofday(). However, it takes additional precautions to detect clock skew. - \implementation We use a mix of static and non-static members to achieve high performance - in the normal case (no clock skew) and still encapsulate the dependency on legacy C - headers. Using the senf::singleton mixin ensures, that the instance is constructed - before main even when instance() is not called. - - \bug There is a deadlock condition between ClockService and the streaming of Boost.DateTime - values: Boost.DateTime seems to call tzset() whenever writing a date/time value (ugh) - and since tzset changes basic date/time values, it seems to block gettimeofday() which - leads to the SIGLARM handler blocking indefinitely. Resolution either a) find out, why - tzset() of all functions is called or b) move the ClockService heartbeat functionality - into the Scheduler. + \implementation The funny mixture of static and non-static members stems from the old + implementation based on interval timers and gettimeofday(). The current implementation + usses POSIX clocks and is much simpler and more precise. */ class ClockService : singleton @@ -106,6 +72,11 @@ namespace senf { nanoseconds relative to some implementation defined reference time. */ typedef boost::int_fast64_t clock_type; + + /** \brief Supplementary integer type + + This type is used to represent varies supplementary values (e.g. number of microseconds) + */ typedef boost::int_fast64_t int64_type; /** \brief Absolute time data type @@ -157,7 +128,10 @@ namespace senf { static int64_type in_hours(clock_type v); ///< Convert \a v to hours static int64_type in_days(clock_type v); ///< Convert \a v to days - static void restart(); + static void restart(); ///< Force re-syncronisation of abstime and clock + /**< Calling the member should never be necessary since + abstime() / clock() automatically call restart() if + needed */ private: ClockService(); diff --git a/Scheduler/FIFORunner.cc b/Scheduler/FIFORunner.cc index 169bcf8..10cfdc2 100644 --- a/Scheduler/FIFORunner.cc +++ b/Scheduler/FIFORunner.cc @@ -27,16 +27,50 @@ //#include "FIFORunner.ih" // Custom includes +#include +#include +#include "../Utils/Exception.hh" //#include "FIFORunner.mpp" #define prefix_ ///////////////////////////////cc.p//////////////////////////////////////// +prefix_ senf::scheduler::FIFORunner::FIFORunner() + : tasks_ (), next_ (tasks_.end()), hangCount_ (0) +{ + struct sigevent ev; + ::memset(&ev, 0, sizeof(ev)); + ev.sigev_notify = SIGEV_SIGNAL; + ev.sigev_signo = SIGURG; + ev.sigev_value.sival_ptr = this; + if (timer_create(CLOCK_MONOTONIC, &ev, &watchdogId_) < 0) + SENF_THROW_SYSTEM_EXCEPTION("timer_create()"); + + struct sigaction sa; + ::memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = &watchdog; + sa.sa_flags = SA_SIGINFO; + if (sigaction(SIGURG, &sa, 0) < 0) + SENF_THROW_SYSTEM_EXCEPTION("sigaction()"); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGURG); + if (sigprocmask(SIG_UNBLOCK, &mask, 0) < 0) + SENF_THROW_SYSTEM_EXCEPTION("sigprocmask()"); +} + +prefix_ senf::scheduler::FIFORunner::~FIFORunner() +{ + timer_delete(watchdogId_); + signal(SIGURG, SIG_DFL); +} + // At the moment, the FIFORunner is not very efficient with many non-runnable tasks since the // complete list of tasks is traversed on each run(). // // To optimize this, we woould need a way to find the relative ordering of two tasks in O(1) (at the -// moment, this is an O)(N) operation by traversing the list). +// moment, this is an O(N) operation by traversing the list). // // One idea is, to give each task an 'order' value. Whenever a task is added at the end, it's order // value is set to the order value of the last task + 1. Whenever the order value such added exceeds @@ -85,10 +119,21 @@ prefix_ void senf::scheduler::FIFORunner::run() tasks_.push_back(null); TaskList::iterator end (TaskList::current(null)); next_ = tasks_.begin(); + struct itimerspec timer; + timer.it_interval.tv_sec = 0; + timer.it_interval.tv_nsec = 0; + timer.it_value.tv_sec = 1; + timer.it_value.tv_nsec = 0; while (next_ != end) { TaskInfo & task (*next_); if (task.runnable) { task.runnable = false; + if (timer_settime(watchdogId_, 0, &timer, 0) < 0) + SENF_THROW_SYSTEM_EXCEPTION("timer_settime()"); + runningName_ = task.name; +# ifdef SENF_DEBUG + runningBacktrace_ = task.backtrace; +# endif TaskList::iterator i (next_); ++ next_; tasks_.splice(tasks_.end(), tasks_, i); @@ -97,10 +142,27 @@ prefix_ void senf::scheduler::FIFORunner::run() else ++ next_; } + timer.it_value.tv_sec = 0; + if (timer_settime(watchdogId_, 0, &timer, 0) < 0) + SENF_THROW_SYSTEM_EXCEPTION("timer_settime()"); tasks_.erase(end); next_ = tasks_.end(); } +prefix_ void senf::scheduler::FIFORunner::watchdog(int, siginfo_t * si, void *) +{ + FIFORunner & runner (*static_cast(si->si_value.sival_ptr)); + ++ runner.hangCount_; + write(1, "\n\n*** Scheduler task hanging: ", 30); + write(1, runner.runningName_.c_str(), runner.runningName_.size()); + write(1, "\n", 1); +#ifdef SENF_DEBUG + write(1, "Task was initialized at\n", 24); + write(1, runner.runningBacktrace_.c_str(), runner.runningBacktrace_.size()); +#endif + write(1, "\n", 1); +} + ///////////////////////////////cc.e//////////////////////////////////////// #undef prefix_ //#include "FIFORunner.mpp" diff --git a/Scheduler/FIFORunner.cci b/Scheduler/FIFORunner.cci index 5247d5d..1061f4d 100644 --- a/Scheduler/FIFORunner.cci +++ b/Scheduler/FIFORunner.cci @@ -26,6 +26,10 @@ //#include "FIFORunner.ih" // Custom includes +#ifdef SENF_DEBUG +#include +#include "../Utils/Backtrace.hh" +#endif #define prefix_ inline ///////////////////////////////cci.p/////////////////////////////////////// @@ -37,13 +41,20 @@ prefix_ senf::scheduler::FIFORunner::TaskInfo::TaskInfo() prefix_ senf::scheduler::FIFORunner::TaskInfo::~TaskInfo() {} -prefix_ senf::scheduler::FIFORunner::FIFORunner() - : tasks_ (), next_ (tasks_.end()) -{} - prefix_ void senf::scheduler::FIFORunner::enqueue(TaskInfo * task) { tasks_.push_back(*task); +#ifdef SENF_DEBUG + std::stringstream ss; + backtrace(ss, 32); + task->backtrace = ss.str(); +#endif +} + +prefix_ unsigned senf::scheduler::FIFORunner::hangCount() + const +{ + return hangCount_; } ///////////////////////////////cci.e/////////////////////////////////////// diff --git a/Scheduler/FIFORunner.hh b/Scheduler/FIFORunner.hh index 3ad8d7e..9121445 100644 --- a/Scheduler/FIFORunner.hh +++ b/Scheduler/FIFORunner.hh @@ -27,6 +27,7 @@ #define HH_FIFORunner_ 1 // Custom includes +#include #include #include "../boost/intrusive/ilist.hpp" #include "../boost/intrusive/ilist_hook.hpp" @@ -37,7 +38,14 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Task execution scheduler + + The FIFORunner implements a simple FIFO scheduler for callback tasks. All tasks are held in + a queue. Whenever a task is run, it is moved to the end of the queue. Running the queue will + run all tasks which have been marked runnable. + + When running a task, it's runnable flag is always reset. The flag is set whenever an event + is posted for the task. */ class FIFORunner : boost::noncopyable @@ -55,14 +63,26 @@ namespace scheduler { /////////////////////////////////////////////////////////////////////////// // Types + /** \brief Task structure + + TaskInfo is the base-class for all tasks. + */ struct TaskInfo : public TaskListBase { TaskInfo(); virtual ~TaskInfo(); - bool runnable; - virtual void run() = 0; + bool runnable; ///< Runnable flag + /**< This must be set to \c true when the task is + runnable. It is reset automatically when the task is + run. */ + + std::string name; ///< Descriptive task name +# ifdef SENF_DEBUG + std::string backtrace; +# endif + virtual void run() = 0; ///< Called to run the task }; /////////////////////////////////////////////////////////////////////////// @@ -70,20 +90,35 @@ namespace scheduler { ///@{ FIFORunner(); + ~FIFORunner(); ///@} /////////////////////////////////////////////////////////////////////////// - void enqueue(TaskInfo * task); - void dequeue(TaskInfo * task); + void enqueue(TaskInfo * task); ///< Add task to queue + void dequeue(TaskInfo * task); ///< Remove task from queue - void run(); + void run(); ///< Run queue + + unsigned hangCount() const; ///< Number of task expirations + /**< The FIFORunner manages a watchdog which checks, that a + single task does not run continuously for a longer time + or block. If a task runs for more than 1s, a warning is + printed and the hangCount is increased. */ protected: private: + static void watchdog(int, siginfo_t *, void *); + TaskList tasks_; TaskList::iterator next_; + int watchdogId_; + std::string runningName_; +# ifdef SENF_DEBUG + std::string runningBacktrace_; +# endif + unsigned hangCount_; }; diff --git a/Scheduler/FdDispatcher.cc b/Scheduler/FdDispatcher.cc index 7d033a3..4103b51 100644 --- a/Scheduler/FdDispatcher.cc +++ b/Scheduler/FdDispatcher.cc @@ -46,10 +46,11 @@ prefix_ senf::scheduler::FdDispatcher::~FdDispatcher() } } -prefix_ void senf::scheduler::FdDispatcher::add(int fd, Callback const & cb, int events) +prefix_ bool senf::scheduler::FdDispatcher::add(std::string const & name, int fd, + Callback const & cb, int events) { if (events == 0) - return; + return true; FdMap::iterator i (fds_.find(fd)); if (i == fds_.end()) { @@ -60,11 +61,28 @@ prefix_ void senf::scheduler::FdDispatcher::add(int fd, Callback const & cb, int } FdEvent & event (i->second); - if (events & EV_READ) event.FdEvent::ReadTask::cb = cb; - if (events & EV_PRIO) event.FdEvent::PrioTask::cb = cb; - if (events & EV_WRITE) event.FdEvent::WriteTask::cb = cb; + if (events & EV_READ) { + event.FdEvent::ReadTask::cb = cb; + event.FdEvent::ReadTask::name = name; + } + if (events & EV_PRIO) { + event.FdEvent::PrioTask::cb = cb; + event.FdEvent::PrioTask::name = name; + } + if (events & EV_WRITE) { + event.FdEvent::WriteTask::cb = cb; + event.FdEvent::WriteTask::name = name; + } - manager_.set(fd, event.activeEvents(), &event); + if (! manager_.set(fd, event.activeEvents(), &event)) { + runner_.dequeue(static_cast(&i->second)); + runner_.dequeue(static_cast(&i->second)); + runner_.dequeue(static_cast(&i->second)); + fds_.erase(i); + return false; + } + else + return true; } prefix_ void senf::scheduler::FdDispatcher::remove(int fd, int events) @@ -77,9 +95,18 @@ prefix_ void senf::scheduler::FdDispatcher::remove(int fd, int events) return; FdEvent & event (i->second); - if (events & EV_READ) event.FdEvent::ReadTask::cb = 0; - if (events & EV_PRIO) event.FdEvent::PrioTask::cb = 0; - if (events & EV_WRITE) event.FdEvent::WriteTask::cb = 0; + if (events & EV_READ) { + event.FdEvent::ReadTask::cb = 0; + event.FdEvent::ReadTask::name.clear(); + } + if (events & EV_PRIO) { + event.FdEvent::PrioTask::cb = 0; + event.FdEvent::PrioTask::name.clear(); + } + if (events & EV_WRITE) { + event.FdEvent::WriteTask::cb = 0; + event.FdEvent::WriteTask::name.clear(); + } int activeEvents (event.activeEvents()); if (! activeEvents) { @@ -106,7 +133,7 @@ prefix_ void senf::scheduler::FdDispatcher::FdEvent::signal(int e) if (events & EV_WRITE) WriteTask::runnable = true; - if (events & (EV_ERR | EV_HUP) && ! events & (EV_READ | EV_PRIO | EV_WRITE)) { + if ((events & (EV_ERR | EV_HUP)) && ! (events & (EV_READ | EV_PRIO | EV_WRITE))) { if (ReadTask::cb) ReadTask::runnable = true; if (PrioTask::cb) PrioTask::runnable = true; if (WriteTask::cb) WriteTask::runnable = true; diff --git a/Scheduler/FdDispatcher.cci b/Scheduler/FdDispatcher.cci index 2222cee..d7c1034 100644 --- a/Scheduler/FdDispatcher.cci +++ b/Scheduler/FdDispatcher.cci @@ -37,6 +37,12 @@ prefix_ senf::scheduler::FdDispatcher::FdDispatcher(FdManager & manager, FIFORun : manager_ (manager), runner_ (runner) {} +prefix_ bool senf::scheduler::FdDispatcher::empty() + const +{ + return fds_.empty(); +} + /////////////////////////////////////////////////////////////////////////// // senf::scheduler::FdDispatcher::FdEvent diff --git a/Scheduler/FdDispatcher.hh b/Scheduler/FdDispatcher.hh index 696be2a..affc61d 100644 --- a/Scheduler/FdDispatcher.hh +++ b/Scheduler/FdDispatcher.hh @@ -38,7 +38,9 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Scheduler dispatcher managing poll-able file descriptors + + File descriptors are added directly to the event loop. */ class FdDispatcher { @@ -64,12 +66,27 @@ namespace scheduler { ///@} /////////////////////////////////////////////////////////////////////////// - void add(int fd, Callback const & cb, int events = EV_ALL); - void remove(int fd, int events = EV_ALL); + bool add(std::string const & name, int fd, Callback const & cb, int events = EV_ALL); + ///< Add file descriptor callback + /**< There is always one active callback for each + combination of file descriptor and event. Registering a + new callback will overwrite the old callback. + \param[in] name descriptive name + \param[in] fd file descriptor + \param[in] cb callback + \param[in] events Events to call \a cb for */ + + void remove(int fd, int events = EV_ALL); ///< Remove callback + /**< \param[in] fd file descriptor + \param[in] events Events for which to remove the + callback */ + + bool empty() const; ///< \c true, if no file descriptors are registered. protected: private: + /// Internal: File descriptor event struct FdEvent : public detail::FdTask<0, FdEvent>, public detail::FdTask<1, FdEvent>, diff --git a/Scheduler/FdDispatcher.ih b/Scheduler/FdDispatcher.ih index 81ea330..42d3c85 100644 --- a/Scheduler/FdDispatcher.ih +++ b/Scheduler/FdDispatcher.ih @@ -34,6 +34,11 @@ namespace senf { namespace scheduler { namespace detail { + /** \brief Internal: File descriptor task + + \implementation This class allows to inherit FIFORunner::TaskInfo multiple times to add + multiple tasks to one event and still allows efficient access to the event class + */ template struct FdTask : public FIFORunner::TaskInfo diff --git a/Scheduler/FdDispatcher.test.cc b/Scheduler/FdDispatcher.test.cc index 2a87e71..853d862 100644 --- a/Scheduler/FdDispatcher.test.cc +++ b/Scheduler/FdDispatcher.test.cc @@ -185,8 +185,8 @@ BOOST_AUTO_UNIT_TEST(fdDispatcher) BOOST_FAIL("connect"); } - SENF_CHECK_NO_THROW( dispatcher.add(sock, boost::bind(&callback, sock, _1), - senf::scheduler::FdDispatcher::EV_READ) ); + BOOST_CHECK( dispatcher.add("testHandler", sock, boost::bind(&callback, sock, _1), + senf::scheduler::FdDispatcher::EV_READ) ); event = 0; SENF_CHECK_NO_THROW( manager.processOnce() ); SENF_CHECK_NO_THROW( runner.run() ); @@ -197,8 +197,8 @@ BOOST_AUTO_UNIT_TEST(fdDispatcher) strcpy(buffer,"WRITE"); size=5; - SENF_CHECK_NO_THROW( dispatcher.add(sock, boost::bind(&callback, sock, _1), - senf::scheduler::FdDispatcher::EV_WRITE) ); + BOOST_CHECK( dispatcher.add("testHandler", sock, boost::bind(&callback, sock, _1), + senf::scheduler::FdDispatcher::EV_WRITE) ); event = 0; sleep(1); SENF_CHECK_NO_THROW( manager.processOnce() ); diff --git a/Scheduler/FdManager.cc b/Scheduler/FdManager.cc index 323825d..db17838 100644 --- a/Scheduler/FdManager.cc +++ b/Scheduler/FdManager.cc @@ -35,6 +35,7 @@ prefix_ void senf::scheduler::FdManager::processOnce() { Poller::range events (poller_.wait()); + eventTime_ = ClockService::now(); for (Poller::iterator i (events.begin()); i != events.end(); ++i) i->second->signal(i->first); } diff --git a/Scheduler/FdManager.cci b/Scheduler/FdManager.cci index 5f7c175..467a97a 100644 --- a/Scheduler/FdManager.cci +++ b/Scheduler/FdManager.cci @@ -33,9 +33,13 @@ /////////////////////////////////////////////////////////////////////////// // senf::scheduler::FdManager -prefix_ void senf::scheduler::FdManager::set(int fd, int events, Event * entry) +prefix_ senf::scheduler::FdManager::FdManager() + : eventTime_ (senf::ClockService::now()) +{} + +prefix_ bool senf::scheduler::FdManager::set(int fd, int events, Event * entry) { - poller_.set(fd, events, entry); + return poller_.set(fd, events, entry); } prefix_ void senf::scheduler::FdManager::remove(int fd) @@ -54,6 +58,12 @@ prefix_ int senf::scheduler::FdManager::timeout() return poller_.timeout(); } +prefix_ senf::ClockService::clock_type senf::scheduler::FdManager::eventTime() + const +{ + return eventTime_; +} + /////////////////////////////////////////////////////////////////////////// // senf::scheduler::FdManager::Event diff --git a/Scheduler/FdManager.hh b/Scheduler/FdManager.hh index ecc7f98..4d7b05e 100644 --- a/Scheduler/FdManager.hh +++ b/Scheduler/FdManager.hh @@ -28,6 +28,7 @@ // Custom includes #include "Poller.hh" +#include "ClockService.hh" //#include "FdManager.mpp" ///////////////////////////////hh.p//////////////////////////////////////// @@ -35,7 +36,21 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Manage file descriptor event processing + + The FdManager is the internal class which manages all events (all events need to somehow be + made accessible via a file descriptor). File descriptors are added or removed from the + FdManager which then allows waiting until an event occurs on one of the descriptors. + + Registered events must be derived from FdManager::Event. The FdManager does \e not manage + the event classes, it just manages pointers to externally owned events (the events are owned + by the respective dispatchers). + + When an event is posted, it's \c signal() member is called. However, this call will \e not + execute the user callback registered for the event, it will just mark the relevant tasks as + runnable. + + \implementation */ class FdManager { @@ -43,9 +58,10 @@ namespace scheduler { /////////////////////////////////////////////////////////////////////////// // Types + ///< Event baseclass struct Event { virtual ~Event(); - virtual void signal(int events) = 0; + virtual void signal(int events) = 0; ///< Called when the given event is posted }; enum Events { @@ -57,21 +73,41 @@ namespace scheduler { ///\name Structors and default members ///@{ + FdManager(); + ///@} /////////////////////////////////////////////////////////////////////////// - void set(int fd, int events, Event * entry); - void remove(int fd); - - void timeout(int t); - int timeout() const; - - void processOnce(); + bool set(int fd, int events, Event * entry); ///< Set file descriptor event mask + /**< This sets the event mask for \a fd to \a events which + is a combination of values from the \c Events enum. If + \a fd is already registered, the registration is + changed to conform to the parameters passed, otherwise + a new registration is added. + \param[in] fd file descriptor + \param[in] events events to register for + \param[in] entry event to signal + \returns \c true, if \a fd supports polling, \c false + otherwise */ + void remove(int fd); ///< Remove \a fd from the manager + + void timeout(int t); ///< Set event timeout + /**< proceseOnce() will wait for max \a t milliseconds for + an event to occur. If set to -1, processOnce() will + wait forever. */ + int timeout() const; ///< Get timeout in milliseconds + + void processOnce(); ///< Wait for events + /**< This call waits until at least one event is posted but + no longer than the current timeout(). */ + + ClockService::clock_type eventTime() const; ///< Time of last event protected: private: Poller poller_; + senf::ClockService::clock_type eventTime_; }; }} diff --git a/Scheduler/FileDispatcher.cc b/Scheduler/FileDispatcher.cc index 1989c90..780335c 100644 --- a/Scheduler/FileDispatcher.cc +++ b/Scheduler/FileDispatcher.cc @@ -47,7 +47,8 @@ prefix_ senf::scheduler::FileDispatcher::~FileDispatcher() } } -prefix_ void senf::scheduler::FileDispatcher::add(int fd, Callback const & cb, int events) +prefix_ void senf::scheduler::FileDispatcher::add(std::string const & name, int fd, + Callback const & cb, int events) { if (events == 0) return; @@ -60,8 +61,14 @@ prefix_ void senf::scheduler::FileDispatcher::add(int fd, Callback const & cb, i } FileEvent & event (i->second); - if (events & EV_READ) event.FileEvent::ReadTask::cb = cb; - if (events & EV_WRITE) event.FileEvent::WriteTask::cb = cb; + if (events & EV_READ) { + event.FileEvent::ReadTask::cb = cb; + event.FileEvent::ReadTask::name = name; + } + if (events & EV_WRITE) { + event.FileEvent::WriteTask::cb = cb; + event.FileEvent::WriteTask::name = name; + } manager_.timeout(0); } diff --git a/Scheduler/FileDispatcher.cci b/Scheduler/FileDispatcher.cci index 7d932d1..a18ea27 100644 --- a/Scheduler/FileDispatcher.cci +++ b/Scheduler/FileDispatcher.cci @@ -51,6 +51,12 @@ prefix_ int senf::scheduler::FileDispatcher::timeout() return managerTimeout_; } +prefix_ bool senf::scheduler::FileDispatcher::empty() + const +{ + return files_.empty(); +} + ///////////////////////////////cci.e/////////////////////////////////////// #undef prefix_ diff --git a/Scheduler/FileDispatcher.hh b/Scheduler/FileDispatcher.hh index cd4b884..4bff54d 100644 --- a/Scheduler/FileDispatcher.hh +++ b/Scheduler/FileDispatcher.hh @@ -38,7 +38,14 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Scheduler dispatcher managing disc files + + This dispatcher manages file descriptors which are connected to disc files. Since disc files + do not support select() / poll() / epoll(), they are considered to be always ready (which is + especially untrue for remote files e.g. vie NFS). + + The FileDispatcher will change the FdManager's event timeout value to 0 (from -1) whenever + there is at least one file registered. */ class FileDispatcher { @@ -64,17 +71,37 @@ namespace scheduler { ///@} /////////////////////////////////////////////////////////////////////////// - void add(int fd, Callback const & cb, int events = EV_ALL); + void add(std::string const & name, int fd, Callback const & cb, int events = EV_ALL); + ///< Add file descriptor callback + /**< There is always one active callback for each + combination of file descriptor and event. Registering a + new callback will overwrite the old callback. + \param[in] name descriptive name + \param[in] fd file descriptor + \param[in] cb callback + \param[in] events Events to call \a cb for */ + void remove(int fd, int events = EV_ALL); + /**< \param[in] fd file descriptor + \param[in] events Events for which to remove the + callback */ + + void prepareRun(); ///< Prepare tasks + /**< This must be called after the FdManager returns before + running the runnable tasks. */ - void prepareRun(); + void timeout(int t); ///< Change FdManager timeout + /**< Since the FileDispatcher must be able to change the + timeout value, the value must be set here and not + directly in the FdManager. */ + int timeout() const; ///< Retrieve current timeout value - void timeout(int t); - int timeout() const; + bool empty() const; ///< \c true, if no files are registered. protected: private: + /// Internal: Disk file event struct FileEvent : public detail::FdTask<0, FileEvent>, public detail::FdTask<1, FileEvent> diff --git a/Scheduler/FileDispatcher.test.cc b/Scheduler/FileDispatcher.test.cc index 045f125..f923616 100644 --- a/Scheduler/FileDispatcher.test.cc +++ b/Scheduler/FileDispatcher.test.cc @@ -66,7 +66,8 @@ BOOST_AUTO_UNIT_TEST(fileDispatcher) int fd (open("/dev/null", O_RDWR)); senf::ClockService::clock_type t (senf::ClockService::now()); - SENF_CHECK_NO_THROW( dispatcher.add(fd, &handler, senf::scheduler::FileDispatcher::EV_READ) ); + SENF_CHECK_NO_THROW( dispatcher.add("testHandler", fd, &handler, + senf::scheduler::FileDispatcher::EV_READ) ); SENF_CHECK_NO_THROW( manager.processOnce() ); SENF_CHECK_NO_THROW( dispatcher.prepareRun() ); SENF_CHECK_NO_THROW( runner.run() ); diff --git a/Scheduler/Mainpage.dox b/Scheduler/Mainpage.dox index e2d5f54..40f3f0e 100644 --- a/Scheduler/Mainpage.dox +++ b/Scheduler/Mainpage.dox @@ -20,27 +20,112 @@ // Free Software Foundation, Inc., // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -namespace senf { - /** \mainpage The SENF Scheduler Library - The Scheduler library provides a simple yet flexible abstraction of the standard asynchronous - UNIX mainloop utilizing \c select or \c poll. The Scheduler library is based on the highly - efficient (but linux specific) \c epoll() system call. + The %Scheduler Library provides a single-threaded application event-loop multiplexing multiple + event sources. + + \autotoc - The library provides - \li the ClockService as a reliable high-resolution highly accurate monotonous time source - \li a central \ref Scheduler %singleton and - \li \ref ReadHelper and \ref WriteHelper templates to simplify common tasks. + \section scheduler_scheduler The Scheduler - The Scheduler supports several types of scheduling activites: - \li Arbitrary file descriptors (however, local disk file-handles are not guaranteed - non-blocking) + The main interface is the senf::Scheduler class. This provides support for several types of + events: + \li File descriptors \li Timers - \li UNIX Signals + \li UNIX signals + + \see senf::Scheduler + + + \section scheduler_clockservice The ClockService + + To support precise event timing, the senf::ClockService class implements a reliable monotonous + time source. It is based on the high precision POSIX clock and adds support for reliable + conversion between an abstract clock type and absolute date/time + + \see senf::ClockService + + + \section scheduler_helpers Miscellaneous helpers + + To ease the use of the Scheduler there are some additional helpers managing callbacks and + registrations. + + \li senf::ReadHelper reads data from an arbitrary file descritor until a use specified condition + is met (e.g. number of chars read or a specific character sequence is found in the input). + \li senf::WriteHelper writes data to an arbitrary file descriptor until all provided data has + been written. + \li senf::SchedulerBinding is an RAII class which manages the registration of a file descriptor + with the %scheduler. + \li senf::SchedulerTimer is an RAII class which manages a timer registration. + + + \section scheduler_i Implementation + + senf::Scheduler is only a wrapper around the real implementation. The real implementation is now + based on a modular dispatcher architecture + + \see \ref scheduler_implementation */ -} +/** \page scheduler_implementation The Scheduler Implementation + + The implentation architecture now is based on a set of dispatchers, one for each type of + event. + + \autotoc + + \section scheduler_i_overview Overview + + The %scheduler utilizes the following components + + \li There is a dispatcher for each event type. This dispatcher manages the event specific + registration and unregistration. The dispatcher is owns the event (and task) objects. + + \li Every registered event is represented by an event specific event class instance. + + \li The Dispatcher ultimately registeres with the senf::scheduler::FdManager. Since the + event-loop is based on epoll() (it could easily be changed to be based on select() or + poll()), all events must ultimately be represented by some type of file descriptor (not + necessarily a \e different file descriptor for each event). + + \li The Dispatcher registeres all callbacks as tasks with the runner + (senf::scheduler::FIFORunner). + + \li The senf::scheduler::FdManager uses senf::scheduler::Poller to access the low-level epoll() + API. + + All these classes are \e not singletons. They are all instantiatied by the senf::Scheduler + singleton. + + + \section scheduler_i_dispatchers Dispatchers + + There is one dispatcher for each event type + + \li senf::scheduler::FdDispatcher manages poll-able file descriptors. This does \e not include + real files. + \li senf::scheduler::FileDispatcher manages disk files + \li senf::scheduler::TimerDispatcher manages timers + \li senf::scheduler::SignalDispatcher manages UNIX signals + + At the moment, each dispatcher has a specific API and the integration into the main-loop is not + standardized for performance reasons. + + The Dispatcher owns all relevant data, the other classese (senf::scheduler::FIFORunner, + senf::scheduler::FdManager) only hold pointers to the data owned by the dispatcher. + + + \section scheduler_i_mainloop The main loop + + The application mainloop is part of senf::Scheduler. It is constructed by calling the correct + members of all these classes repeatedly in the correct order: + \li First dispatchers are set up + \li then the senf::scheduler::FdManager is called to wait for an event + \li After cleaning up the dispatchers, + \li the senf::scheduler::FIFORunner is called to executed all now runnable tasks. + */ // Local Variables: @@ -51,4 +136,5 @@ namespace senf { // ispell-local-dictionary: "american" // mode: flyspell // mode: auto-fill +// compile-command: "scons -U doc" // End: diff --git a/Scheduler/Poller.hh b/Scheduler/Poller.hh index 0dc5504..0bedf72 100644 --- a/Scheduler/Poller.hh +++ b/Scheduler/Poller.hh @@ -38,7 +38,13 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Epoll abstraction + + This class provides a more convenient interface to the epoll() API. File descriptors are + registered with pointers to a parameterized event type. After waiting for an event, the + Poller allows to iterate over the event instances for all posted events. + + \tparam Value Event type */ template class Poller @@ -75,12 +81,17 @@ namespace scheduler { ///@} /////////////////////////////////////////////////////////////////////////// - bool set(int fd, int events, Value * data); - void remove(int fd); - range wait(); + bool set(int fd, int events, Value * data); ///< Set file descriptor event data and mask + /**< The Poller does \e not own \a data. The value is owned + by some external entity (the dispatcher to be more + precise). */ + void remove(int fd); ///< Remove file descriptor + range wait(); ///< Wait for one event + /**< \returns a range of iterators which iterate over the + data values registered with the event */ - void timeout(int t); - int timeout() const; + void timeout(int t); ///< Set event timeout to \a t milliseconds + int timeout() const; ///< Current event timeout private: int epollFd_; diff --git a/Scheduler/ReadHelper.ct b/Scheduler/ReadHelper.ct index 981d970..42d068a 100644 --- a/Scheduler/ReadHelper.ct +++ b/Scheduler/ReadHelper.ct @@ -57,8 +57,7 @@ prefix_ void senf::ReadHelper::revoke() template prefix_ void -senf::ReadHelper::dispatchProcess(ptr helper, Handle handle, - senf::Scheduler::EventId event) +senf::ReadHelper::dispatchProcess(ptr helper, Handle handle, int event) { // since we have a 'ptr' argument, the instance cannot be deleted // before this method returns @@ -66,8 +65,7 @@ senf::ReadHelper::dispatchProcess(ptr helper, Handle handle, } template -prefix_ void senf::ReadHelper::process(Handle handle, - senf::Scheduler::EventId event) +prefix_ void senf::ReadHelper::process(Handle handle,int event) { try { if (event != senf::Scheduler::EV_READ) diff --git a/Scheduler/ReadHelper.hh b/Scheduler/ReadHelper.hh index 34246e2..d43afd7 100644 --- a/Scheduler/ReadHelper.hh +++ b/Scheduler/ReadHelper.hh @@ -131,8 +131,8 @@ namespace senf { ReadHelper(Handle handle, unsigned maxSize, InternalPredicate * predicate, Callback cb); - static void dispatchProcess(ptr helper, Handle handle, senf::Scheduler::EventId event); - void process(Handle handle, senf::Scheduler::EventId event); + static void dispatchProcess(ptr helper, Handle handle, int event); + void process(Handle handle, int event); void done(); Handle handle_; diff --git a/Scheduler/Scheduler.cc b/Scheduler/Scheduler.cc index 3d61946..02364a4 100644 --- a/Scheduler/Scheduler.cc +++ b/Scheduler/Scheduler.cc @@ -31,310 +31,27 @@ threads) */ -// Here a basic concept of how to add signal support to the scheduler: -// -// ... no, I had overlooked one race condition. So back to the signal-pipe approach ... - #include "Scheduler.hh" //#include "Scheduler.ih" // Custom includes -#include "../Utils/senfassert.hh" -#include -#include -#include -#include -#include "../Utils/Exception.hh" -#include "../Utils/Backtrace.hh" - -static const int EPollInitialSize = 16; #define prefix_ ///////////////////////////////cc.p//////////////////////////////////////// -prefix_ senf::Scheduler::Scheduler() - : files_(0), timerIdCounter_(0), epollFd_ (epoll_create(EPollInitialSize)), terminate_(false), - eventTime_(0), eventEarly_(ClockService::milliseconds(11)), eventAdjust_(0) -{ - if (epollFd_<0) - SENF_THROW_SYSTEM_EXCEPTION("::epoll_create()"); - - if (::pipe(sigpipe_) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::pipe()"); - - int flags (::fcntl(sigpipe_[1],F_GETFL)); - if (flags < 0) - SENF_THROW_SYSTEM_EXCEPTION("::fcntl(F_GETFL)"); - flags |= O_NONBLOCK; - if (::fcntl(sigpipe_[1], F_SETFL, flags) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::fcntl(F_SETFL)"); - - ::epoll_event ev; - ::memset(&ev, 0, sizeof(ev)); - ev.events = EV_READ; - ev.data.fd = sigpipe_[0]; - if (::epoll_ctl(epollFd_, EPOLL_CTL_ADD, sigpipe_[0], &ev) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::epoll_ctl(EPOLL_CTL_ADD)"); -} - -prefix_ void senf::Scheduler::registerSignal(unsigned signal, SimpleCallback const & cb) -{ - ::sigset_t sig; - ::sigemptyset(&sig); - if (::sigaddset(&sig, signal) < 0) - throw InvalidSignalNumberException(); - ::sigprocmask(SIG_BLOCK, &sig, 0); - ::sigaddset(&sigset_, signal); - if (sigHandlers_.size() <= signal) - sigHandlers_.resize(signal+1); - sigHandlers_[signal] = cb; - - registerSigHandlers(); -} - -prefix_ void senf::Scheduler::unregisterSignal(unsigned signal) -{ - if (::sigdelset(&sigset_, signal) < 0) - throw InvalidSignalNumberException(); - sigHandlers_[signal] = 0; - ::signal(signal, SIG_DFL); - registerSigHandlers(); -} - -prefix_ void senf::Scheduler::do_add(int fd, FdCallback const & cb, int eventMask) -{ - if (eventMask == 0) - return; - - FdTable::iterator i (fdTable_.find(fd)); - int action (EPOLL_CTL_MOD); - if (i == fdTable_.end()) { - action = EPOLL_CTL_ADD; - i = fdTable_.insert(std::make_pair(fd, EventSpec())).first; - } - else if (i->second.epollMask() == 0) { - action = EPOLL_CTL_ADD; - fdErase_.erase( std::remove(fdErase_.begin(), fdErase_.end(), unsigned(fd)), - fdErase_.end() ); - } - - if (eventMask & EV_READ) i->second.cb_read = cb; - if (eventMask & EV_PRIO) i->second.cb_prio = cb; - if (eventMask & EV_WRITE) i->second.cb_write = cb; - - epoll_event ev; - memset(&ev,0,sizeof(ev)); - ev.events = i->second.epollMask(); - ev.data.fd = fd; - - for (;;) { - if ( (!i->second.file) && (epoll_ctl(epollFd_, action, fd, &ev) < 0) ) { - switch (errno) { - case EPERM : - // Argh ... epoll does not support ordinary files :-( :-( - i->second.file = true; - ++ files_; - return; - case ENOENT : - action = EPOLL_CTL_ADD; - break; - default: - SENF_THROW_SYSTEM_EXCEPTION("::epoll_ctl()"); - } - } - else - return; - } -} - -prefix_ void senf::Scheduler::do_remove(int fd, int eventMask) -{ - if (eventMask == 0) - return; - - FdTable::iterator i (fdTable_.find(fd)); - if (i == fdTable_.end()) - return; - - if (eventMask & EV_READ) i->second.cb_read = 0; - if (eventMask & EV_PRIO) i->second.cb_prio = 0; - if (eventMask & EV_WRITE) i->second.cb_write = 0; - - epoll_event ev; - memset(&ev,0,sizeof(ev)); - ev.events = i->second.epollMask(); - ev.data.fd = fd; - - int action (EPOLL_CTL_MOD); - bool file (i->second.file); - if (ev.events==0) { - action = EPOLL_CTL_DEL; - fdErase_.push_back(fd); - } - - if (! file && epoll_ctl(epollFd_, action, fd, &ev) < 0 && errno != ENOENT) - SENF_THROW_SYSTEM_EXCEPTION("::epoll_ctl()"); - if (file) - -- files_; -} - -prefix_ void senf::Scheduler::registerSigHandlers() -{ - for (unsigned signal (1); signal < sigHandlers_.size(); ++signal) { - if (sigHandlers_[signal]) { - struct ::sigaction sa; - sa.sa_sigaction = & Scheduler::sigHandler; - sa.sa_mask = sigset_; - sa.sa_flags = SA_SIGINFO; - if (signal == SIGCHLD) - sa.sa_flags |= SA_NOCLDSTOP; - if (::sigaction(signal, &sa, 0) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::sigaction()"); - } - } -} - -prefix_ void senf::Scheduler::sigHandler(int signal, ::siginfo_t * siginfo, void *) -{ - // This is a bit unsafe. Better write single bytes and place the siginfo into an explicit - // queue. Since signals are only unblocked during epoll_wait, we even wouldn't need to - // synchronize access to that queue any further. - - ::write(instance().sigpipe_[1], siginfo, sizeof(*siginfo)); - - // We ignore errors. The file handle is set to non-blocking IO. If any failure occurs (pipe - // full), the signal will be dropped. That's like kernel signal handling which may also drop - // signals. -} - -prefix_ int senf::Scheduler::EventSpec::epollMask() - const -{ - int mask (0); - if (cb_read) mask |= EPOLLIN; - if (cb_prio) mask |= EPOLLPRI; - if (cb_write) mask |= EPOLLOUT; - return mask; -} - prefix_ void senf::Scheduler::process() { terminate_ = false; - eventTime_ = ClockService::now(); - while (! terminate_) { - - // Since a callback may have disabled further timers, we need to check for canceled timeouts - // again. - - while (! timerQueue_.empty()) { - TimerMap::iterator i (timerQueue_.top()); - if (! i->second.canceled) - break; - timerMap_.erase(i); - timerQueue_.pop(); - } - - for (FdEraseList::iterator i (fdErase_.begin()); i != fdErase_.end(); ++i) - fdTable_.erase(*i); - fdErase_.clear(); - - int timeout (-1); - if (files_ > 0) - timeout = 0; - else { - if (timerQueue_.empty()) { - if (fdTable_.empty()) - break; - } - else { - ClockService::clock_type delta ( - (timerQueue_.top()->second.timeout - eventTime_ + eventAdjust_)/1000000UL); - timeout = delta < 0 ? 0 : delta; - } - } - - ///\todo Handle more than one epoll_event per call - struct epoll_event ev; - - if (::sigprocmask(SIG_UNBLOCK, &sigset_, 0) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::sigprocmask(SIG_UNBLOCK)"); - int events (::epoll_wait(epollFd_, &ev, 1, timeout)); - if (::sigprocmask(SIG_BLOCK, &sigset_, 0) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::sigprocmask(SIG_BLOCK)"); - - if (events<0) - if (errno != EINTR) - SENF_THROW_SYSTEM_EXCEPTION("::epoll_wait()"); - - eventTime_ = ClockService::now(); - - // We always run timeout handlers. This is important, even if a file-descriptor is signaled - // since some descriptors (e.g. real files) will *always* be ready and we still may want to - // handle timers. Time handlers are run before file events to not delay them unnecessarily. - - while (! timerQueue_.empty()) { - TimerMap::iterator i (timerQueue_.top()); - if (i->second.canceled) - ; - else if (i->second.timeout <= eventTime_ + eventEarly_) - i->second.cb(); - else - break; - timerQueue_.pop(); - timerMap_.erase(i); - } - - // Check the signal queue - if (events > 0 && ev.data.fd == sigpipe_[0]) { - ::siginfo_t siginfo; - if (::read(sigpipe_[0], &siginfo, sizeof(siginfo)) < int(sizeof(siginfo))) { - // We ignore truncated records which may only occur if the signal - // queue became filled up - SENF_LOG((senf::log::IMPORTANT)("Truncated signal record!")); - continue; - } - if (siginfo.si_signo < int(sigHandlers_.size()) && sigHandlers_[siginfo.si_signo]) - sigHandlers_[siginfo.si_signo](); - continue; - } - - for (FdTable::iterator i = fdTable_.begin(); i != fdTable_.end(); ++i) { - EventSpec & spec (i->second); - - if (! (spec.file || (events > 0 && i->first == ev.data.fd))) - continue; - - unsigned extraFlags (0); - unsigned mask (spec.file ? spec.epollMask() : ev.events); - - if (mask & EPOLLHUP) extraFlags |= EV_HUP; - if (mask & EPOLLERR) extraFlags |= EV_ERR; - - if (mask & EPOLLIN) { - SENF_ASSERT(spec.cb_read); - spec.cb_read(EventId(EV_READ | extraFlags)); - } - else if (mask & EPOLLPRI) { - SENF_ASSERT(spec.cb_prio); - spec.cb_prio(EventId(EV_PRIO | extraFlags)); - } - else if (mask & EPOLLOUT) { - SENF_ASSERT(spec.cb_write); - spec.cb_write(EventId(EV_WRITE | extraFlags)); - } - else { - // This branch is only taken, if HUP or ERR is signaled but none of IN/OUT/PRI. - // In this case we will signal all registered callbacks. The callbacks must be - // prepared to be called multiple times if they are registered to more than - // one event. - if (spec.cb_write) - spec.cb_write(EventId(extraFlags)); - if (spec.cb_prio) - spec.cb_prio(EventId(extraFlags)); - if (spec.cb_read) - spec.cb_read(EventId(extraFlags)); - } - } + while(! terminate_ && ! (fdDispatcher_.empty() && + timerDispatcher_.empty() && + fileDispatcher_.empty())) { + signalDispatcher_.unblockSignals(); + timerDispatcher_.unblockSignals(); + manager_.processOnce(); + timerDispatcher_.blockSignals(); + signalDispatcher_.blockSignals(); + fileDispatcher_.prepareRun(); + runner_.run(); } } diff --git a/Scheduler/Scheduler.cci b/Scheduler/Scheduler.cci index f436274..f8a565e 100644 --- a/Scheduler/Scheduler.cci +++ b/Scheduler/Scheduler.cci @@ -27,54 +27,100 @@ //#include "Scheduler.ih" // Custom includes +#include #define prefix_ inline ///////////////////////////////cci.p/////////////////////////////////////// +// private members + +prefix_ void senf::Scheduler::do_add(int fd, FdCallback const & cb, int eventMask) +{ + do_add((boost::format("") % fd).str(), fd, cb, eventMask); +} + +prefix_ void senf::Scheduler::do_add(std::string const & name, int fd, FdCallback const & cb, + int eventMask) +{ + if (! fdDispatcher_.add(name, fd, cb, eventMask)) + fileDispatcher_.add(name, fd, cb, eventMask); +} + +prefix_ void senf::Scheduler::do_remove(int fd, int eventMask) +{ + // We don't know, where the descriptor is registered. However, this is no problem since removing + // a non-registered fd is a no-opp + fdDispatcher_.remove(fd, eventMask); + fileDispatcher_.remove(fd, eventMask); +} + +// public members + prefix_ senf::Scheduler::Scheduler & senf::Scheduler::instance() { static Scheduler instance; return instance; } -prefix_ unsigned senf::Scheduler::timeout(ClockService::clock_type timeout, - SimpleCallback const & cb) +prefix_ int senf::retrieve_filehandle(int fd) +{ + return fd; +} + +prefix_ senf::Scheduler::timer_id senf::Scheduler::timeout(ClockService::clock_type timeout, + SimpleCallback const & cb) +{ + return timerDispatcher_.add("", timeout, cb); +} + +prefix_ senf::Scheduler::timer_id senf::Scheduler::timeout(std::string const & name, + ClockService::clock_type timeout, + SimpleCallback const & cb) { - ++ timerIdCounter_; - TimerMap::iterator i ( - timerMap_.insert(std::make_pair(timerIdCounter_, - TimerSpec(timeout,cb,timerIdCounter_))).first); - timerQueue_.push(i); - return timerIdCounter_; + return timerDispatcher_.add(name, timeout, cb); } -prefix_ void senf::Scheduler::cancelTimeout(unsigned id) +prefix_ void senf::Scheduler::cancelTimeout(timer_id id) { - TimerMap::iterator i (timerMap_.find(id)); - if (i != timerMap_.end()) - i->second.canceled = true; + timerDispatcher_.remove(id); } prefix_ senf::ClockService::clock_type senf::Scheduler::timeoutEarly() const { - return eventEarly_; + SENF_LOG( (senf::log::IMPORTANT) + ("timeoutEarly() is deprecated and a no-op. It will be removed") ); + return 0; } prefix_ void senf::Scheduler::timeoutEarly(ClockService::clock_type v) { - eventEarly_ = v; + SENF_LOG( (senf::log::IMPORTANT) + ("timeoutEarly() is deprecated and a no-op. It will be removed") ); } prefix_ senf::ClockService::clock_type senf::Scheduler::timeoutAdjust() const { - return eventAdjust_; + SENF_LOG( (senf::log::IMPORTANT) + ("timeoutAdjust() is deprecated and a no-op. It will be removed") ); + return 0; } prefix_ void senf::Scheduler::timeoutAdjust(ClockService::clock_type v) { - eventAdjust_ = v; + SENF_LOG( (senf::log::IMPORTANT) + ("timeoutAdjust() is deprecated and a no-op. It will be removed") ); +} + +prefix_ void senf::Scheduler::registerSignal(unsigned signal, SignalCallback const & cb) +{ + signalDispatcher_.add(signal, cb); +} + +prefix_ void senf::Scheduler::unregisterSignal(unsigned signal) +{ + signalDispatcher_.remove(signal); } prefix_ void senf::Scheduler::terminate() @@ -85,19 +131,22 @@ prefix_ void senf::Scheduler::terminate() prefix_ senf::ClockService::clock_type senf::Scheduler::eventTime() const { - return eventTime_; + return manager_.eventTime(); } -prefix_ int senf::retrieve_filehandle(int fd) +prefix_ unsigned senf::Scheduler::hangCount() + const { - return fd; + return runner_.hangCount(); } -prefix_ senf::Scheduler::TimerSpecCompare::result_type -senf::Scheduler::TimerSpecCompare::operator()(first_argument_type a, second_argument_type b) -{ - return a->second < b->second; -} +prefix_ senf::Scheduler::Scheduler() + : terminate_ (false), + fdDispatcher_ (manager_, runner_), + timerDispatcher_ (manager_, runner_), + signalDispatcher_ (manager_, runner_), + fileDispatcher_ (manager_, runner_) +{} ///////////////////////////////cci.e/////////////////////////////////////// #undef prefix_ diff --git a/Scheduler/Scheduler.cti b/Scheduler/Scheduler.cti index 1404615..aff6271 100644 --- a/Scheduler/Scheduler.cti +++ b/Scheduler/Scheduler.cti @@ -40,6 +40,13 @@ prefix_ void senf::Scheduler::add(Handle const & handle, FdCallback const & cb, } template +prefix_ void senf::Scheduler::add(std::string const & name, Handle const & handle, + FdCallback const & cb, int eventMask) +{ + do_add(name, retrieve_filehandle(handle),cb,eventMask); +} + +template prefix_ void senf::Scheduler::remove(Handle const & handle, int eventMask) { // retrieve_filehandle is found via ADL diff --git a/Scheduler/Scheduler.hh b/Scheduler/Scheduler.hh index 695e9b3..89e9d23 100644 --- a/Scheduler/Scheduler.hh +++ b/Scheduler/Scheduler.hh @@ -28,14 +28,11 @@ #define HH_Scheduler_ 1 // Custom includes -#include -#include -#include -#include -#include -#include -#include -#include "ClockService.hh" +#include "../Utils/Logger/SenfLog.hh" +#include "FdDispatcher.hh" +#include "TimerDispatcher.hh" +#include "SignalDispatcher.hh" +#include "FileDispatcher.hh" #include "../Utils/Logger/SenfLog.hh" //#include "scheduler.mpp" @@ -44,10 +41,10 @@ /** \brief SENF Project namespace */ namespace senf { - /** \brief Singleton class to manage the event loop + /** \brief Visible scheduler interface - The %scheduler singleton manages the central event loop. It manages and dispatches all types - of events managed by the scheduler library: + The %scheduler singleton manages access to the %scheduler library. It provides access to + several event dispatchers: \li File descriptor notifications \li Timeouts \li UNIX Signals @@ -87,7 +84,10 @@ namespace senf { // e.g. in Foo::Foo() constructor: Scheduler::instance().add(handle_, senf::membind(&Foo::callback, this)), EV_READ) \endcode - + + The handler can also be identified by an arbitrary, user specified name. This name is used + in error messages to identify the failing handler. + \section sched_fd Registering file descriptors @@ -125,20 +125,9 @@ namespace senf { Scheduler::instance().cancelTimeout(id); \endcode Timing is based on the ClockService, which provides a high resolution and strictly - monotonous time source. Registering a timeout will fire the callback when the target time is - reached. The timer may be canceled by passing the returned \a id to cancelTimeout(). - - There are two parameters which adjust the exact: \a timeoutEarly and \a timeoutAdjust. \a - timeoutEarly is the time, a callback may be called before the deadline time is - reached. Setting this value below the scheduling granularity of the kernel will have the - %scheduler go into a busy wait (that is, an endless loop consuming 100% of CPU - recources) until the deadline time is reached! This is seldom desired. The default setting - of 11ms is adequate in most cases (it's slightly above the lowest linux scheduling - granularity). - - The other timeout scheduling parameter is \a timeoutAdjust. This value will be added to the - timeout value before calculating the next delay value thereby compensating for \a - timeoutEarly. By default, this value is set to 0 but may be changed if needed. + monotonous time source which again is based on POSIX timers. Registering a timeout will fire + the callback when the target time is reached. The timer may be canceled by passing the + returned \a id to cancelTimeout(). \section sched_signals Registering POSIX/UNIX signals @@ -155,13 +144,10 @@ namespace senf { A registered signal does \e not count as 'something to do'. It is therefore not possible to wait for signals \e only. - \todo Fix EventId parameter (probably to int) to allow |-ing without casting ... - + \todo Change the Scheduler API to use RAII. Additionally, this will remove all dynamic + memory allocations from the scheduler. \todo Fix the file support to use threads (?) fork (?) and a pipe so it works reliably even over e.g. NFS. - - \todo Add a check in the alarm callback which is already called every x seconds to check, - that a single callback is not blocking. */ class Scheduler : boost::noncopyable @@ -181,35 +167,31 @@ namespace senf { \li Error flags. These additional flags may be passed to a handler to pass an error condition to the handler. */ - enum EventId { - EV_NONE = 0 /**< No event */ - , EV_READ = 1 /**< File descriptor is readable */ - , EV_PRIO = 2 /**< File descriptor has OOB data */ - , EV_WRITE = 4 /**< File descriptor is writable */ - , EV_ALL = 7 /**< Used to register all events at once (read/prio/write) */ - , EV_HUP = 8 /**< Hangup condition on file handle */ - , EV_ERR = 16 /**< Error condition on file handle */ + enum EventId { + EV_NONE = 0 /**< No event */ + , EV_READ = scheduler::FdManager::EV_READ /**< File descriptor is readable */ + , EV_PRIO = scheduler::FdManager::EV_PRIO /**< File descriptor has OOB data */ + , EV_WRITE = scheduler::FdManager::EV_WRITE /**< File descriptor is writable */ + , EV_ALL = scheduler::FdManager::EV_READ + | scheduler::FdManager::EV_PRIO + | scheduler::FdManager::EV_WRITE /**< Used to register all events at once + (read/prio/write) */ + , EV_HUP = scheduler::FdManager::EV_HUP /**< Hangup condition on file handle */ + , EV_ERR = scheduler::FdManager::EV_ERR /**< Error condition on file handle */ }; - /** \brief Template typedef for Callback type - - This is a template typedef (which does not exist in C++) that is, a template class whose - sole member is a typedef symbol defining the callback type given the handle type. - - The Callback is any callable object taking a \c Handle and an \c EventId as argument. - \code - template - struct GenericCallback { - typedef boost::function::param_type, - EventId) > Callback; - }; - \endcode - */ - typedef boost::function FdCallback; + /** \brief Callback type for file descriptor events */ + typedef boost::function FdCallback; /** \brief Callback type for timer events */ typedef boost::function SimpleCallback; + /** \brief Callback type for signal events */ + typedef boost::function SignalCallback; + + /** \brief Timer id type */ + typedef scheduler::TimerDispatcher::timer_id timer_id; + /////////////////////////////////////////////////////////////////////////// ///\name Structors and default members ///@{ @@ -225,11 +207,6 @@ namespace senf { This static member is used to access the singleton instance. This member is save to return a correctly initialized %scheduler instance even if called at global construction time - - \implementation This static member just defines the %scheduler as a static method - variable. The C++ standard then provides above guarantee. The instance will be - initialized the first time, the code flow passes the variable declaration found in - the instance() body. */ static Scheduler & instance(); @@ -240,20 +217,29 @@ namespace senf { ///\{ template - void add(Handle const & handle, FdCallback const & cb, - int eventMask = EV_ALL); ///< Add file handle event callback + void add(std::string const & name, Handle const & handle, FdCallback const & cb, + int eventMask = EV_ALL); ///< Add file handle event callback /**< add() will add a callback to the %scheduler. The callback will be called for the given type of event on the given arbitrary file-descriptor or handle-like object. If there already is a Callback registered for one of the events requested, the new handler will replace the old one. + \param[in] name descriptive name to identify the + callback. \param[in] handle file descriptor or handle providing the Handle interface defined above. \param[in] cb callback \param[in] eventMask arbitrary combination via '|' operator of \ref senf::Scheduler::EventId "EventId" designators. */ + + template + void add(Handle const & handle, FdCallback const & cb, + int eventMask = EV_ALL); ///< Add file handle event callback + /**< \see add() */ + + template void remove(Handle const & handle, int eventMask = EV_ALL); ///< Remove event callback /**< remove() will remove any callback registered for any of @@ -264,36 +250,42 @@ namespace senf { \param[in] eventMask arbitrary combination via '|' operator of \ref senf::Scheduler::EventId "EventId" designators. */ + ///\} ///\name Timeouts ///\{ - unsigned timeout(ClockService::clock_type timeout, SimpleCallback const & cb); + timer_id timeout(std::string const & name, ClockService::clock_type timeout, + SimpleCallback const & cb); ///< Add timeout event /**< \returns timer id + \param[in] name descriptive name to identify the + callback. \param[in] timeout timeout in nanoseconds \param[in] cb callback to call after \a timeout milliseconds */ - void cancelTimeout(unsigned id); ///< Cancel timeout \a id + timer_id timeout(ClockService::clock_type timeout, SimpleCallback const & cb); + ///< Add timeout event + /**< \see timeout() */ + + void cancelTimeout(timer_id id); ///< Cancel timeout \a id +#ifndef DOXYGEN ClockService::clock_type timeoutEarly() const; - ///< Fetch the \a timeoutEarly parameter void timeoutEarly(ClockService::clock_type v); - ///< Set the \a timeoutEarly parameter ClockService::clock_type timeoutAdjust() const; - ///< Fetch the \a timeoutAdjust parameter void timeoutAdjust(ClockService::clock_type v); - ///< Set the \a timeoutAdjust parameter +#endif ///\} ///\name Signal handlers ///\{ - void registerSignal(unsigned signal, SimpleCallback const & cb); + void registerSignal(unsigned signal, SignalCallback const & cb); ///< Add signal handler /**< \param[in] signal signal number to register handler for \param[in] cb callback to call whenever \a signal is @@ -302,12 +294,6 @@ namespace senf { void unregisterSignal(unsigned signal); ///< Remove signal handler for \a signal - /// The signal number passed to registerSignal or unregisterSignal is invalid - struct InvalidSignalNumberException : public senf::Exception - { InvalidSignalNumberException() - : senf::Exception("senf::Scheduler::InvalidSignalNumberException"){} }; - - ///\} void process(); ///< Event handler main loop @@ -328,92 +314,26 @@ namespace senf { delivered \e not the time it should have been delivered (in the case of timers). */ + unsigned hangCount() const; + protected: private: Scheduler(); void do_add(int fd, FdCallback const & cb, int eventMask = EV_ALL); - void do_remove(int fd, int eventMask = EV_ALL); - - void registerSigHandlers(); - static void sigHandler(int signal, ::siginfo_t * siginfo, void *); - -# ifndef DOXYGEN - - /** \brief Descriptor event specification - \internal */ - struct EventSpec - { - FdCallback cb_read; - FdCallback cb_prio; - FdCallback cb_write; - - EventSpec() : file(false) {} - - int epollMask() const; - - bool file; - }; - - /** \brief Timer event specification - \internal */ - struct TimerSpec - { - TimerSpec() : timeout(), cb() {} - TimerSpec(ClockService::clock_type timeout_, SimpleCallback cb_, unsigned id_) - : timeout(timeout_), cb(cb_), id(id_), canceled(false) {} - - bool operator< (TimerSpec const & other) const - { return timeout > other.timeout; } - - ClockService::clock_type timeout; - SimpleCallback cb; - unsigned id; - bool canceled; - }; + void do_add(std::string const & name, int fd, FdCallback const & cb, + int eventMask = EV_ALL); + void do_remove(int fd, int eventMask); -# endif - - typedef std::map FdTable; - typedef std::map TimerMap; // sorted by id - typedef std::vector FdEraseList; - -# ifndef DOXYGEN - - struct TimerSpecCompare - { - typedef TimerMap::iterator first_argument_type; - typedef TimerMap::iterator second_argument_type; - typedef bool result_type; - - result_type operator()(first_argument_type a, second_argument_type b); - }; - -# endif - - typedef std::priority_queue, - TimerSpecCompare> TimerQueue; // sorted by time - - typedef std::vector SigHandlers; - - FdTable fdTable_; - FdEraseList fdErase_; - unsigned files_; - - unsigned timerIdCounter_; - TimerQueue timerQueue_; - TimerMap timerMap_; - - SigHandlers sigHandlers_; - ::sigset_t sigset_; - int sigpipe_[2]; - - int epollFd_; bool terminate_; - ClockService::clock_type eventTime_; - ClockService::clock_type eventEarly_; - ClockService::clock_type eventAdjust_; + scheduler::FdManager manager_; + scheduler::FIFORunner runner_; + + scheduler::FdDispatcher fdDispatcher_; + scheduler::TimerDispatcher timerDispatcher_; + scheduler::SignalDispatcher signalDispatcher_; + scheduler::FileDispatcher fileDispatcher_; }; /** \brief Default file descriptor accessor diff --git a/Scheduler/Scheduler.test.cc b/Scheduler/Scheduler.test.cc index 5da6b8c..0501762 100644 --- a/Scheduler/Scheduler.test.cc +++ b/Scheduler/Scheduler.test.cc @@ -141,7 +141,7 @@ namespace { int size; int event; - void callback(int fd, Scheduler::EventId ev) + void callback(int fd, int ev) { event = ev; switch (event & Scheduler::EV_ALL) { @@ -160,8 +160,10 @@ namespace { Scheduler::instance().terminate(); } + bool timeoutCalled = false; void timeout() { + timeoutCalled = true; Scheduler::instance().terminate(); } @@ -177,7 +179,7 @@ namespace { return handle.fd_; } - void handleCallback(HandleWrapper const & handle, Scheduler::EventId event) + void handleCallback(HandleWrapper const & handle, int event) { if (handle.tag_ != "TheTag") return; @@ -191,12 +193,12 @@ namespace { ClockService::clock_type sigtime (0); - void sigusr() + void sigusr(siginfo_t const &) { sigtime = ClockService::now(); Scheduler::instance().terminate(); } - + void delay(unsigned long milliseconds) { struct timespec ts; @@ -204,9 +206,16 @@ namespace { ts.tv_nsec = (milliseconds % 1000) * 1000000; while (nanosleep(&ts,&ts) < 0 && errno == EINTR) ; } + + void blockingHandler() + { + delay(1200); + Scheduler::instance().terminate(); + } + } -BOOST_AUTO_UNIT_TEST(scheduler) +BOOST_AUTO_UNIT_TEST(testScheduler) { int pid = start_server(); BOOST_REQUIRE (pid); @@ -239,15 +248,26 @@ BOOST_AUTO_UNIT_TEST(scheduler) buffer[size]=0; BOOST_CHECK_EQUAL( buffer, "READ" ); + event = Scheduler::EV_NONE; BOOST_CHECK_NO_THROW( Scheduler::instance().timeout( ClockService::now()+ClockService::milliseconds(200),&timeout) ); BOOST_CHECK_NO_THROW( Scheduler::instance().timeout( ClockService::now()+ClockService::milliseconds(400),&timeout) ); ClockService::clock_type t (ClockService::now()); BOOST_CHECK_NO_THROW( Scheduler::instance().process() ); - BOOST_CHECK_PREDICATE( is_close, (ClockService::now()) (t+ClockService::milliseconds(200)) ); + BOOST_CHECK_PREDICATE( is_close, (ClockService::now()-t) (ClockService::milliseconds(200)) ); + BOOST_CHECK( timeoutCalled ); + BOOST_CHECK_EQUAL( event, Scheduler::EV_NONE ); + BOOST_CHECK_PREDICATE( is_close, (ClockService::now()) (Scheduler::instance().eventTime()) ); + timeoutCalled = false; + BOOST_CHECK_NO_THROW( Scheduler::instance().process() ); + BOOST_CHECK_PREDICATE( is_close, (ClockService::now()-t) (ClockService::milliseconds(400)) ); + BOOST_CHECK( timeoutCalled ); + BOOST_CHECK_EQUAL( event, Scheduler::EV_NONE ); + + BOOST_CHECK_NO_THROW( Scheduler::instance().timeout(ClockService::now(), &blockingHandler) ); BOOST_CHECK_NO_THROW( Scheduler::instance().process() ); - BOOST_CHECK_PREDICATE( is_close, (ClockService::now()) (t+ClockService::milliseconds(400)) ); + BOOST_CHECK_EQUAL( Scheduler::instance().hangCount(), 1u ); HandleWrapper handle(sock,"TheTag"); BOOST_CHECK_NO_THROW( Scheduler::instance().add(handle, diff --git a/Scheduler/SignalDispatcher.cc b/Scheduler/SignalDispatcher.cc index 490c2c2..69d72bc 100644 --- a/Scheduler/SignalDispatcher.cc +++ b/Scheduler/SignalDispatcher.cc @@ -68,7 +68,7 @@ prefix_ void senf::scheduler::SignalDispatcher::add(int signal, Callback const & return; } - i = handlers_.insert(std::make_pair(signal, SignalEvent(cb))).first; + i = handlers_.insert(std::make_pair(signal, SignalEvent(signal, cb))).first; sigaddset(&sigSet_, signal); runner_.enqueue(&i->second); diff --git a/Scheduler/SignalDispatcher.cci b/Scheduler/SignalDispatcher.cci index 088fc92..84fe9ab 100644 --- a/Scheduler/SignalDispatcher.cci +++ b/Scheduler/SignalDispatcher.cci @@ -27,6 +27,7 @@ // Custom includes #include +#include "../Utils/signalnames.hh" #define prefix_ inline ///////////////////////////////cci.p/////////////////////////////////////// @@ -45,12 +46,20 @@ prefix_ void senf::scheduler::SignalDispatcher::unblockSignals() blocked_ = false; } +prefix_ bool senf::scheduler::SignalDispatcher::empty() + const +{ + return handlers_.empty(); +} + /////////////////////////////////////////////////////////////////////////// // senf::scheduler::SignalDispatcher::SignalEvent -prefix_ senf::scheduler::SignalDispatcher::SignalEvent::SignalEvent(Callback cb_) +prefix_ senf::scheduler::SignalDispatcher::SignalEvent::SignalEvent(int signal, Callback cb_) : cb (cb_) -{} +{ + name = signalName(signal); +} prefix_ void senf::scheduler::SignalDispatcher::SignalEvent::run() { diff --git a/Scheduler/SignalDispatcher.hh b/Scheduler/SignalDispatcher.hh index b2c44a4..2c2b7ae 100644 --- a/Scheduler/SignalDispatcher.hh +++ b/Scheduler/SignalDispatcher.hh @@ -38,7 +38,16 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Scheduler dispatcher managing UNIX signals + + This dispatcher supports registering UNIX signals with the Scheduler. + + \implementation SignalDispatcher provides a single signal handler which all registered + signals are assigned to. When a signal is received, data is written to a pipe which has + been added to the FdManager and this signals the event. + + \todo Add signal block/unblock management to the FdManager to reduce the number of + setprocmask() calls */ class SignalDispatcher : public FdManager::Event @@ -59,19 +68,27 @@ namespace scheduler { ///@} /////////////////////////////////////////////////////////////////////////// - void add(int signal, Callback const & cb); - void remove(int signal); + void add(int signal, Callback const & cb); ///< Add signal event + /**< \param[in] signal signal number + \param[in] cb Callback */ + + void remove(int signal); ///< Unregister signal event + + void unblockSignals(); ///< Unblock registered signals + /**< Must be called before waiting for an event */ + void blockSignals(); ///< Block registered signals + /**< Must be called directly after FdManager returns */ - void blockSignals(); - void unblockSignals(); + bool empty() const; ///< \c true, if no signal is registered. protected: private: + ///< Internal: UNIX signal event struct SignalEvent : public FIFORunner::TaskInfo { - explicit SignalEvent(Callback cb_); + SignalEvent(int signal, Callback cb_); virtual void run(); siginfo_t siginfo; diff --git a/Scheduler/SignalDispatcher.test.cc b/Scheduler/SignalDispatcher.test.cc index d0bd0da..7531aae 100644 --- a/Scheduler/SignalDispatcher.test.cc +++ b/Scheduler/SignalDispatcher.test.cc @@ -45,6 +45,10 @@ namespace { } +#if 0 +// We can't test this when testing the Scheduler since the Scheduler instance +// already uses the only SignalDispatcher instance allowed ... + BOOST_AUTO_UNIT_TEST(signalDispatcher) { senf::scheduler::FdManager manager; @@ -65,6 +69,8 @@ BOOST_AUTO_UNIT_TEST(signalDispatcher) SENF_CHECK_NO_THROW( dispatcher.remove(SIGUSR1) ); } +#endif + ///////////////////////////////cc.e//////////////////////////////////////// #undef prefix_ diff --git a/Scheduler/TimerDispatcher.cc b/Scheduler/TimerDispatcher.cc index 225e608..520500d 100644 --- a/Scheduler/TimerDispatcher.cc +++ b/Scheduler/TimerDispatcher.cc @@ -56,6 +56,7 @@ prefix_ senf::scheduler::TimerDispatcher::TimerDispatcher(FdManager & manager, } struct sigevent ev; + ::memset(&ev, 0, sizeof(ev)); ev.sigev_notify = SIGEV_SIGNAL; ev.sigev_signo = SIGALRM; ev.sigev_value.sival_ptr = this; @@ -84,10 +85,12 @@ prefix_ senf::scheduler::TimerDispatcher::~TimerDispatcher() } prefix_ senf::scheduler::TimerDispatcher::timer_id -senf::scheduler::TimerDispatcher::add(ClockService::clock_type timeout, Callback const & cb) +senf::scheduler::TimerDispatcher::add(std::string const & name, + ClockService::clock_type timeout, Callback const & cb) { while (timerIdIndex_.find(++lastId_) != timerIdIndex_.end()) ; - TimerMap::iterator i (timers_.insert(std::make_pair(timeout, TimerEvent(lastId_, cb, *this)))); + TimerMap::iterator i ( + timers_.insert(std::make_pair(timeout, TimerEvent(lastId_, cb, *this, name)))); timerIdIndex_.insert(std::make_pair(lastId_, i)); runner_.enqueue(&(i->second)); if (! blocked_) @@ -132,7 +135,7 @@ prefix_ void senf::scheduler::TimerDispatcher::signal(int events) TimerMap::iterator i (timers_.begin()); TimerMap::iterator const i_end (timers_.end()); - ClockService::clock_type now (ClockService::now()); + ClockService::clock_type now (manager_.eventTime()); for (; i != i_end && i->first <= now ; ++i) i->second.runnable = true; } @@ -152,22 +155,41 @@ prefix_ void senf::scheduler::TimerDispatcher::sigHandler(int signal, ::siginfo_ prefix_ void senf::scheduler::TimerDispatcher::reschedule() { struct itimerspec timer; + memset(&timer, 0, sizeof(timer)); timer.it_interval.tv_sec = 0; timer.it_interval.tv_nsec = 0; if (timers_.empty()) { + SENF_LOG( (senf::log::VERBOSE)("Timer disabled") ); timer.it_value.tv_sec = 0; timer.it_value.tv_nsec = 0; } else { ClockService::clock_type next (timers_.begin()->first); + if (next <= 0) + next = 1; timer.it_value.tv_sec = ClockService::in_seconds(next); timer.it_value.tv_nsec = ClockService::in_nanoseconds( next - ClockService::seconds(timer.it_value.tv_sec)); + SENF_LOG( (senf::log::VERBOSE)("Next timeout scheduled @" << timer.it_value.tv_sec << "." + << std::setw(9) << std::setfill('0') << timer.it_value.tv_nsec) ); } if (timer_settime(timerId_, TIMER_ABSTIME, &timer, 0)<0) SENF_THROW_SYSTEM_EXCEPTION("timer_settime()"); } +/////////////////////////////////////////////////////////////////////////// +// senf::scheduler::TimerDispatcher::TimerEvent + +prefix_ void senf::scheduler::TimerDispatcher::TimerEvent::run() +{ + Callback savedCb (cb); + dispatcher.remove(id); + // The member is now running WITHOUT AN OBJECT ... that has been destroyed above !!!!!! On the + // other hand, if we do things the other way round, we have no idea, whether the callback might + // explicitly remove us and we have the same problem then ... + savedCb(); +} + ///////////////////////////////cc.e//////////////////////////////////////// #undef prefix_ //#include "TimerDispatcher.mpp" diff --git a/Scheduler/TimerDispatcher.cci b/Scheduler/TimerDispatcher.cci index d0a3308..6e8e9e6 100644 --- a/Scheduler/TimerDispatcher.cci +++ b/Scheduler/TimerDispatcher.cci @@ -33,14 +33,17 @@ prefix_ senf::scheduler::TimerDispatcher::TimerEvent::TimerEvent(timer_id id_, Callback const & cb_, - TimerDispatcher & dispatcher_) + TimerDispatcher & dispatcher_, + std::string const & n) : id (id_), cb (cb_), dispatcher (dispatcher_) -{} +{ + name = n; +} -prefix_ void senf::scheduler::TimerDispatcher::TimerEvent::run() +prefix_ bool senf::scheduler::TimerDispatcher::empty() + const { - cb(); - dispatcher.remove(id); + return timers_.empty(); } ///////////////////////////////cci.e/////////////////////////////////////// diff --git a/Scheduler/TimerDispatcher.hh b/Scheduler/TimerDispatcher.hh index 1a90ed5..7b6f92a 100644 --- a/Scheduler/TimerDispatcher.hh +++ b/Scheduler/TimerDispatcher.hh @@ -33,6 +33,7 @@ #include "ClockService.hh" #include "FdManager.hh" #include "FIFORunner.hh" +#include "../Utils/Logger/SenfLog.hh" //#include "TimerDispatcher.mpp" ///////////////////////////////hh.p//////////////////////////////////////// @@ -40,11 +41,20 @@ namespace senf { namespace scheduler { - /** \brief + /** \brief Scheduler dispatcher managing timers + + Timers are implemented using high-precision POSIX real-time timers. As such, the timer + granularity is given by clock_getres(CLOCK_MONOTONIC) which is 1ns on current linux kernels. + + \implementation TimerDispatcher manages a single POSIX timer which is always programmed to + expire when the next scheduled timer needs to fire. The timer sends a signal (SIGALRM is + used). The handler writes data into a pipe which is has been added to the FdManager. */ class TimerDispatcher : public FdManager::Event { + SENF_LOG_CLASS_AREA(); + public: /////////////////////////////////////////////////////////////////////////// // Types @@ -62,19 +72,35 @@ namespace scheduler { ///@} /////////////////////////////////////////////////////////////////////////// - timer_id add(ClockService::clock_type timeout, Callback const & cb); - void remove(timer_id id); - - void blockSignals(); - void unblockSignals(); + timer_id add(std::string const & name, ClockService::clock_type timeout, + Callback const & cb); + ///< Add timer event + /**< This call adds a new timer expiring at the given point + in time. + \param[in] name descriptive name + \param[in] timeout point in time when the timer is to + expire + \param[in] cb callback + \returns a \c timer_id which can be used to remove the + timer. */ + void remove(timer_id id); ///< Remove timer + + void unblockSignals(); ///< Unblock internal signals + /**< Must be called before waiting for an event */ + void blockSignals(); ///< Block internal signals + /**< Must be called directly after the FdManager returns */ + + bool empty() const; ///< \c true, if no timer is registered. protected: private: + /// Internal: Timer event struct TimerEvent : public FIFORunner::TaskInfo { - TimerEvent(timer_id id_, Callback const & cb_, TimerDispatcher & dispatcher_); + TimerEvent(timer_id id_, Callback const & cb_, TimerDispatcher & dispatcher_, + std::string const & name); virtual void run(); timer_id id; diff --git a/Scheduler/TimerDispatcher.test.cc b/Scheduler/TimerDispatcher.test.cc index 46e9a82..9f0599c 100644 --- a/Scheduler/TimerDispatcher.test.cc +++ b/Scheduler/TimerDispatcher.test.cc @@ -60,16 +60,26 @@ BOOST_AUTO_UNIT_TEST(timerDispatcher) senf::ClockService::clock_type t (senf::ClockService::now()); senf::scheduler::TimerDispatcher::timer_id id; SENF_CHECK_NO_THROW( - id = dispatcher.add( t + senf::ClockService::milliseconds(500), &handler ) ); + id = dispatcher.add( "testTimer", t + senf::ClockService::milliseconds(500), &handler ) ); SENF_CHECK_NO_THROW( dispatcher.unblockSignals() ); SENF_CHECK_NO_THROW( manager.processOnce() ); SENF_CHECK_NO_THROW( dispatcher.blockSignals() ); SENF_CHECK_NO_THROW( runner.run() ); senf::ClockService::clock_type t2 (senf::ClockService::now()); BOOST_CHECK( called ); - BOOST_CHECK_PREDICATE( is_close, (t2)(t + senf::ClockService::milliseconds(500)) ); + BOOST_CHECK_PREDICATE( is_close, (t2-t)(senf::ClockService::milliseconds(500)) ); SENF_CHECK_NO_THROW( dispatcher.remove(id) ); + + called=false; + t = senf::ClockService::now(); + SENF_CHECK_NO_THROW( dispatcher.add( "testTimer", t, &handler ) ); + SENF_CHECK_NO_THROW( dispatcher.unblockSignals() ); + SENF_CHECK_NO_THROW( manager.processOnce() ); + SENF_CHECK_NO_THROW( dispatcher.blockSignals() ); + SENF_CHECK_NO_THROW( runner.run() ); + BOOST_CHECK_PREDICATE( is_close, (t) (senf::ClockService::now()) ); + BOOST_CHECK( called ); } ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Utils/Daemon/Daemon.cc b/Utils/Daemon/Daemon.cc index 94d95ea..708faca 100644 --- a/Utils/Daemon/Daemon.cc +++ b/Utils/Daemon/Daemon.cc @@ -43,6 +43,7 @@ #include "../Exception.hh" #include "../membind.hh" #include "../Backtrace.hh" +#include "../signalnames.hh" // #define __USE_GNU #include @@ -487,19 +488,9 @@ prefix_ bool senf::Daemon::pidfileCreate() namespace { void fatalSignalsHandler(int sig, ::siginfo_t * info, void * arg) { - static char const * const signames[] = { - "", - "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", - "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", - "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", - "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", - "SIGPWR", "SIGSYS" }; - // ::ucontext_t * ucontext = static_cast(arg); - std::cerr << "\n" << "Signal " << sig; - if (unsigned(sig) < sizeof(signames) / sizeof(signames[0])) - std::cerr << " (" << signames[unsigned(sig)] << ")"; - std::cerr << " received\n"; + std::cerr << "\n" << "Signal " << senf::signalName(sig) << '(' << sig << ')' + << " received\n"; if (sig == SIGSEGV) std::cerr << "Invalid memory access at " << info->si_addr << "\n"; @@ -605,7 +596,7 @@ prefix_ void senf::detail::DaemonWatcher::pipeClosed(int id) } } -prefix_ void senf::detail::DaemonWatcher::sigChld() +prefix_ void senf::detail::DaemonWatcher::sigChld(siginfo_t const &) { sigChld_ = true; if (coutpipe_ == -1 && cerrpipe_ == -1) @@ -658,7 +649,7 @@ prefix_ void senf::detail::DaemonWatcher::Forwarder::addTarget(int fd) targets_.push_back(target); } -prefix_ void senf::detail::DaemonWatcher::Forwarder::readData(Scheduler::EventId event) +prefix_ void senf::detail::DaemonWatcher::Forwarder::readData(int event) { char buf[1024]; int n (0); @@ -692,7 +683,7 @@ prefix_ void senf::detail::DaemonWatcher::Forwarder::readData(Scheduler::EventId buffer_.insert(buffer_.end(), buf, buf+n); } -prefix_ void senf::detail::DaemonWatcher::Forwarder::writeData(Scheduler::EventId event, +prefix_ void senf::detail::DaemonWatcher::Forwarder::writeData(int event, Targets::iterator target) { if (event != Scheduler::EV_WRITE) { diff --git a/Utils/Daemon/Daemon.ih b/Utils/Daemon/Daemon.ih index 96b375c..0cc5c17 100644 --- a/Utils/Daemon/Daemon.ih +++ b/Utils/Daemon/Daemon.ih @@ -70,8 +70,8 @@ namespace detail { }; typedef std::list Targets; - void readData(Scheduler::EventId event); - void writeData(Scheduler::EventId event, Targets::iterator target); + void readData(int event); + void writeData(int event, Targets::iterator target); Buffer buffer_; int src_; @@ -82,7 +82,7 @@ namespace detail { }; void pipeClosed(int id); - void sigChld(); + void sigChld(siginfo_t const &); void childDied(); void childOk(); diff --git a/Utils/Daemon/Daemon.test.cc b/Utils/Daemon/Daemon.test.cc index 57a5115..7591434 100644 --- a/Utils/Daemon/Daemon.test.cc +++ b/Utils/Daemon/Daemon.test.cc @@ -35,6 +35,7 @@ #include #include "Daemon.hh" #include "../Utils/Exception.hh" +#include "../Utils/Backtrace.hh" #include "../Utils/auto_unit_test.hh" #include @@ -79,11 +80,19 @@ namespace { int pid; + void backtrace(int) + { + senf::backtrace(std::cerr, 100); + ::signal(SIGABRT, SIG_DFL); + ::kill(::getpid(), SIGABRT); + }; + int run(int argc, char ** argv) { pid = ::fork(); if (pid < 0) throw senf::SystemException("::fork()"); if (pid == 0) { + signal(SIGABRT, &backtrace); try { ::_exit(myMain(argc, argv)); } catch (std::exception & ex) { diff --git a/Utils/Logger/IOStreamTarget.cc b/Utils/Logger/IOStreamTarget.cc index 487e508..7a57ec2 100644 --- a/Utils/Logger/IOStreamTarget.cc +++ b/Utils/Logger/IOStreamTarget.cc @@ -71,10 +71,10 @@ prefix_ void senf::log::IOStreamTarget::v_write(time_type timestamp, char sep (' '); for (; i != i_end; ++i) { - stream_ << timestamp << sep; + stream_ << senf::ClockService::abstime(timestamp) << sep; stream_ << "[" << LEVELNAMES_[level] << "]"; if (area != "senf::log::DefaultArea") - stream_ << "[" << area << "]"; + stream_ << " [" << area << "]"; stream_ << " " << *i << "\n"; sep = '-'; } diff --git a/Utils/Logger/TimeSource.cc b/Utils/Logger/TimeSource.cc index 61b7253..375cce6 100644 --- a/Utils/Logger/TimeSource.cc +++ b/Utils/Logger/TimeSource.cc @@ -46,10 +46,7 @@ prefix_ senf::log::TimeSource::~TimeSource() prefix_ senf::log::time_type senf::log::SystemTimeSource::operator()() const { - struct ::timespec tm; - if (::clock_gettime(CLOCK_MONOTONIC, &tm) < 0) - SENF_THROW_SYSTEM_EXCEPTION("::clock_gettime()"); - return static_cast(tm.tv_sec)*1000000000ll+tm.tv_nsec; + return senf::ClockService::now(); } ///////////////////////////////cc.e//////////////////////////////////////// diff --git a/Utils/Logger/TimeSource.hh b/Utils/Logger/TimeSource.hh index f88bb3b..ed2c490 100644 --- a/Utils/Logger/TimeSource.hh +++ b/Utils/Logger/TimeSource.hh @@ -29,6 +29,7 @@ // Custom includes #include #include +#include "../../Scheduler/ClockService.hh" //#include "TimeSource.mpp" ///////////////////////////////hh.p//////////////////////////////////////// @@ -36,7 +37,7 @@ namespace senf { namespace log { - typedef boost::int_fast64_t time_type; + typedef ClockService::clock_type time_type; /** \brief Log message time source abstract base class diff --git a/Scheduler/Dispatcher.cci b/Utils/signalnames.cc similarity index 55% copy from Scheduler/Dispatcher.cci copy to Utils/signalnames.cc index 9364c47..4fc09d2 100644 --- a/Scheduler/Dispatcher.cci +++ b/Utils/signalnames.cc @@ -21,21 +21,35 @@ // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /** \file - \brief Dispatcher inline non-template implementation */ + \brief signalnames non-inline non-template implementation */ -//#include "Dispatcher.ih" +#include "signalnames.hh" +//#include "signalnames.ih" // Custom includes +#include -#define prefix_ inline -///////////////////////////////cci.p/////////////////////////////////////// +//#include "signalnames.mpp" +#define prefix_ +///////////////////////////////cc.p//////////////////////////////////////// -prefix_ senf::scheduler::Dispatcher::Dispatcher() - : fdDispatcher_ (fdManager_, runner_) -{} +prefix_ std::string const & senf::signalName(int signal) +{ + static std::string const names[] = { + "", + "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", + "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", + "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", + "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", + "SIGPWR", "SIGSYS" }; -///////////////////////////////cci.e/////////////////////////////////////// + return names[ + (signal <= 0 || signal > int(sizeof(names)/sizeof(names[0]))) ? 0 : signal ]; +} + +///////////////////////////////cc.e//////////////////////////////////////// #undef prefix_ +//#include "signalnames.mpp" // Local Variables: diff --git a/Scheduler/Dispatcher.cci b/Utils/signalnames.hh similarity index 73% rename from Scheduler/Dispatcher.cci rename to Utils/signalnames.hh index 9364c47..d052e86 100644 --- a/Scheduler/Dispatcher.cci +++ b/Utils/signalnames.hh @@ -21,21 +21,29 @@ // 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. /** \file - \brief Dispatcher inline non-template implementation */ + \brief signalnames public header */ -//#include "Dispatcher.ih" +#ifndef HH_signalnames_ +#define HH_signalnames_ 1 // Custom includes +#include -#define prefix_ inline -///////////////////////////////cci.p/////////////////////////////////////// +//#include "signalnames.mpp" +///////////////////////////////hh.p//////////////////////////////////////// -prefix_ senf::scheduler::Dispatcher::Dispatcher() - : fdDispatcher_ (fdManager_, runner_) -{} +namespace senf { -///////////////////////////////cci.e/////////////////////////////////////// -#undef prefix_ + std::string const & signalName(int signal); + +} + + +///////////////////////////////hh.e//////////////////////////////////////// +//#include "signalnames.cci" +//#include "signalnames.ct" +//#include "signalnames.cti" +#endif // Local Variables: