From e2bc6f4153813cc570ae814c8ddb74628009b488 Mon Sep 17 00:00:00 2001 From: Michal Kubecek Date: Mon, 13 Apr 2015 09:21:39 +0200 Subject: initial checkin Check in contents of upstream 1.4.2 tarball, exclude generated files. --- src/timekeeper.cpp | 785 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 src/timekeeper.cpp (limited to 'src/timekeeper.cpp') diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp new file mode 100644 index 0000000..9a1a515 --- /dev/null +++ b/src/timekeeper.cpp @@ -0,0 +1,785 @@ +/* + Copyright (C) 2005-2009 Michel de Boer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include +#include "events.h" +#include "line.h" +#include "log.h" +#include "phone.h" +#include "subscription.h" +#include "timekeeper.h" +#include "transaction_mgr.h" +#include "threads/thread.h" +#include "audits/memman.h" + +extern t_phone *phone; +extern t_event_queue *evq_trans_layer; +extern t_event_queue *evq_trans_mgr; +extern t_event_queue *evq_timekeeper; +extern t_timekeeper *timekeeper; +extern bool threading_is_LinuxThreads; + +string timer_type2str(t_timer_type t) { + switch(t) { + case TMR_TRANSACTION: return "TMR_TRANSACTION"; + case TMR_PHONE: return "TMR_PHONE"; + case TMR_LINE: return "TMR_LINE"; + case TMR_SUBSCRIBE: return "TMR_SUBSCRIBE"; + case TMR_PUBLISH: return "TMR_PUBLISH"; + case TMR_STUN_TRANSACTION: return "TMR_STUN_TRANSACTION"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_timer +/////////////////////////////////////////////////////////// + +t_timer::t_timer(long dur) : t_id_object() { + long d = dur; + + // HACK: if a timer is set to zero seconds, set it to 1 ms, otherwise + // the timer will not expire. + if (dur == 0) d++; + + duration = d; + relative_duration = d; +} + +long t_timer::get_duration(void) const { + return duration; +} + +long t_timer::get_relative_duration(void) const { + return relative_duration; +} + +void t_timer::set_relative_duration(long d) { + relative_duration = d; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_transaction +/////////////////////////////////////////////////////////// + +t_tmr_transaction::t_tmr_transaction(long dur, t_sip_timer tmr, + unsigned short tid) : t_timer(dur) +{ + sip_timer = tmr; + transaction_id = tid; +} + +void t_tmr_transaction::expired(void) { + // Create a timeout event for the transaction manager + evq_trans_mgr->push_timeout(this); +} + +t_timer *t_tmr_transaction::copy(void) const { + t_tmr_transaction *t = new t_tmr_transaction(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_transaction::get_type(void) const { + return TMR_TRANSACTION; +} + +unsigned short t_tmr_transaction::get_tid(void) const { + return transaction_id; +} + +t_sip_timer t_tmr_transaction::get_sip_timer(void) const { + return sip_timer; +} + +string t_tmr_transaction::get_name(void) const { + switch(sip_timer) { + case TIMER_T1: return "TIMER_T1"; + case TIMER_T2: return "TIMER_T2"; + case TIMER_T4: return "TIMER_T4"; + case TIMER_A: return "TIMER_A"; + case TIMER_B: return "TIMER_B"; + case TIMER_C: return "TIMER_C"; + case TIMER_D: return "TIMER_D"; + case TIMER_E: return "TIMER_E"; + case TIMER_F: return "TIMER_F"; + case TIMER_G: return "TIMER_G"; + case TIMER_H: return "TIMER_H"; + case TIMER_I: return "TIMER_I"; + case TIMER_J: return "TIMER_J"; + case TIMER_K: return "TIMER_K"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_phone +/////////////////////////////////////////////////////////// +t_tmr_phone::t_tmr_phone(long dur, t_phone_timer ptmr, t_phone *p) : t_timer(dur) +{ + phone_timer = ptmr; + the_phone = p; +} + +void t_tmr_phone::expired(void) { + evq_trans_layer->push_timeout(this); +} + +t_timer *t_tmr_phone::copy(void) const { + t_tmr_phone *t = new t_tmr_phone(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_phone::get_type(void) const { + return TMR_PHONE; +} + +t_phone_timer t_tmr_phone::get_phone_timer(void) const { + return phone_timer; +} + +t_phone *t_tmr_phone::get_phone(void) const { + return the_phone; +} + +string t_tmr_phone::get_name(void) const { + switch(phone_timer) { + case PTMR_REGISTRATION: return "PTMR_REGISTRATION"; + case PTMR_NAT_KEEPALIVE: return "PTMR_NAT_KEEPALIVE"; + case PTMR_TCP_PING: return "PTMR_TCP_PING"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_line +/////////////////////////////////////////////////////////// +t_tmr_line::t_tmr_line(long dur, t_line_timer ltmr, t_object_id lid, + t_object_id d) : t_timer(dur) +{ + line_timer = ltmr; + line_id = lid; + dialog_id = d; +} + +void t_tmr_line::expired(void) { + evq_trans_layer->push_timeout(this); +} + +t_timer *t_tmr_line::copy(void) const { + t_tmr_line *t = new t_tmr_line(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_line::get_type(void) const { + return TMR_LINE; +} + +t_line_timer t_tmr_line::get_line_timer(void) const { + return line_timer; +} + +t_object_id t_tmr_line::get_line_id(void) const { + return line_id; +} + +t_object_id t_tmr_line::get_dialog_id(void) const { + return dialog_id; +} + +string t_tmr_line::get_name(void) const { + switch(line_timer) { + case LTMR_ACK_TIMEOUT: return "LTMR_ACK_TIMEOUT"; + case LTMR_ACK_GUARD: return "LTMR_ACK_GUARD"; + case LTMR_INVITE_COMP: return "LTMR_INVITE_COMP"; + case LTMR_NO_ANSWER: return "LTMR_NO_ANSWER"; + case LTMR_RE_INVITE_GUARD: return "LTMR_RE_INVITE_GUARD"; + case LTMR_100REL_TIMEOUT: return "LTMR_100REL_TIMEOUT"; + case LTMR_100REL_GUARD: return "LTMR_100REL_GUARD"; + case LTMR_CANCEL_GUARD: return "LTMR_CANCEL_GUARD"; + case LTMR_GLARE_RETRY: return "LTMR_GLARE_RETRY"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_subscribe +/////////////////////////////////////////////////////////// +t_tmr_subscribe::t_tmr_subscribe(long dur, t_subscribe_timer stmr, + t_object_id lid, t_object_id d, const string &event_type, + const string &event_id) : t_timer(dur) +{ + subscribe_timer = stmr; + line_id = lid; + dialog_id = d; + sub_event_type = event_type; + sub_event_id = event_id; +} + +void t_tmr_subscribe::expired(void) { + evq_trans_layer->push_timeout(this); +} + +t_timer *t_tmr_subscribe::copy(void) const { + t_tmr_subscribe *t = new t_tmr_subscribe(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_subscribe::get_type(void) const { + return TMR_SUBSCRIBE; +} + +t_subscribe_timer t_tmr_subscribe::get_subscribe_timer(void) const { + return subscribe_timer; +} + +t_object_id t_tmr_subscribe::get_line_id(void) const { + return line_id; +} + +t_object_id t_tmr_subscribe::get_dialog_id(void) const { + return dialog_id; +} + +string t_tmr_subscribe::get_sub_event_type(void) const { + return sub_event_type; +} + +string t_tmr_subscribe::get_sub_event_id(void) const { + return sub_event_id; +} + +string t_tmr_subscribe::get_name(void) const { + switch(subscribe_timer) { + case STMR_SUBSCRIPTION: return "STMR_SUBSCRIPTION"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_publish +/////////////////////////////////////////////////////////// +t_tmr_publish::t_tmr_publish(long dur, t_publish_timer ptmr, const string &_event_type) : + t_timer(dur), + publish_timer(ptmr), + event_type(_event_type) +{} + +void t_tmr_publish::expired(void) { + evq_trans_layer->push_timeout(this); +} + +t_timer *t_tmr_publish::copy(void) const { + t_tmr_publish *t = new t_tmr_publish(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_publish::get_type(void) const { + return TMR_PUBLISH; +} + +t_publish_timer t_tmr_publish::get_publish_timer(void) const { + return publish_timer; +} + +string t_tmr_publish::get_name(void) const { + switch (publish_timer) { + case PUBLISH_TMR_PUBLICATION: return "PUBLISH_TMR_PUBLICATION"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_tmr_stun_trans +/////////////////////////////////////////////////////////// + +t_tmr_stun_trans::t_tmr_stun_trans(long dur, t_stun_timer tmr, + unsigned short tid) : t_timer(dur) +{ + stun_timer = tmr; + transaction_id = tid; +} + +void t_tmr_stun_trans::expired(void) { + // Create a timeout event for the transaction manager + evq_trans_mgr->push_timeout(this); +} + +t_timer *t_tmr_stun_trans::copy(void) const { + t_tmr_stun_trans *t = new t_tmr_stun_trans(*this); + MEMMAN_NEW(t); + return t; +} + +t_timer_type t_tmr_stun_trans::get_type(void) const { + return TMR_STUN_TRANSACTION; +} + +unsigned short t_tmr_stun_trans::get_tid(void) const { + return transaction_id; +} + +t_stun_timer t_tmr_stun_trans::get_stun_timer(void) const { + return stun_timer; +} + +string t_tmr_stun_trans::get_name(void) const { + switch(stun_timer) { + case STUN_TMR_REQ_TIMEOUT: return "STUN_TMR_REQ_TIMEOUT"; + } + + return "UNKNOWN"; +} + +/////////////////////////////////////////////////////////// +// class t_timekeeper +/////////////////////////////////////////////////////////// + +t_timekeeper::t_timekeeper() : mutex() { + stopped = false; + timer_expired = false; +} + +void t_timekeeper::start(void (*timeout_handler)(int)) { + signal(SIGALRM, timeout_handler); +} + +t_timekeeper::~t_timekeeper() { + struct itimerval itimer; + + mutex.lock(); + + log_file->write_header("t_timekeeper::~t_timekeeper", + LOG_NORMAL, LOG_INFO); + log_file->write_raw("Clean up timekeeper.\n"); + + // Stop timers + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_usec = 0; + itimer.it_value.tv_sec = 0; + itimer.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &itimer, NULL); + + for (list::iterator i = timer_list.begin(); + i != timer_list.end(); i++) + { + log_file->write_raw("\nDeleting timer:\n"); + log_file->write_raw("Id: "); + log_file->write_raw((*i)->get_object_id()); + log_file->write_raw(", Type: "); + log_file->write_raw(timer_type2str((*i)->get_type())); + log_file->write_raw(", Timer: "); + log_file->write_raw((*i)->get_name()); + log_file->write_raw("\nDuration: "); + log_file->write_raw((*i)->get_duration()); + log_file->write_raw(", Relative duration: "); + log_file->write_raw((*i)->get_relative_duration()); + log_file->write_endl(); + if ((*i)->get_type() == TMR_TRANSACTION) { + log_file->write_raw("Transaction id: "); + log_file->write_raw( + ((t_tmr_transaction *)(*i))->get_tid()); + log_file->write_endl(); + } + MEMMAN_DELETE(*i); + delete *i; + } + + if (threading_is_LinuxThreads) { + signal(SIGALRM, SIG_DFL); + } + + log_file->write_footer(); + + mutex.unlock(); +} + +void t_timekeeper::lock(void) { + mutex.lock(); +} + +void t_timekeeper::unlock(void) { + mutex.unlock(); + + if (timer_expired) { + timer_expired = false; + report_expiry(); + } +} + +void t_timekeeper::start_timer(t_timer *t) { + struct itimerval itimer; + long remain_msec; + + lock(); + + // The next interval option is not used + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_usec = 0; + + // Get duration of the timer to start + long d = t->get_relative_duration(); + + // If no timer is currently running then simply start the timer + if (timer_list.empty()) { + timer_list.push_back(t); + itimer.it_value.tv_sec = d / 1000; + itimer.it_value.tv_usec = (d % 1000) * 1000; + setitimer(ITIMER_REAL, &itimer, NULL); + + unlock(); + return; + } + + // Get remaining duration of current running timer + getitimer(ITIMER_REAL, &itimer); + remain_msec = itimer.it_value.tv_sec * 1000 + + itimer.it_value.tv_usec / 1000; + + // If the new timer is shorter than the current timer. + // then the new timer should be run first. + if (d < remain_msec) { + // Change running timer to new timer + itimer.it_value.tv_sec = d / 1000; + itimer.it_value.tv_usec = (d % 1000) * 1000; + setitimer(ITIMER_REAL, &itimer, NULL); + + // Calculate the relative duration the timer + // that was running. + t_timer *old_timer = timer_list.front(); + old_timer->set_relative_duration(remain_msec - d); + + // Add new timer at the front of the list + timer_list.push_front(t); + + unlock(); + return; + } + + // Calculate the relative duration for the new timer + long new_duration = d - remain_msec; + + // Insert the new timer at the right position in the list. + list::iterator i; + for (i = timer_list.begin(); i != timer_list.end(); i++) + { + // skip the first timer + if (i == timer_list.begin()) continue; + + long dur = (*i)->get_relative_duration(); + if (new_duration < dur) { + // Adjust relative duration existing timer + (*i)->set_relative_duration(dur - new_duration); + + // Insert new timer before existing timer + t->set_relative_duration(new_duration); + timer_list.insert(i, t); + + unlock(); + return; + } + + new_duration -= dur; + } + + // Add the new timer to the end of the list + t->set_relative_duration(new_duration); + timer_list.push_back(t); + + unlock(); +} + +void t_timekeeper::stop_timer(t_object_id id) { + struct itimerval itimer; + long remain_msec; + + lock(); + + // The next interval option is not used + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_usec = 0; + + + if (timer_list.empty()) { + // Timer already expired or stopped + unlock(); + return; + } + + // Find timer + list::iterator i = timer_list.begin(); + while (i != timer_list.end()) { + if ((*i)->get_object_id() == id) break; + i++; + } + + if (i == timer_list.end()) { + // Timer already expired or stopped. + unlock(); + return; + } + + // If it is the current running timer, then it must be stopped + if (i == timer_list.begin()) { + getitimer(ITIMER_REAL, &itimer); + + // If remaining time is less then 100 msec then let it + // expire to prevent race condition when timer expires + // while stopping it now. + remain_msec = itimer.it_value.tv_sec * 1000 + + itimer.it_value.tv_usec / 1000; + if (remain_msec < 100) { + stopped = true; + unlock(); + return; + } + + // Stop timer + itimer.it_value.tv_sec = 0; + itimer.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &itimer, NULL); + + // Remove the timer + MEMMAN_DELETE(timer_list.front()); + delete timer_list.front(); + timer_list.pop_front(); + + // If a next timer exists then adjust its relative + // duration and start it. + if (!timer_list.empty()) { + t_timer *next_timer = timer_list.front(); + long dur = next_timer->get_relative_duration(); + dur += remain_msec; + next_timer->set_relative_duration(dur); + itimer.it_value.tv_sec = dur / 1000; + itimer.it_value.tv_usec = (dur % 1000) * 1000; + setitimer(ITIMER_REAL, &itimer, NULL); + } + + unlock(); + return; + } + + // Timer is not the current running timer, so delete it + // and adjust relative duration of the next timer. + list::iterator next = i; + next++; + + if (next == timer_list.end()) { + // There is no next timer + MEMMAN_DELETE(timer_list.back()); + delete timer_list.back(); + timer_list.pop_back(); + unlock(); + return; + } + + long dur = (*i)->get_relative_duration(); + long dur_next = (*next)->get_relative_duration(); + (*next)->set_relative_duration(dur + dur_next); + MEMMAN_DELETE(*i); + delete *i; + timer_list.erase(i); + + unlock(); +} + +void t_timekeeper::report_expiry(void) { + lock(); + + if (timer_list.empty()) { + unlock(); + return; + } + + t_timer *t = timer_list.front(); + + // Trigger action if timer was not stopped + if (!stopped) { + t->expired(); + } + stopped = false; + + // Remove the timer + MEMMAN_DELETE(timer_list.front()); + delete timer_list.front(); + timer_list.pop_front(); + + if (timer_list.empty()) { + unlock(); + return; + } + + // If the relative duration of the next timer is 0, then + // it also expired. Action should be triggerd. If not, then + // it should be started. + t_timer *next = timer_list.front(); + long dur = next->get_relative_duration(); + while (dur == 0) { + next->expired(); + MEMMAN_DELETE(next); + delete next; + timer_list.pop_front(); + if (timer_list.empty()) break; + next = timer_list.front(); + dur = next->get_relative_duration(); + } + + if (!timer_list.empty()) { + struct itimerval itimer; + + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_usec = 0; + itimer.it_value.tv_sec = dur / 1000; + itimer.it_value.tv_usec = (dur % 1000) * 1000; + setitimer(ITIMER_REAL, &itimer, NULL); + } + + unlock(); +} + +unsigned long t_timekeeper::get_remaining_time(t_object_id timer_id) { + struct itimerval itimer; + unsigned long remain_msec = 0; + unsigned long duration = 0; + + lock(); + + // The next interval option is not used + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_usec = 0; + + // Get remaining duration of current running timer + getitimer(ITIMER_REAL, &itimer); + remain_msec = itimer.it_value.tv_sec * 1000 + + itimer.it_value.tv_usec / 1000; + + // Find the timer + list::iterator i = timer_list.begin(); + while (i != timer_list.end()) { + if (i != timer_list.begin()) { + remain_msec += (*i)->get_relative_duration(); + } + + if ((*i)->get_object_id() == timer_id) break; + + i++; + } + + // Return duration to originator of get event + if (i == timer_list.end()) { + duration = 0; + } else { + duration = remain_msec; + } + + unlock(); + return duration; +} + +// SIGALRM handler +void timeout_handler(int signum) { + signal(SIGALRM, timeout_handler); + // timekeeper.report_expiry(); + + // This will signal an interrupt to the call to pop in the + // main look t_timekeeper::run + evq_timekeeper->interrupt(); +} + +void t_timekeeper::run(void) { + t_event *event; + t_event_start_timer *ev_start; + t_event_stop_timer *ev_stop; + bool timeout; + + // The timekeeper should not try to take the phone lock as + // it may lead to a deadlock. Make sure an assert is raised + // if this situation ever happens. + phone->add_prohibited_thread(); + + if (threading_is_LinuxThreads) { + // In LinuxThreads SIGALRM caused by the expiration of a timer + // started with setitimer is always delivered to the thread calling + // setitimer. So the sigwait() call from another thread does not + // work. Use a signal handler instead. + start(timeout_handler); + } + + bool quit = false; + while (!quit) { + event = evq_timekeeper->pop(timeout); + + if (timeout) { + report_expiry(); + continue; + } + + switch(event->get_type()) { + case EV_START_TIMER: + ev_start = (t_event_start_timer *)event; + start_timer(ev_start->get_timer()); + break; + case EV_STOP_TIMER: + ev_stop = (t_event_stop_timer *)event; + stop_timer(ev_stop->get_timer_id()); + break; + case EV_QUIT: + quit = true; + break; + default: + assert(false); + } + + MEMMAN_DELETE(event); + delete event; + } +} + +void *timekeeper_main(void *arg) { + timekeeper->run(); + return NULL; +} + +void *timekeeper_sigwait(void *arg) { + sigset_t sigset; + int sig; + + sigemptyset(&sigset); + sigaddset(&sigset, SIGALRM); + + while (true) { + // When SIGCONT is received after SIGSTOP, sigwait returns + // with EINTR ?? + if (sigwait(&sigset, &sig) == EINTR) continue; + evq_timekeeper->interrupt(); + } +} -- cgit v1.2.3