mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 10:47:18 +00:00 
			
		
		
		
	
		
			
	
	
		
			285 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			285 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * Asterisk -- An open source telephony toolkit. | ||
|  |  * | ||
|  |  * Copyright (C) 2021, Sangoma Technologies Corporation | ||
|  |  * | ||
|  |  * Kevin Harwell <kharwell@sangoma.com> | ||
|  |  * | ||
|  |  * See http://www.asterisk.org for more information about
 | ||
|  |  * the Asterisk project. Please do not directly contact | ||
|  |  * any of the maintainers of this project for assistance; | ||
|  |  * the project provides a web site, mailing lists and IRC | ||
|  |  * channels for your use. | ||
|  |  * | ||
|  |  * This program is free software, distributed under the terms of | ||
|  |  * the GNU General Public License Version 2. See the LICENSE file | ||
|  |  * at the top of the source tree. | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "asterisk.h"
 | ||
|  | 
 | ||
|  | #include "asterisk/astobj2.h"
 | ||
|  | #include "asterisk/sched.h"
 | ||
|  | #include "asterisk/utils.h"
 | ||
|  | 
 | ||
|  | #include "asterisk/res_aeap.h"
 | ||
|  | #include "asterisk/res_aeap_message.h"
 | ||
|  | 
 | ||
|  | #include "general.h"
 | ||
|  | #include "logger.h"
 | ||
|  | #include "transaction.h"
 | ||
|  | 
 | ||
|  | struct aeap_transaction { | ||
|  | 	/*! Pointer back to owner object */ | ||
|  | 	struct ast_aeap *aeap; | ||
|  | 	/*! The container this transaction is in */ | ||
|  | 	struct ao2_container *container; | ||
|  | 	/*! Scheduler ID message timeout */ | ||
|  | 	int sched_id; | ||
|  | 	/*! Whether or not the handler has been executed */ | ||
|  | 	int handled; | ||
|  | 	/*! Used to sync matching received messages */ | ||
|  | 	ast_cond_t handled_cond; | ||
|  | 	/*! The result of this transaction */ | ||
|  | 	int result; | ||
|  | 	/*! The timeout data */ | ||
|  | 	struct ast_aeap_tsx_params params; | ||
|  | 	/*! The transaction identifier */ | ||
|  | 	char id[0]; | ||
|  | }; | ||
|  | 
 | ||
|  | /*! \brief Number of transaction buckets */ | ||
|  | #define AEAP_TRANSACTION_BUCKETS 11
 | ||
|  | 
 | ||
|  | AO2_STRING_FIELD_HASH_FN(aeap_transaction, id); | ||
|  | AO2_STRING_FIELD_CMP_FN(aeap_transaction, id); | ||
|  | 
 | ||
|  | int aeap_transaction_cancel_timer(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	if (tsx && tsx->sched_id != -1) { | ||
|  | 		AST_SCHED_DEL_UNREF(aeap_sched_context(), tsx->sched_id, ao2_ref(tsx, -1)); | ||
|  | 		return tsx->sched_id != -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | void aeap_transaction_params_cleanup(struct ast_aeap_tsx_params *params) | ||
|  | { | ||
|  | 	ao2_cleanup(params->msg); | ||
|  | 
 | ||
|  | 	if (params->obj_cleanup) { | ||
|  | 		params->obj_cleanup(params->obj); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | static void transaction_destructor(void *obj) | ||
|  | { | ||
|  | 	struct aeap_transaction *tsx = obj; | ||
|  | 
 | ||
|  | 	/* Ensure timer is canceled */ | ||
|  | 	aeap_transaction_cancel_timer(tsx); | ||
|  | 
 | ||
|  | 	aeap_transaction_params_cleanup(&tsx->params); | ||
|  | 
 | ||
|  | 	ast_cond_destroy(&tsx->handled_cond); | ||
|  | } | ||
|  | 
 | ||
|  | static struct aeap_transaction *transaction_create(const char *id, | ||
|  | 	struct ast_aeap_tsx_params *params, struct ast_aeap *aeap) | ||
|  | { | ||
|  | 	struct aeap_transaction *tsx; | ||
|  | 
 | ||
|  | 	if (!id) { | ||
|  | 		aeap_error(aeap, "transaction", "missing transaction id"); | ||
|  | 		aeap_transaction_params_cleanup(params); | ||
|  | 		return NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	tsx = ao2_alloc(sizeof(*tsx) + strlen(id) + 1, transaction_destructor); | ||
|  | 	if (!tsx) { | ||
|  | 		aeap_error(aeap, "transaction", "unable to create for '%s'", id); | ||
|  | 		aeap_transaction_params_cleanup(params); | ||
|  | 		return NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	strcpy(tsx->id, id); /* safe */ | ||
|  | 	tsx->sched_id = -1; | ||
|  | 
 | ||
|  | 	ast_cond_init(&tsx->handled_cond, NULL); | ||
|  | 
 | ||
|  | 	/*
 | ||
|  | 	 * Currently, transactions, and their lifetimes are fully managed by the given 'aeap' | ||
|  | 	 * object, so do not bump its reference here as we want the 'aeap' object to stop | ||
|  | 	 * transactions and not transactions potentially stopping the 'aeap' object. | ||
|  | 	 */ | ||
|  | 	tsx->aeap = aeap; | ||
|  | 	tsx->params = *params; | ||
|  | 
 | ||
|  | 	return tsx; | ||
|  | } | ||
|  | 
 | ||
|  | static void transaction_end(struct aeap_transaction *tsx, int timed_out, int result) | ||
|  | { | ||
|  | 	if (!tsx) { | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	ao2_lock(tsx); | ||
|  | 
 | ||
|  | 	tsx->result = result; | ||
|  | 
 | ||
|  | 	if (tsx->container) { | ||
|  | 		ao2_unlink(tsx->container, tsx); | ||
|  | 		tsx->container = NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!timed_out) { | ||
|  | 		aeap_transaction_cancel_timer(tsx); | ||
|  | 	} else if (tsx->sched_id != -1) { | ||
|  | 		tsx->sched_id = -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!tsx->handled) { | ||
|  | 		if (timed_out) { | ||
|  | 			if (tsx->params.on_timeout) { | ||
|  | 				tsx->params.on_timeout(tsx->aeap, tsx->params.msg, tsx->params.obj); | ||
|  | 			} else { | ||
|  | 				aeap_error(tsx->aeap, "transaction", "message '%s' timed out", | ||
|  | 					ast_aeap_message_name(tsx->params.msg)); | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		tsx->handled = 1; | ||
|  | 		ast_cond_signal(&tsx->handled_cond); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	ao2_unlock(tsx); | ||
|  | 
 | ||
|  | 	ao2_ref(tsx, -1); | ||
|  | } | ||
|  | 
 | ||
|  | static int transaction_raise_timeout(const void *data) | ||
|  | { | ||
|  | 	/* Ref added added at timer creation removed in end call */ | ||
|  | 	transaction_end((struct aeap_transaction *)data, 1, -1); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static int transaction_sched_timer(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	if (tsx->params.timeout <= 0 || tsx->sched_id != -1) { | ||
|  | 		return 0; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	tsx->sched_id = ast_sched_add(aeap_sched_context(), tsx->params.timeout, | ||
|  | 			transaction_raise_timeout, ao2_bump(tsx)); | ||
|  | 	if (tsx->sched_id == -1) { | ||
|  | 		aeap_error(tsx->aeap, "transaction", "unable to schedule timeout for '%s'", tsx->id); | ||
|  | 		ao2_ref(tsx, -1); | ||
|  | 		return -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static void transaction_wait(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	ao2_lock(tsx); | ||
|  | 
 | ||
|  | 	while (!tsx->handled) { | ||
|  | 		ast_cond_wait(&tsx->handled_cond, ao2_object_get_lockaddr(tsx)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	ao2_unlock(tsx); | ||
|  | } | ||
|  | 
 | ||
|  | int aeap_transaction_start(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	if (transaction_sched_timer(tsx)) { | ||
|  | 		return -1; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (tsx->params.wait) { | ||
|  | 		/* Wait until transaction completes, or times out */ | ||
|  | 		transaction_wait(tsx); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | struct aeap_transaction *aeap_transaction_get(struct ao2_container *transactions, const char *id) | ||
|  | { | ||
|  | 	return ao2_find(transactions, id, OBJ_SEARCH_KEY); | ||
|  | } | ||
|  | 
 | ||
|  | void aeap_transaction_end(struct aeap_transaction *tsx, int result) | ||
|  | { | ||
|  | 	transaction_end(tsx, 0, result); | ||
|  | } | ||
|  | 
 | ||
|  | int aeap_transaction_result(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	return tsx->result; | ||
|  | } | ||
|  | 
 | ||
|  | void *aeap_transaction_user_obj(struct aeap_transaction *tsx) | ||
|  | { | ||
|  | 	return tsx->params.obj; | ||
|  | } | ||
|  | 
 | ||
|  | struct aeap_transaction *aeap_transaction_create_and_add(struct ao2_container *transactions, | ||
|  | 	const char *id, struct ast_aeap_tsx_params *params, struct ast_aeap *aeap) | ||
|  | { | ||
|  | 	struct aeap_transaction *tsx; | ||
|  | 
 | ||
|  | 	tsx = transaction_create(id, params, aeap); | ||
|  | 	if (!tsx) { | ||
|  | 		return NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (!ao2_link(transactions, tsx)) { | ||
|  | 		aeap_error(tsx->aeap, "transaction", "unable to add '%s' to container", id); | ||
|  | 		ao2_ref(tsx, -1); | ||
|  | 		return NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/*
 | ||
|  | 	 * Yes, this creates a circular reference. This reference is removed though | ||
|  | 	 * upon transaction end. It's assumed here that the given transactions container | ||
|  | 	 * takes "ownership", and ultimate responsibility of its contained transactions. | ||
|  | 	 * Thus when the given container needs to be unref'ed/freed it must call | ||
|  | 	 * aeap_transaction_end for each transaction prior to doing so. | ||
|  | 	 */ | ||
|  | 	/* tsx->container = ao2_bump(transactions); */ | ||
|  | 
 | ||
|  | 	/*
 | ||
|  | 	 * The transaction needs to know what container manages it, so it can remove | ||
|  | 	 * itself from the given container under certain conditions (e.g. transaction | ||
|  | 	 * timeout). | ||
|  | 	 * | ||
|  | 	 * It's expected that the given container will out live any contained transaction | ||
|  | 	 * (i.e. the container will not itself be destroyed before ensuring all contained | ||
|  | 	 * transactions are ended, and removed). Thus there is no reason to bump the given | ||
|  | 	 * container's reference here. | ||
|  | 	 */ | ||
|  | 	tsx->container = transactions; | ||
|  | 
 | ||
|  | 	return tsx; | ||
|  | } | ||
|  | 
 | ||
|  | struct ao2_container *aeap_transactions_create(void) | ||
|  | { | ||
|  | 	struct ao2_container *transactions; | ||
|  | 
 | ||
|  | 	transactions = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, AEAP_TRANSACTION_BUCKETS, | ||
|  | 		aeap_transaction_hash_fn, NULL, aeap_transaction_cmp_fn); | ||
|  | 	if (!transactions) { | ||
|  | 		ast_log(LOG_ERROR, "AEAP transaction: unable to create container\n"); | ||
|  | 		return NULL; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return transactions; | ||
|  | } |