#include #include #include "LogFile.h" #include "DeadManTimer.h" ///////////////////////////////////////////////////////////////////// // TouchConnectionRequest ///////////////////////////////////////////////////////////////////// class TouchConnectionRequest : public Request { private: int _seconds; public: TouchConnectionRequest(SocketInfo *socket, int seconds) : Request(socket), _seconds(seconds) { } int getSeconds() { return _seconds; } }; ///////////////////////////////////////////////////////////////////// // DeadManTimer // // There are three important lists which drive this class. // // _immune is a set of sockets which should never time out. // // _socketByTime and _timeBySocket contain the timeout values for // each socket. We use two lists because there are two ways to // access these lists. An external request can tell us to update // or remove a socket. And interally we have to know which socket // is the next one to time out. // // A typical socket in the ax_alert_server project will update the // dead man timer at least once per second, and each time it will // request to stay alive for 120 seconds. It would be simple but // wasteful to completely remove the entries from _socketByTime and // _timeBySocket each time we got an update. Instead, we only // update _timeBySocket. Eventually the entry in _socketByTime // will come up. At that time we check _timeBySocket to see if the // socket has really expired, or if it has time left. If it has // time left, then we update both _socketByTime and _timeBySocket. // // The current implementation assumes that we only extend the time. // If someone calls touchConnection() and requests that we timeout // sooner, that request might not work. We could implement that, // but I don't think anyone needs that. ///////////////////////////////////////////////////////////////////// DeadManTimer::DeadManTimer() : ThreadClass("DeadManTimer"), _incomingRequests("DeadManTimer"), _defaultTimeout(300) { startThread(); } DeadManTimer::~DeadManTimer() { Request *r = new Request(NULL); r->callbackId = mtQuit; _incomingRequests.newRequest(r); waitForThread(); } void DeadManTimer::remove(SocketInfo *socket) { Times *times = getProperty(_timeBySocket, socket); if (times) { _socketByTime.erase(AbortInfo(times->orig, socket)); _timeBySocket.erase(socket); } } void DeadManTimer::threadFunction() { while (true) { _incomingRequests.resetWaitHandle(); while (Request *current = _incomingRequests.getRequest()) { switch (current->callbackId) { case mtTouchConnection: { SocketInfo *socket = current->getSocketInfo(); if (!_immune.count(socket)) { TimeVal newTimeout(true); newTimeout.addSeconds(dynamic_cast< TouchConnectionRequest * >(current)->getSeconds()); Times *times = getProperty(_timeBySocket, socket); if (times) { // Update the existing entry times->last = newTimeout; } else { // Add a new entry _timeBySocket[socket] = Times(newTimeout, newTimeout); _socketByTime.insert(AbortInfo(newTimeout, socket)); } } break; } case mtGrantImmunity: { SocketInfo *socket = current->getSocketInfo(); remove(socket); _immune.insert(socket); break; } case mtQuit: { delete current; return; } case DeleteSocketThread::callbackId: { SocketInfo *socket = current->getSocketInfo(); remove(socket); _immune.erase(socket); break; } } delete current; } while (true) { if (_socketByTime.empty()) { _incomingRequests.waitForRequest(); break; } AbortInfo const &info = *_socketByTime.begin(); timeval waitTime = info.first.waitTime(); if (waitTime.tv_sec || waitTime.tv_usec) { _incomingRequests.waitForRequest(&waitTime); break; } Times × = _timeBySocket[info.second]; timeval realWaitTime = times.last.waitTime(); if (realWaitTime.tv_sec || realWaitTime.tv_usec) { // Update this entry and keep waiting. _socketByTime.insert(AbortInfo(times.last, info.second)); _socketByTime.erase(info); times.orig = times.last; break; } LogFile::primary().quoteAndSend("DeadManTimer.C", "Expired", info.second); DeleteSocketThread::deleteSocket(info.second); remove(info.second); } } } void DeadManTimer::touchConnection(SocketInfo *socketInfo, int seconds) { Request *r = new TouchConnectionRequest(socketInfo, seconds); r->callbackId = mtTouchConnection; _incomingRequests.newRequest(r); } void DeadManTimer::grantImmunity(SocketInfo *socketInfo) { Request *r = new Request(socketInfo); r->callbackId = mtGrantImmunity; _incomingRequests.newRequest(r); }