diff options
Diffstat (limited to 'pceplib/pcep_timers.c')
| -rw-r--r-- | pceplib/pcep_timers.c | 482 | 
1 files changed, 482 insertions, 0 deletions
diff --git a/pceplib/pcep_timers.c b/pceplib/pcep_timers.c new file mode 100644 index 0000000000..e9d9d4b21d --- /dev/null +++ b/pceplib/pcep_timers.c @@ -0,0 +1,482 @@ +/* + * This file is part of the PCEPlib, a PCEP protocol library. + * + * Copyright (C) 2020 Volta Networks https://voltanet.io/ + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + * + * Author : Brady Johnson <brady@voltanet.io> + * + */ + + +/* + *  Implementation of public API timer functions. + */ + +#include <limits.h> +#include <pthread.h> +#include <stddef.h> +#include <stdbool.h> +#include <string.h> + +#include "pcep_timers.h" +#include "pcep_utils_logging.h" +#include "pcep_utils_memory.h" +#include "pcep_utils_ordered_list.h" + +static pcep_timers_context *timers_context_ = NULL; +static int timer_id_ = 0; + + +/* simple compare method callback used by pcep_utils_ordered_list + * for ordered list insertion. */ +int timer_list_node_compare(void *list_entry, void *new_entry) +{ +	/* return: +	 *   < 0  if new_entry < list_entry +	 *   == 0 if new_entry == list_entry (new_entry will be inserted after +	 * list_entry) > 0  if new_entry > list_entry */ +	return ((pcep_timer *)new_entry)->expire_time +	       - ((pcep_timer *)list_entry)->expire_time; +} + + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its timer_id. */ +int timer_list_node_timer_id_compare(void *list_entry, void *new_entry) +{ +	return ((pcep_timer *)new_entry)->timer_id +	       - ((pcep_timer *)list_entry)->timer_id; +} + +/* simple compare method callback used by pcep_utils_ordered_list + * ordered_list_remove_first_node_equals2 to remove a timer based on + * its address. */ +int timer_list_node_timer_ptr_compare(void *list_entry, void *new_entry) +{ +	return ((char *)new_entry - (char *)list_entry); +} + +/* internal util method */ +static pcep_timers_context *create_timers_context_() +{ +	if (timers_context_ == NULL) { +		timers_context_ = pceplib_malloc(PCEPLIB_INFRA, +						 sizeof(pcep_timers_context)); +		memset(timers_context_, 0, sizeof(pcep_timers_context)); +		timers_context_->active = false; +	} + +	return timers_context_; +} + + +/* Internal util function */ +static bool initialize_timers_common(timer_expire_handler expire_handler) +{ +	if (expire_handler == NULL) { +		/* Cannot have a NULL handler function */ +		return false; +	} + +	timers_context_ = create_timers_context_(); + +	if (timers_context_->active == true) { +		/* already initialized */ +		return false; +	} + +	timers_context_->active = true; +	timers_context_->timer_list = +		ordered_list_initialize(timer_list_node_compare); +	timers_context_->expire_handler = expire_handler; + +	if (pthread_mutex_init(&(timers_context_->timer_list_lock), NULL) +	    != 0) { +		pcep_log( +			LOG_ERR, +			"%s: ERROR initializing timers, cannot initialize the mutex", +			__func__); +		return false; +	} + +	return true; +} + +bool initialize_timers(timer_expire_handler expire_handler) +{ +	if (initialize_timers_common(expire_handler) == false) { +		return false; +	} + +	if (pthread_create(&(timers_context_->event_loop_thread), NULL, +			   event_loop, timers_context_)) { +		pcep_log( +			LOG_ERR, +			"%s: ERROR initializing timers, cannot initialize the thread", +			__func__); +		return false; +	} + +	return true; +} + +bool initialize_timers_external_infra( +	timer_expire_handler expire_handler, void *external_timer_infra_data, +	ext_timer_create timer_create_func, ext_timer_cancel timer_cancel_func, +	ext_pthread_create_callback thread_create_func) +{ +	if (initialize_timers_common(expire_handler) == false) { +		return false; +	} + +	if (thread_create_func != NULL) { +		if (thread_create_func(&(timers_context_->event_loop_thread), +				       NULL, event_loop, timers_context_, +				       "pceplib_timers")) { +			pcep_log( +				LOG_ERR, +				"%s: Cannot initialize external timers thread.", +				__func__); +			return false; +		} +	} else { +		if (pthread_create(&(timers_context_->event_loop_thread), NULL, +				   event_loop, timers_context_)) { +			pcep_log( +				LOG_ERR, +				"%s: ERROR initializing timers, cannot initialize the thread", +				__func__); +			return false; +		} +	} + +	timers_context_->external_timer_infra_data = external_timer_infra_data; +	timers_context_->timer_create_func = timer_create_func; +	timers_context_->timer_cancel_func = timer_cancel_func; + +	return true; +} + +/* + * This function is only used to tear_down the timer data. + * Only the timer data is deleted, not the list itself, + * which is deleted by ordered_list_destroy(). + */ +void free_all_timers(pcep_timers_context *timers_context) +{ +	pthread_mutex_lock(&timers_context->timer_list_lock); + +	ordered_list_node *timer_node = timers_context->timer_list->head; + +	while (timer_node != NULL) { +		if (timer_node->data != NULL) { +			pceplib_free(PCEPLIB_INFRA, timer_node->data); +		} +		timer_node = timer_node->next_node; +	} + +	pthread_mutex_unlock(&timers_context->timer_list_lock); +} + + +bool teardown_timers() +{ +	if (timers_context_ == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to teardown the timers, but they are not initialized", +			__func__); +		return false; +	} + +	if (timers_context_->active == false) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to teardown the timers, but they are not active", +			__func__); +		return false; +	} + +	timers_context_->active = false; +	if (timers_context_->event_loop_thread != 0) { +		/* TODO this does not build +		 * Instead of calling pthread_join() which could block if the +		thread +		 * is blocked, try joining for at most 1 second. +		struct timespec ts; +		clock_gettime(CLOCK_REALTIME, &ts); +		ts.tv_sec += 1; +		int retval = +		pthread_timedjoin_np(timers_context_->event_loop_thread, NULL, +		&ts); if (retval != 0) +		{ +		    pcep_log(LOG_WARNING, "%s: thread did not stop after 1 +		second waiting on it.", __func__); +		} +		*/ +		pthread_join(timers_context_->event_loop_thread, NULL); +	} + +	free_all_timers(timers_context_); +	ordered_list_destroy(timers_context_->timer_list); + +	if (pthread_mutex_destroy(&(timers_context_->timer_list_lock)) != 0) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to teardown the timers, cannot destroy the mutex", +			__func__); +	} + +	pceplib_free(PCEPLIB_INFRA, timers_context_); +	timers_context_ = NULL; + +	return true; +} + + +int get_next_timer_id() +{ +	if (timer_id_ == INT_MAX) { +		timer_id_ = 0; +	} + +	return timer_id_++; +} + +int create_timer(uint16_t sleep_seconds, void *data) +{ +	if (timers_context_ == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to create a timer: the timers have not been initialized", +			__func__); +		return -1; +	} + +	pcep_timer *timer = pceplib_malloc(PCEPLIB_INFRA, sizeof(pcep_timer)); +	memset(timer, 0, sizeof(pcep_timer)); +	timer->data = data; +	timer->sleep_seconds = sleep_seconds; +	timer->expire_time = time(NULL) + sleep_seconds; + +	pthread_mutex_lock(&timers_context_->timer_list_lock); +	timer->timer_id = get_next_timer_id(); + +	/* implemented in pcep_utils_ordered_list.c */ +	if (ordered_list_add_node(timers_context_->timer_list, timer) == NULL) { +		pceplib_free(PCEPLIB_INFRA, timer); +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		pcep_log( +			LOG_WARNING, +			"%s: Trying to create a timer, cannot add the timer to the timer list", +			__func__); + +		return -1; +	} + +	pthread_mutex_unlock(&timers_context_->timer_list_lock); + +	if (timers_context_->timer_create_func) { +		timers_context_->timer_create_func( +			timers_context_->external_timer_infra_data, +			&timer->external_timer, sleep_seconds, timer); +	} + +	return timer->timer_id; +} + + +bool cancel_timer(int timer_id) +{ +	static pcep_timer compare_timer; + +	if (timers_context_ == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to cancel a timer: the timers have not been initialized", +			__func__); +		return false; +	} + +	pthread_mutex_lock(&timers_context_->timer_list_lock); + +	compare_timer.timer_id = timer_id; +	pcep_timer *timer_toRemove = ordered_list_remove_first_node_equals2( +		timers_context_->timer_list, &compare_timer, +		timer_list_node_timer_id_compare); +	if (timer_toRemove == NULL) { +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		pcep_log( +			LOG_WARNING, +			"%s: Trying to cancel a timer [%d] that does not exist", +			__func__, timer_id); +		return false; +	} + +	pthread_mutex_unlock(&timers_context_->timer_list_lock); + +	if (timers_context_->timer_cancel_func) { +		timers_context_->timer_cancel_func( +			&timer_toRemove->external_timer); +	} + +	pceplib_free(PCEPLIB_INFRA, timer_toRemove); + +	return true; +} + + +bool reset_timer(int timer_id) +{ +	static pcep_timer compare_timer; + +	if (timers_context_ == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: Trying to reset a timer: the timers have not been initialized", +			__func__); + +		return false; +	} + +	pthread_mutex_lock(&timers_context_->timer_list_lock); + +	compare_timer.timer_id = timer_id; +	ordered_list_node *timer_to_reset_node = +		ordered_list_find2(timers_context_->timer_list, &compare_timer, +				   timer_list_node_timer_id_compare); +	if (timer_to_reset_node == NULL) { +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		pcep_log(LOG_WARNING, +			 "%s: Trying to reset a timer node that does not exist", +			 __func__); + +		return false; +	} + +	pcep_timer *timer_to_reset = timer_to_reset_node->data; +	if (timer_to_reset == NULL) { +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		pcep_log(LOG_WARNING, +			 "%s: Trying to reset a timer that does not exist", +			 __func__); + +		return false; +	} + +	/* First check if the timer to reset already has the same expire time, +	 * which means multiple reset_timer() calls were made on the same timer +	 * in the same second */ +	time_t expire_time = time(NULL) + timer_to_reset->sleep_seconds; +	if (timer_to_reset->expire_time == expire_time) { +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		return true; +	} + +	ordered_list_remove_node2(timers_context_->timer_list, +				  timer_to_reset_node); + +	timer_to_reset->expire_time = expire_time; +	if (ordered_list_add_node(timers_context_->timer_list, timer_to_reset) +	    == NULL) { +		pceplib_free(PCEPLIB_INFRA, timer_to_reset); +		pthread_mutex_unlock(&timers_context_->timer_list_lock); +		pcep_log( +			LOG_WARNING, +			"%s: Trying to reset a timer, cannot add the timer to the timer list", +			__func__); + +		return false; +	} + +	pthread_mutex_unlock(&timers_context_->timer_list_lock); + +	if (timers_context_->timer_cancel_func) { +		/* Keeping this log for now, since in older versions of FRR the +		 * timer cancellation was blocking. This allows us to see how +		 * long the it takes.*/ +		pcep_log(LOG_DEBUG, "%s: Reseting timer [%d] with callback", +			 __func__, timer_to_reset->timer_id); +		timers_context_->timer_cancel_func( +			&timer_to_reset->external_timer); +		timer_to_reset->external_timer = NULL; +	} + +	if (timers_context_->timer_create_func) { +		timers_context_->timer_create_func( +			timers_context_->external_timer_infra_data, +			&timer_to_reset->external_timer, +			timer_to_reset->sleep_seconds, timer_to_reset); +		/* Keeping this log for now, since in older versions of FRR the +		 * timer cancellation was blocking. This allows us to see how +		 * long the it takes.*/ +		pcep_log(LOG_DEBUG, "%s: Reset timer [%d] with callback", +			 __func__, timer_to_reset->timer_id); +	} + +	return true; +} + + +void pceplib_external_timer_expire_handler(void *data) +{ +	if (timers_context_ == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: External timer expired but timers_context is not initialized", +			__func__); +		return; +	} + +	if (timers_context_->expire_handler == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: External timer expired but expire_handler is not initialized", +			__func__); +		return; +	} + +	if (data == NULL) { +		pcep_log(LOG_WARNING, +			 "%s: External timer expired with NULL data", __func__); +		return; +	} + +	pcep_timer *timer = (pcep_timer *)data; +	pthread_mutex_lock(&timers_context_->timer_list_lock); +	ordered_list_node *timer_node = +		ordered_list_find2(timers_context_->timer_list, timer, +				   timer_list_node_timer_ptr_compare); +	pthread_mutex_unlock(&timers_context_->timer_list_lock); + +	/* Cannot continue if the timer does not exist */ +	if (timer_node == NULL) { +		pcep_log( +			LOG_WARNING, +			"%s: pceplib_external_timer_expire_handler timer [%p] id [%d] does not exist", +			__func__, timer, timer->timer_id); +		return; +	} + +	timers_context_->expire_handler(timer->data, timer->timer_id); + +	pthread_mutex_lock(&timers_context_->timer_list_lock); +	ordered_list_remove_node2(timers_context_->timer_list, timer_node); +	pthread_mutex_unlock(&timers_context_->timer_list_lock); + +	pceplib_free(PCEPLIB_INFRA, timer); +}  | 
