Wed Jan 8 2020 09:49:47

Asterisk developer's documentation


func_lock.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Tilghman Lesher
5  *
6  * Tilghman Lesher <func_lock_2007@the-tilghman.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 /*! \file
20  *
21  * \brief Dialplan mutexes
22  *
23  * \author Tilghman Lesher <func_lock_2007@the-tilghman.com>
24  *
25  * \ingroup functions
26  *
27  */
28 
29 /*** MODULEINFO
30  <support_level>core</support_level>
31  ***/
32 
33 #include "asterisk.h"
34 
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 403913 $")
36 
37 #include <signal.h>
38 
39 #include "asterisk/lock.h"
40 #include "asterisk/file.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/linkedlists.h"
45 #include "asterisk/astobj2.h"
46 #include "asterisk/utils.h"
47 
48 /*** DOCUMENTATION
49  <function name="LOCK" language="en_US">
50  <synopsis>
51  Attempt to obtain a named mutex.
52  </synopsis>
53  <syntax>
54  <parameter name="lockname" required="true" />
55  </syntax>
56  <description>
57  <para>Attempts to grab a named lock exclusively, and prevents other channels from
58  obtaining the same lock. LOCK will wait for the lock to become available.
59  Returns <literal>1</literal> if the lock was obtained or <literal>0</literal> on error.</para>
60  <note><para>To avoid the possibility of a deadlock, LOCK will only attempt to
61  obtain the lock for 3 seconds if the channel already has another lock.</para></note>
62  <note>
63  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
64  is set to <literal>no</literal>, this function can only be executed from the
65  dialplan, and not directly from external protocols.</para>
66  </note>
67  </description>
68  </function>
69  <function name="TRYLOCK" language="en_US">
70  <synopsis>
71  Attempt to obtain a named mutex.
72  </synopsis>
73  <syntax>
74  <parameter name="lockname" required="true" />
75  </syntax>
76  <description>
77  <para>Attempts to grab a named lock exclusively, and prevents other channels
78  from obtaining the same lock. Returns <literal>1</literal> if the lock was
79  available or <literal>0</literal> otherwise.</para>
80  <note>
81  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
82  is set to <literal>no</literal>, this function can only be executed from the
83  dialplan, and not directly from external protocols.</para>
84  </note>
85  </description>
86  </function>
87  <function name="UNLOCK" language="en_US">
88  <synopsis>
89  Unlocks a named mutex.
90  </synopsis>
91  <syntax>
92  <parameter name="lockname" required="true" />
93  </syntax>
94  <description>
95  <para>Unlocks a previously locked mutex. Returns <literal>1</literal> if the channel
96  had a lock or <literal>0</literal> otherwise.</para>
97  <note><para>It is generally unnecessary to unlock in a hangup routine, as any locks
98  held are automatically freed when the channel is destroyed.</para></note>
99  <note>
100  <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal>
101  is set to <literal>no</literal>, this function can only be executed from the
102  dialplan, and not directly from external protocols.</para>
103  </note>
104  </description>
105  </function>
106  ***/
107 
108 
109 
111 
112 static void lock_free(void *data);
113 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
114 static int unloading = 0;
115 static pthread_t broker_tid = AST_PTHREADT_NULL;
116 
117 static const struct ast_datastore_info lock_info = {
118  .type = "MUTEX",
119  .destroy = lock_free,
120  .chan_fixup = lock_fixup,
121 };
122 
123 struct lock_frame {
127  /*! count is needed so if a recursive mutex exits early, we know how many times to unlock it. */
128  unsigned int count;
129  /*! Container of requesters for the named lock */
131  /*! who owns us */
133  /*! name of the lock */
134  char name[0];
135 };
136 
138  AST_LIST_ENTRY(channel_lock_frame) list;
139  /*! Need to save channel pointer here, because during destruction, we won't have it. */
140  struct ast_channel *channel;
142 };
143 
144 static void lock_free(void *data)
145 {
146  AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
147  struct channel_lock_frame *clframe;
148  AST_LIST_LOCK(oldlist);
149  while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
150  /* Only unlock if we own the lock */
151  if (clframe->channel == clframe->lock_frame->owner) {
152  clframe->lock_frame->count = 0;
153  clframe->lock_frame->owner = NULL;
154  }
155  ast_free(clframe);
156  }
157  AST_LIST_UNLOCK(oldlist);
158  AST_LIST_HEAD_DESTROY(oldlist);
159  ast_free(oldlist);
160 }
161 
162 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
163 {
164  struct ast_datastore *lock_store = ast_channel_datastore_find(oldchan, &lock_info, NULL);
165  AST_LIST_HEAD(, channel_lock_frame) *list;
166  struct channel_lock_frame *clframe = NULL;
167 
168  if (!lock_store) {
169  return;
170  }
171  list = lock_store->data;
172 
174  AST_LIST_TRAVERSE(list, clframe, list) {
175  if (clframe->lock_frame->owner == oldchan) {
176  clframe->lock_frame->owner = newchan;
177  }
178  /* We don't move requesters, because the thread stack is different */
179  clframe->channel = newchan;
180  }
182 }
183 
184 static void *lock_broker(void *unused)
185 {
186  struct lock_frame *frame;
187  struct timespec forever = { 1000000, 0 };
188  for (;;) {
189  int found_requester = 0;
190 
191  /* Test for cancel outside of the lock */
192  pthread_testcancel();
194 
195  AST_LIST_TRAVERSE(&locklist, frame, entries) {
196  if (ao2_container_count(frame->requesters)) {
197  found_requester++;
198  ast_mutex_lock(&frame->mutex);
199  if (!frame->owner) {
200  ast_cond_signal(&frame->cond);
201  }
202  ast_mutex_unlock(&frame->mutex);
203  }
204  }
205 
207  pthread_testcancel();
208 
209  /* If there are no requesters, then wait for a signal */
210  if (!found_requester) {
211  nanosleep(&forever, NULL);
212  } else {
213  sched_yield();
214  }
215  }
216  /* Not reached */
217  return NULL;
218 }
219 
220 static int ast_channel_hash_cb(const void *obj, const int flags)
221 {
222  const struct ast_channel *chan = obj;
223  return ast_str_case_hash(chan->name);
224 }
225 
226 static int ast_channel_cmp_cb(void *obj, void *arg, int flags)
227 {
228  struct ast_channel *chan = obj, *cmp_args = arg;
229  return strcasecmp(chan->name, cmp_args->name) ? 0 : CMP_MATCH;
230 }
231 
232 static int get_lock(struct ast_channel *chan, char *lockname, int trylock)
233 {
234  struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
235  struct lock_frame *current;
236  struct channel_lock_frame *clframe = NULL;
237  AST_LIST_HEAD(, channel_lock_frame) *list;
238  int res = 0;
239  struct timespec timeout = { 0, };
240  struct timeval now;
241 
242  if (!lock_store) {
243  ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
244  lock_store = ast_datastore_alloc(&lock_info, NULL);
245  if (!lock_store) {
246  ast_log(LOG_ERROR, "Unable to allocate new datastore. No locks will be obtained.\n");
247  return -1;
248  }
249 
250  list = ast_calloc(1, sizeof(*list));
251  if (!list) {
253  "Unable to allocate datastore list head. %sLOCK will fail.\n",
254  trylock ? "TRY" : "");
255  ast_datastore_free(lock_store);
256  return -1;
257  }
258 
259  lock_store->data = list;
260  AST_LIST_HEAD_INIT(list);
261  ast_channel_datastore_add(chan, lock_store);
262  } else
263  list = lock_store->data;
264 
265  /* Lock already exists? */
267  AST_LIST_TRAVERSE(&locklist, current, entries) {
268  if (strcmp(current->name, lockname) == 0) {
269  break;
270  }
271  }
272 
273  if (!current) {
274  if (unloading) {
275  /* Don't bother */
277  return -1;
278  }
279 
280  /* Create new lock entry */
281  current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
282  if (!current) {
284  return -1;
285  }
286 
287  strcpy(current->name, lockname); /* SAFE */
288  if ((res = ast_mutex_init(&current->mutex))) {
289  ast_log(LOG_ERROR, "Unable to initialize mutex: %s\n", strerror(res));
290  ast_free(current);
292  return -1;
293  }
294  if ((res = ast_cond_init(&current->cond, NULL))) {
295  ast_log(LOG_ERROR, "Unable to initialize condition variable: %s\n", strerror(res));
296  ast_mutex_destroy(&current->mutex);
297  ast_free(current);
299  return -1;
300  }
302  ast_mutex_destroy(&current->mutex);
303  ast_cond_destroy(&current->cond);
304  ast_free(current);
306  return -1;
307  }
308  AST_LIST_INSERT_TAIL(&locklist, current, entries);
309  }
311 
312  /* Found lock or created one - now find or create the corresponding link in the channel */
313  AST_LIST_LOCK(list);
314  AST_LIST_TRAVERSE(list, clframe, list) {
315  if (clframe->lock_frame == current) {
316  break;
317  }
318  }
319 
320  if (!clframe) {
321  if (unloading) {
322  /* Don't bother */
323  AST_LIST_UNLOCK(list);
324  return -1;
325  }
326 
327  if (!(clframe = ast_calloc(1, sizeof(*clframe)))) {
329  "Unable to allocate channel lock frame. %sLOCK will fail.\n",
330  trylock ? "TRY" : "");
331  AST_LIST_UNLOCK(list);
332  return -1;
333  }
334 
335  clframe->lock_frame = current;
336  clframe->channel = chan;
337  AST_LIST_INSERT_TAIL(list, clframe, list);
338  }
339  AST_LIST_UNLOCK(list);
340 
341  /* If we already own the lock, then we're being called recursively.
342  * Keep track of how many times that is, because we need to unlock
343  * the same amount, before we'll release this one.
344  */
345  if (current->owner == chan) {
346  current->count++;
347  return 0;
348  }
349 
350  /* Okay, we have both frames, so now we need to try to lock.
351  *
352  * Locking order: always lock locklist first. We need the
353  * locklist lock because the broker thread counts whether
354  * there are requesters with the locklist lock held, and we
355  * need to hold it, so that when we send our signal, below,
356  * to wake up the broker thread, it definitely will see that
357  * a requester exists at that point in time. Otherwise, we
358  * could add to the requesters after it has already seen that
359  * that lock is unoccupied and wait forever for another signal.
360  */
362  ast_mutex_lock(&current->mutex);
363  /* Add to requester list */
364  ao2_link(current->requesters, chan);
365  pthread_kill(broker_tid, SIGURG);
367 
368  /* Wait up to three seconds from now for LOCK. */
369  now = ast_tvnow();
370  timeout.tv_sec = now.tv_sec + 3;
371  timeout.tv_nsec = now.tv_usec * 1000;
372 
373  if (!current->owner
374  || (!trylock
375  && !(res = ast_cond_timedwait(&current->cond, &current->mutex, &timeout)))) {
376  res = 0;
377  current->owner = chan;
378  current->count++;
379  } else {
380  res = -1;
381  }
382  /* Remove from requester list */
383  ao2_unlink(current->requesters, chan);
384  ast_mutex_unlock(&current->mutex);
385 
386  return res;
387 }
388 
389 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
390 {
391  struct ast_datastore *lock_store;
392  struct channel_lock_frame *clframe;
393  AST_LIST_HEAD(, channel_lock_frame) *list;
394 
395  if (!chan) {
396  return -1;
397  }
398 
399  lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
400  if (!lock_store) {
401  ast_log(LOG_WARNING, "No datastore for dialplan locks. Nothing was ever locked!\n");
402  ast_copy_string(buf, "0", len);
403  return 0;
404  }
405 
406  if (!(list = lock_store->data)) {
407  ast_debug(1, "This should NEVER happen\n");
408  ast_copy_string(buf, "0", len);
409  return 0;
410  }
411 
412  /* Find item in the channel list */
414  AST_LIST_TRAVERSE(list, clframe, list) {
415  if (clframe->lock_frame && clframe->lock_frame->owner == chan && strcmp(clframe->lock_frame->name, data) == 0) {
416  break;
417  }
418  }
419  /* We never destroy anything until channel destruction, which will never
420  * happen while this routine is executing, so we don't need to hold the
421  * lock beyond this point. */
423 
424  if (!clframe) {
425  /* We didn't have this lock in the first place */
426  ast_copy_string(buf, "0", len);
427  return 0;
428  }
429 
430  if (--clframe->lock_frame->count == 0) {
431  clframe->lock_frame->owner = NULL;
432  }
433 
434  ast_copy_string(buf, "1", len);
435  return 0;
436 }
437 
438 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
439 {
440  if (!chan) {
441  return -1;
442  }
443  ast_autoservice_start(chan);
444  ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
445  ast_autoservice_stop(chan);
446 
447  return 0;
448 }
449 
450 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
451 {
452  if (!chan) {
453  return -1;
454  }
455  ast_autoservice_start(chan);
456  ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
457  ast_autoservice_stop(chan);
458 
459  return 0;
460 }
461 
463  .name = "LOCK",
464  .read = lock_read,
465  .read_max = 2,
466 };
467 
469  .name = "TRYLOCK",
470  .read = trylock_read,
471  .read_max = 2,
472 };
473 
475  .name = "UNLOCK",
476  .read = unlock_read,
477  .read_max = 2,
478 };
479 
480 static int unload_module(void)
481 {
482  struct lock_frame *current;
483 
484  /* Module flag */
485  unloading = 1;
486 
488  while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
489  /* If any locks are currently in use, then we cannot unload this module */
490  if (current->owner || ao2_container_count(current->requesters)) {
491  /* Put it back */
492  AST_LIST_INSERT_HEAD(&locklist, current, entries);
494  unloading = 0;
495  return -1;
496  }
497  ast_mutex_destroy(&current->mutex);
498  ao2_ref(current->requesters, -1);
499  ast_free(current);
500  }
501 
502  /* No locks left, unregister functions */
503  ast_custom_function_unregister(&lock_function);
504  ast_custom_function_unregister(&trylock_function);
505  ast_custom_function_unregister(&unlock_function);
506 
507  if (broker_tid != AST_PTHREADT_NULL) {
508  pthread_cancel(broker_tid);
509  pthread_kill(broker_tid, SIGURG);
510  pthread_join(broker_tid, NULL);
511  }
512 
514 
515  return 0;
516 }
517 
518 static int load_module(void)
519 {
520  int res = ast_custom_function_register_escalating(&lock_function, AST_CFE_READ);
521  res |= ast_custom_function_register_escalating(&trylock_function, AST_CFE_READ);
522  res |= ast_custom_function_register_escalating(&unlock_function, AST_CFE_READ);
523 
524  if (ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL)) {
525  ast_log(LOG_ERROR, "Failed to start lock broker thread. Unloading func_lock module.\n");
526  broker_tid = AST_PTHREADT_NULL;
527  unload_module();
529  }
530 
531  return res;
532 }
533 
const char * type
Definition: datastore.h:32
Main Channel structure associated with a channel.
Definition: channel.h:742
#define AST_MODULE_INFO_STANDARD(keystr, desc)
Definition: module.h:396
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:39
static int load_module(void)
Definition: func_lock.c:518
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
#define ao2_link(arg1, arg2)
Definition: astobj2.h:785
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
Definition: astobj2.c:470
#define AST_LIST_HEAD(name, type)
Defines a structure to be used to hold a list of specified type.
Definition: linkedlists.h:172
static int unloading
Definition: func_lock.c:114
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:179
struct lock_frame * lock_frame
Definition: func_lock.c:141
#define LOG_WARNING
Definition: logger.h:144
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:139
static struct ast_custom_function trylock_function
Definition: func_lock.c:468
Structure for a data store type.
Definition: datastore.h:31
struct ao2_container * requesters
Definition: func_lock.c:130
#define ast_cond_init(cond, attr)
Definition: lock.h:167
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:142
#define ast_mutex_lock(a)
Definition: lock.h:155
Structure for a data store object.
Definition: datastore.h:54
struct ast_datastore * ast_channel_datastore_find(struct ast_channel *chan, const struct ast_datastore_info *info, const char *uid)
Find a datastore on a channel.
Definition: channel.c:2604
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
struct channel_lock_frame::@135 list
#define ast_cond_signal(cond)
Definition: lock.h:169
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
Definition: pbx.c:3814
int ast_datastore_free(struct ast_datastore *datastore)
Free a data store object.
Definition: datastore.c:65
Utility functions.
pthread_cond_t ast_cond_t
Definition: lock.h:144
#define AST_LIST_HEAD_DESTROY(head)
Destroys a list head structure.
Definition: linkedlists.h:638
static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_lock.c:450
#define ast_pthread_create_background(a, b, c, d)
Definition: utils.h:426
unsigned int flags
Definition: channel.h:850
static int ast_channel_hash_cb(const void *obj, const int flags)
Definition: func_lock.c:220
#define ast_custom_function_register_escalating(acf, escalation)
Register a custom function which requires escalated privileges.
Definition: pbx.h:1173
static void * lock_broker(void *unused)
Definition: func_lock.c:184
#define ast_debug(level,...)
Log a DEBUG message.
Definition: logger.h:236
General Asterisk PBX channel definitions.
#define AST_PTHREADT_NULL
Definition: lock.h:65
Data structure associated with a custom dialplan function.
Definition: pbx.h:95
#define ao2_ref(o, delta)
Definition: astobj2.h:472
A set of macros to manage forward-linked lists.
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:818
Core PBX routines and definitions.
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:238
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:290
#define LOG_ERROR
Definition: logger.h:155
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:716
ast_mutex_t mutex
Definition: func_lock.c:125
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
static int get_lock(struct ast_channel *chan, char *lockname, int trylock)
Definition: func_lock.c:232
const ast_string_field name
Definition: channel.h:787
void ast_log(int level, const char *file, int line, const char *function, const char *fmt,...)
Used for sending a log message This is the standard logger function. Probably the only way you will i...
Definition: logger.c:1207
#define ast_cond_destroy(cond)
Definition: lock.h:168
struct ast_datastore * ast_datastore_alloc(const struct ast_datastore_info *info, const char *uid)
Definition: datastore.c:98
static void lock_free(void *data)
Definition: func_lock.c:144
ast_cond_t cond
Definition: func_lock.c:126
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:490
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:409
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
Definition: linkedlists.h:696
#define AST_LIST_HEAD_INIT(head)
Initializes a list head structure.
Definition: linkedlists.h:611
#define ast_free(a)
Definition: astmm.h:97
static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
Definition: func_lock.c:162
char name[0]
Definition: func_lock.c:134
static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_lock.c:389
static struct ast_custom_function unlock_function
Definition: func_lock.c:474
void * data
Definition: datastore.h:56
#define ast_calloc(a, b)
Definition: astmm.h:82
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:223
#define ao2_container_alloc(arg1, arg2, arg3)
Definition: astobj2.h:734
struct ast_channel * owner
Definition: func_lock.c:132
struct ast_channel * channel
Definition: func_lock.c:140
static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_lock.c:438
static struct ast_datastore_info lock_info
Definition: func_lock.c:117
const char * name
Definition: pbx.h:96
#define ast_mutex_init(pmutex)
Definition: lock.h:152
#define ast_mutex_destroy(a)
Definition: lock.h:154
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:38
Asterisk module definitions.
int ast_channel_datastore_add(struct ast_channel *chan, struct ast_datastore *datastore)
Add a datastore to a channel.
Definition: channel.c:2590
struct lock_frame::@134 entries
static int unload_module(void)
Definition: func_lock.c:480
#define ao2_unlink(arg1, arg2)
Definition: astobj2.h:817
#define ast_cond_timedwait(cond, mutex, time)
Definition: lock.h:172
static struct ast_custom_function lock_function
Definition: func_lock.c:462
unsigned int count
Definition: func_lock.c:128
Structure for mutex and tracking information.
Definition: lock.h:121
static force_inline int attribute_pure ast_str_case_hash(const char *str)
Compute a hash value on a case-insensitive string.
Definition: strings.h:989
static int ast_channel_cmp_cb(void *obj, void *arg, int flags)
Definition: func_lock.c:226
#define ASTERISK_FILE_VERSION(file, version)
Register/unregister a source code file with the core.
Definition: asterisk.h:180
static pthread_t broker_tid
Definition: func_lock.c:115
#define ast_mutex_unlock(a)
Definition: lock.h:156