diff --git a/libs/freetdm/src/ftdm_io.c b/libs/freetdm/src/ftdm_io.c index 6991e9ab9b..97aa01bca0 100644 --- a/libs/freetdm/src/ftdm_io.c +++ b/libs/freetdm/src/ftdm_io.c @@ -50,6 +50,7 @@ #endif #include "ftdm_cpu_monitor.h" +#define FORCE_HANGUP_TIMER 3000 #define SPAN_PENDING_CHANS_QUEUE_SIZE 1000 #define SPAN_PENDING_SIGNALS_QUEUE_SIZE 1000 #define FTDM_READ_TRACE_INDEX 0 @@ -102,6 +103,7 @@ static struct { ftdm_mutex_t *mutex; ftdm_mutex_t *span_mutex; ftdm_mutex_t *group_mutex; + ftdm_sched_t *timingsched; uint32_t span_index; uint32_t group_index; uint32_t running; @@ -2002,6 +2004,9 @@ static ftdm_status_t call_hangup(ftdm_channel_t *chan, const char *file, const c /* make user's life easier, and just ignore double hangup requests */ return FTDM_SUCCESS; } + if (chan->hangup_timer) { + ftdm_sched_cancel_timer(globals.timingsched, chan->hangup_timer); + } ftdm_channel_set_state(file, func, line, chan, FTDM_CHANNEL_STATE_HANGUP, 1); } else { /* the signaling stack did not touch the state, @@ -2283,6 +2288,9 @@ FT_DECLARE(ftdm_status_t) ftdm_channel_done(ftdm_channel_t *ftdmchan) close_dtmf_debug(ftdmchan); #endif ftdm_channel_clear_vars(ftdmchan); + if (ftdmchan->hangup_timer) { + ftdm_sched_cancel_timer(globals.timingsched, ftdmchan->hangup_timer); + } ftdmchan->init_state = FTDM_CHANNEL_STATE_DOWN; ftdmchan->state = FTDM_CHANNEL_STATE_DOWN; @@ -4601,6 +4609,21 @@ FT_DECLARE(ftdm_status_t) ftdm_span_trigger_signals(const ftdm_span_t *span) return FTDM_SUCCESS; } + +static void execute_safety_hangup(void *data) +{ + ftdm_channel_t *fchan = data; + ftdm_channel_lock(fchan); + fchan->hangup_timer = 0; + if (fchan->state == FTDM_CHANNEL_STATE_TERMINATING) { + ftdm_log_chan(fchan, FTDM_LOG_CRIT, "Forcing hangup since the user did not confirmed our hangup after %dms\n", FORCE_HANGUP_TIMER); + call_hangup(fchan, __FILE__, __FUNCTION__, __LINE__); + } else { + ftdm_log_chan(fchan, FTDM_LOG_CRIT, "Not performing safety hangup, channel state is %s\n", ftdm_channel_state2str(fchan->state)); + } + ftdm_channel_unlock(fchan); +} + FT_DECLARE(ftdm_status_t) ftdm_span_send_signal(ftdm_span_t *span, ftdm_sigmsg_t *sigmsg) { if (sigmsg->channel) { @@ -4634,6 +4657,11 @@ FT_DECLARE(ftdm_status_t) ftdm_span_send_signal(ftdm_span_t *span, ftdm_sigmsg_t ftdm_log_chan_msg(sigmsg->channel, FTDM_LOG_DEBUG, "Ignoring SIGEVENT_STOP since user already requested hangup\n"); goto done; } + if (sigmsg->channel->state == FTDM_CHANNEL_STATE_TERMINATING) { + ftdm_log_chan_msg(sigmsg->channel, FTDM_LOG_DEBUG, "Scheduling safety hangup timer\n"); + /* if the user does not move us to hangup in 2 seconds, we will do it ourselves */ + ftdm_sched_timer(globals.timingsched, "safety-hangup", FORCE_HANGUP_TIMER, execute_safety_hangup, sigmsg->channel, &sigmsg->channel->hangup_timer); + } break; default: @@ -4755,6 +4783,14 @@ FT_DECLARE(ftdm_status_t) ftdm_global_init(void) ftdm_mutex_create(&globals.span_mutex); ftdm_mutex_create(&globals.group_mutex); ftdm_sched_global_init(); + if (ftdm_sched_create(&globals.timingsched, "freetdm-master") != FTDM_SUCCESS) { + ftdm_log(FTDM_LOG_CRIT, "Failed to create master timing schedule context\n"); + return FTDM_FAIL; + } + if (ftdm_sched_free_run(globals.timingsched) != FTDM_SUCCESS) { + ftdm_log(FTDM_LOG_CRIT, "Failed to run master timing schedule context\n"); + return FTDM_FAIL; + } globals.running = 1; return FTDM_SUCCESS; } @@ -4807,6 +4843,8 @@ FT_DECLARE(ftdm_status_t) ftdm_global_destroy(void) globals.running = 0; + ftdm_sched_destroy(&globals.timingsched); + ftdm_cpu_monitor_stop(); globals.span_index = 0; diff --git a/libs/freetdm/src/ftdm_sched.c b/libs/freetdm/src/ftdm_sched.c index 5aacc202bf..be21696d71 100644 --- a/libs/freetdm/src/ftdm_sched.c +++ b/libs/freetdm/src/ftdm_sched.c @@ -34,6 +34,8 @@ #include "private/ftdm_core.h" +typedef struct ftdm_timer ftdm_timer_t; + static struct { ftdm_sched_t *freeruns; ftdm_mutex_t *mutex; @@ -42,6 +44,7 @@ static struct { struct ftdm_sched { char name[80]; + ftdm_timer_id_t currid; ftdm_mutex_t *mutex; ftdm_timer_t *timers; int freerun; @@ -51,6 +54,7 @@ struct ftdm_sched { struct ftdm_timer { char name[80]; + ftdm_timer_id_t id; #ifdef __linux__ struct timeval time; #endif @@ -191,6 +195,7 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_create(ftdm_sched_t **sched, const char *na } ftdm_set_string(newsched->name, name); + newsched->currid = 1; *sched = newsched; ftdm_log(FTDM_LOG_DEBUG, "Created schedule %s\n", name); @@ -219,12 +224,13 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_run(ftdm_sched_t *sched) int rc = -1; void *data; struct timeval now; + ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n"); - ftdm_mutex_lock(sched->mutex); - tryagain: + ftdm_mutex_lock(sched->mutex); + rc = gettimeofday(&now, NULL); if (rc == -1) { ftdm_log(FTDM_LOG_ERROR, "Failed to retrieve time of day\n"); @@ -257,11 +263,16 @@ tryagain: runtimer->prev->next = runtimer->next; } + runtimer->id = 0; ftdm_safe_free(runtimer); + /* avoid deadlocks by releasing the sched lock before triggering callbacks */ + ftdm_mutex_unlock(sched->mutex); + callback(data); /* after calling a callback we must start the scanning again since the - * callback may have added or cancelled timers to the linked list */ + * callback or some other thread may have added or cancelled timers to + * the linked list */ goto tryagain; } } @@ -283,7 +294,7 @@ done: } FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name, - int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_t **timer) + int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_id_t *timerid) { ftdm_status_t status = FTDM_FAIL; #ifdef __linux__ @@ -296,8 +307,8 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name ftdm_assert_return(callback != NULL, FTDM_EINVAL, "sched callback is null!\n"); ftdm_assert_return(ms > 0, FTDM_EINVAL, "milliseconds must be bigger than 0!\n"); - if (timer) { - *timer = NULL; + if (timerid) { + *timerid = 0; } rc = gettimeofday(&now, NULL); @@ -312,6 +323,15 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name if (!newtimer) { goto done; } + newtimer->id = sched->currid; + sched->currid++; + if (!sched->currid) { + ftdm_log(FTDM_LOG_NOTICE, "Timer id wrap around for sched %s\n", sched->name); + /* we do not want currid to be zero since is an invalid id + * TODO: check that currid does not exists already in the context, it'd be insane + * though, having a timer to live all that time */ + sched->currid++; + } ftdm_set_string(newtimer->name, name); newtimer->callback = callback; @@ -332,9 +352,10 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name sched->timers = newtimer; } - if (timer) { - *timer = newtimer; + if (timerid) { + *timerid = newtimer->id; } + status = FTDM_SUCCESS; done: @@ -418,53 +439,37 @@ done: return status; } -FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_t **intimer) +FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_id_t timerid) { ftdm_status_t status = FTDM_FAIL; ftdm_timer_t *timer; ftdm_assert_return(sched != NULL, FTDM_EINVAL, "sched is null!\n"); - ftdm_assert_return(intimer != NULL, FTDM_EINVAL, "timer is null!\n"); - ftdm_assert_return(*intimer != NULL, FTDM_EINVAL, "timer is null!\n"); + + if (!timerid) { + return FTDM_SUCCESS; + } ftdm_mutex_lock(sched->mutex); - /* special case where the cancelled timer is the head */ - if (*intimer == sched->timers) { - timer = *intimer; - /* the timer next is the new head (even if that means the new head will be NULL)*/ - sched->timers = timer->next; - /* if there is a new head, clean its prev member */ - if (sched->timers) { - sched->timers->prev = NULL; - } - /* free the old head */ - ftdm_safe_free(timer); - status = FTDM_SUCCESS; - *intimer = NULL; - goto done; - } - - /* look for the timer and destroy it (we know now that is not head, se we start at the next member after head) */ - for (timer = sched->timers->next; timer; timer = timer->next) { - if (timer == *intimer) { + /* look for the timer and destroy it */ + for (timer = sched->timers; timer; timer = timer->next) { + if (timer->id == timerid) { + if (timer == sched->timers) { + /* it's the head timer, put a new head */ + sched->timers = timer->next; + } if (timer->prev) { timer->prev->next = timer->next; } if (timer->next) { timer->next->prev = timer->prev; } - ftdm_log(FTDM_LOG_DEBUG, "cancelled timer %s\n", timer->name); ftdm_safe_free(timer); status = FTDM_SUCCESS; - *intimer = NULL; break; } } -done: - if (status == FTDM_FAIL) { - ftdm_log(FTDM_LOG_ERROR, "Could not find timer %s to cancel it\n", (*intimer)->name); - } ftdm_mutex_unlock(sched->mutex); diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.h b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.h index cd96a18fce..ae6c0d92f7 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.h +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn.h @@ -156,7 +156,7 @@ typedef struct sngisdn_chan_data { uint8_t globalFlg; sngisdn_glare_data_t glare; - ftdm_timer_t *timers[SNGISDN_NUM_TIMERS]; + ftdm_timer_id_t timers[SNGISDN_NUM_TIMERS]; } sngisdn_chan_data_t; /* Span specific data */ diff --git a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_hndl.c b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_hndl.c index e8ba3879e5..33024d73d0 100644 --- a/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_hndl.c +++ b/libs/freetdm/src/ftmod/ftmod_sangoma_isdn/ftmod_sangoma_isdn_stack_hndl.c @@ -149,7 +149,8 @@ void sngisdn_process_con_ind (sngisdn_event_data_t *sngisdn_event) ftdm_set_state(ftdmchan, FTDM_CHANNEL_STATE_GET_CALLERID); /* Launch timer in case we never get a FACILITY msg */ if (signal_data->facility_timeout) { - ftdm_sched_timer(signal_data->sched, "facility_timeout", signal_data->facility_timeout, sngisdn_facility_timeout, (void*) sngisdn_info, &sngisdn_info->timers[SNGISDN_TIMER_FACILITY]); + ftdm_sched_timer(signal_data->sched, "facility_timeout", signal_data->facility_timeout, + sngisdn_facility_timeout, (void*) sngisdn_info, sngisdn_info->timers[SNGISDN_TIMER_FACILITY]); } break; } else if (ret_val == 0) { @@ -715,7 +716,7 @@ void sngisdn_process_fac_ind (sngisdn_event_data_t *sngisdn_event) } if (signal_data->facility_timeout) { /* Cancel facility timeout */ - ftdm_sched_cancel_timer(signal_data->sched, &sngisdn_info->timers[SNGISDN_TIMER_FACILITY]); + ftdm_sched_cancel_timer(signal_data->sched, sngisdn_info->timers[SNGISDN_TIMER_FACILITY]); } } diff --git a/libs/freetdm/src/include/private/ftdm_core.h b/libs/freetdm/src/include/private/ftdm_core.h index d8947f6de4..a701c1383d 100644 --- a/libs/freetdm/src/include/private/ftdm_core.h +++ b/libs/freetdm/src/include/private/ftdm_core.h @@ -425,6 +425,7 @@ struct ftdm_channel { float txgain; int availability_rate; void *user_private; + ftdm_timer_id_t hangup_timer; #ifdef FTDM_DEBUG_DTMF ftdm_dtmf_debug_t dtmfdbg; #endif diff --git a/libs/freetdm/src/include/private/ftdm_sched.h b/libs/freetdm/src/include/private/ftdm_sched.h index 0951d050a7..9a222896f5 100644 --- a/libs/freetdm/src/include/private/ftdm_sched.h +++ b/libs/freetdm/src/include/private/ftdm_sched.h @@ -44,8 +44,8 @@ extern "C" { #define FTDM_MICROSECONDS_PER_SECOND 1000000 typedef struct ftdm_sched ftdm_sched_t; -typedef struct ftdm_timer ftdm_timer_t; typedef void (*ftdm_sched_callback_t)(void *data); +typedef uint64_t ftdm_timer_id_t; /*! \brief Create a new scheduling context */ FT_DECLARE(ftdm_status_t) ftdm_sched_create(ftdm_sched_t **sched, const char *name); @@ -62,18 +62,22 @@ FT_DECLARE(ftdm_status_t) ftdm_sched_free_run(ftdm_sched_t *sched); * \param name Timer name, typically unique but is not required to be unique, any null terminated string is fine (required) * \param callback The callback to call upon timer expiration (required) * \param data Optional data to pass to the callback - * \param timer The timer that was created, it can be NULL if you dont care, - * but you need this if you want to be able to cancel the timer with ftdm_sched_cancel_timer + * \param timer Timer id pointer to store the id of the newly created timer. It can be null + * if you do not need to know the id, but you need this if you want to be able + * to cancel the timer with ftdm_sched_cancel_timer */ FT_DECLARE(ftdm_status_t) ftdm_sched_timer(ftdm_sched_t *sched, const char *name, - int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_t **timer); + int ms, ftdm_sched_callback_t callback, void *data, ftdm_timer_id_t *timer); /*! * \brief Cancel the timer + * Note that there is a race between cancelling and triggering a timer. + * By the time you call this function the timer may be about to be triggered. + * This is specially true with timers in free run schedule. * \param sched The scheduling context (required) * \param timer The timer to cancel (required) */ -FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_t **timer); +FT_DECLARE(ftdm_status_t) ftdm_sched_cancel_timer(ftdm_sched_t *sched, ftdm_timer_id_t timer); /*! \brief Destroy the context and all of the scheduled timers in it */ FT_DECLARE(ftdm_status_t) ftdm_sched_destroy(ftdm_sched_t **sched);