Asterisk - The Open Source Telephony Project  21.4.1
transaction.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2021, Sangoma Technologies Corporation
5  *
6  * Kevin Harwell <kharwell@sangoma.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 #include "asterisk.h"
20 
21 #include "asterisk/astobj2.h"
22 #include "asterisk/sched.h"
23 #include "asterisk/utils.h"
24 
25 #include "asterisk/res_aeap.h"
27 
28 #include "general.h"
29 #include "logger.h"
30 #include "transaction.h"
31 
33  /*! Pointer back to owner object */
34  struct ast_aeap *aeap;
35  /*! The container this transaction is in */
37  /*! Scheduler ID message timeout */
38  int sched_id;
39  /*! Whether or not the handler has been executed */
40  int handled;
41  /*! Used to sync matching received messages */
42  ast_cond_t handled_cond;
43  /*! The result of this transaction */
44  int result;
45  /*! The timeout data */
47  /*! The transaction identifier */
48  char id[0];
49 };
50 
51 /*! \brief Number of transaction buckets */
52 #define AEAP_TRANSACTION_BUCKETS 11
53 
56 
57 int aeap_transaction_cancel_timer(struct aeap_transaction *tsx)
58 {
59  if (tsx && tsx->sched_id != -1) {
60  AST_SCHED_DEL_UNREF(aeap_sched_context(), tsx->sched_id, ao2_ref(tsx, -1));
61  return tsx->sched_id != -1;
62  }
63 
64  return 0;
65 }
66 
67 void aeap_transaction_params_cleanup(struct ast_aeap_tsx_params *params)
68 {
69  ao2_cleanup(params->msg);
70 
71  if (params->obj_cleanup) {
72  params->obj_cleanup(params->obj);
73  }
74 }
75 
76 static void transaction_destructor(void *obj)
77 {
78  struct aeap_transaction *tsx = obj;
79 
80  /* Ensure timer is canceled */
81  aeap_transaction_cancel_timer(tsx);
82 
83  aeap_transaction_params_cleanup(&tsx->params);
84 
85  ast_cond_destroy(&tsx->handled_cond);
86 }
87 
88 static struct aeap_transaction *transaction_create(const char *id,
89  struct ast_aeap_tsx_params *params, struct ast_aeap *aeap)
90 {
91  struct aeap_transaction *tsx;
92 
93  if (!id) {
94  aeap_error(aeap, "transaction", "missing transaction id");
95  aeap_transaction_params_cleanup(params);
96  return NULL;
97  }
98 
99  tsx = ao2_alloc(sizeof(*tsx) + strlen(id) + 1, transaction_destructor);
100  if (!tsx) {
101  aeap_error(aeap, "transaction", "unable to create for '%s'", id);
102  aeap_transaction_params_cleanup(params);
103  return NULL;
104  }
105 
106  strcpy(tsx->id, id); /* safe */
107  tsx->sched_id = -1;
108 
109  ast_cond_init(&tsx->handled_cond, NULL);
110 
111  /*
112  * Currently, transactions, and their lifetimes are fully managed by the given 'aeap'
113  * object, so do not bump its reference here as we want the 'aeap' object to stop
114  * transactions and not transactions potentially stopping the 'aeap' object.
115  */
116  tsx->aeap = aeap;
117  tsx->params = *params;
118 
119  return tsx;
120 }
121 
122 static void transaction_end(struct aeap_transaction *tsx, int timed_out, int result)
123 {
124  if (!tsx) {
125  return;
126  }
127 
128  ao2_lock(tsx);
129 
130  tsx->result = result;
131 
132  if (tsx->container) {
133  ao2_unlink(tsx->container, tsx);
134  tsx->container = NULL;
135  }
136 
137  if (!timed_out) {
138  aeap_transaction_cancel_timer(tsx);
139  } else if (tsx->sched_id != -1) {
140  tsx->sched_id = -1;
141  }
142 
143  if (!tsx->handled) {
144  if (timed_out) {
145  if (tsx->params.on_timeout) {
146  tsx->params.on_timeout(tsx->aeap, tsx->params.msg, tsx->params.obj);
147  } else {
148  aeap_error(tsx->aeap, "transaction", "message '%s' timed out",
150  }
151  }
152 
153  tsx->handled = 1;
154  ast_cond_signal(&tsx->handled_cond);
155  }
156 
157  ao2_unlock(tsx);
158 
159  ao2_ref(tsx, -1);
160 }
161 
162 static int transaction_raise_timeout(const void *data)
163 {
164  /* Ref added added at timer creation removed in end call */
165  transaction_end((struct aeap_transaction *)data, 1, -1);
166 
167  return 0;
168 }
169 
170 static int transaction_sched_timer(struct aeap_transaction *tsx)
171 {
172  if (tsx->params.timeout <= 0 || tsx->sched_id != -1) {
173  return 0;
174  }
175 
176  tsx->sched_id = ast_sched_add(aeap_sched_context(), tsx->params.timeout,
177  transaction_raise_timeout, ao2_bump(tsx));
178  if (tsx->sched_id == -1) {
179  aeap_error(tsx->aeap, "transaction", "unable to schedule timeout for '%s'", tsx->id);
180  ao2_ref(tsx, -1);
181  return -1;
182  }
183 
184  return 0;
185 }
186 
187 static void transaction_wait(struct aeap_transaction *tsx)
188 {
189  ao2_lock(tsx);
190 
191  while (!tsx->handled) {
192  ast_cond_wait(&tsx->handled_cond, ao2_object_get_lockaddr(tsx));
193  }
194 
195  ao2_unlock(tsx);
196 }
197 
198 int aeap_transaction_start(struct aeap_transaction *tsx)
199 {
200  if (transaction_sched_timer(tsx)) {
201  return -1;
202  }
203 
204  if (tsx->params.wait) {
205  /* Wait until transaction completes, or times out */
206  transaction_wait(tsx);
207  }
208 
209  return 0;
210 }
211 
212 struct aeap_transaction *aeap_transaction_get(struct ao2_container *transactions, const char *id)
213 {
214  return ao2_find(transactions, id, OBJ_SEARCH_KEY);
215 }
216 
217 void aeap_transaction_end(struct aeap_transaction *tsx, int result)
218 {
219  transaction_end(tsx, 0, result);
220 }
221 
222 int aeap_transaction_result(struct aeap_transaction *tsx)
223 {
224  return tsx->result;
225 }
226 
227 void *aeap_transaction_user_obj(struct aeap_transaction *tsx)
228 {
229  return tsx->params.obj;
230 }
231 
232 struct aeap_transaction *aeap_transaction_create_and_add(struct ao2_container *transactions,
233  const char *id, struct ast_aeap_tsx_params *params, struct ast_aeap *aeap)
234 {
235  struct aeap_transaction *tsx;
236 
237  tsx = transaction_create(id, params, aeap);
238  if (!tsx) {
239  return NULL;
240  }
241 
242  if (!ao2_link(transactions, tsx)) {
243  aeap_error(tsx->aeap, "transaction", "unable to add '%s' to container", id);
244  ao2_ref(tsx, -1);
245  return NULL;
246  }
247 
248  /*
249  * Yes, this creates a circular reference. This reference is removed though
250  * upon transaction end. It's assumed here that the given transactions container
251  * takes "ownership", and ultimate responsibility of its contained transactions.
252  * Thus when the given container needs to be unref'ed/freed it must call
253  * aeap_transaction_end for each transaction prior to doing so.
254  */
255  /* tsx->container = ao2_bump(transactions); */
256 
257  /*
258  * The transaction needs to know what container manages it, so it can remove
259  * itself from the given container under certain conditions (e.g. transaction
260  * timeout).
261  *
262  * It's expected that the given container will out live any contained transaction
263  * (i.e. the container will not itself be destroyed before ensuring all contained
264  * transactions are ended, and removed). Thus there is no reason to bump the given
265  * container's reference here.
266  */
267  tsx->container = transactions;
268 
269  return tsx;
270 }
271 
272 struct ao2_container *aeap_transactions_create(void)
273 {
274  struct ao2_container *transactions;
275 
276  transactions = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, AEAP_TRANSACTION_BUCKETS,
277  aeap_transaction_hash_fn, NULL, aeap_transaction_cmp_fn);
278  if (!transactions) {
279  ast_log(LOG_ERROR, "AEAP transaction: unable to create container\n");
280  return NULL;
281  }
282 
283  return transactions;
284 }
Asterisk External Application Protocol API.
Asterisk main include file. File version handling, generic pbx functions.
AO2_STRING_FIELD_HASH_FN(transport_monitor, key)
Hashing function for struct transport_monitor.
Asterisk External Application Protocol Message API.
The arg parameter is a search key, but is not an object.
Definition: astobj2.h:1101
struct ao2_container * container
Definition: transaction.c:36
AO2_STRING_FIELD_CMP_FN(transport_monitor, key)
Comparison function for struct transport_monitor.
#define AST_SCHED_DEL_UNREF(sched, id, refcall)
schedule task to get deleted and call unref function
Definition: sched.h:82
Parameters to be used when sending a transaction based message.
Definition: res_aeap.h:331
Utility functions.
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
struct ast_aeap * aeap
Definition: transaction.c:34
void * ao2_object_get_lockaddr(void *obj)
Return the mutex lock address of an object.
Definition: astobj2.c:476
ast_aeap_user_obj_cleanup obj_cleanup
Definition: res_aeap.h:350
ast_aeap_on_timeout on_timeout
Definition: res_aeap.h:337
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
Scheduler Routines (derived from cheops)
const char * ast_aeap_message_name(const struct ast_aeap_message *message)
Retrieve a message name.
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition: astobj2.h:1303
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition: astobj2.h:1578
struct ast_aeap_tsx_params params
Definition: transaction.c:46
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition: sched.c:567
struct ast_aeap_message * msg
Definition: res_aeap.h:333
Generic container type.
ast_cond_t handled_cond
Definition: transaction.c:42
Definition: aeap.c:47
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532