Sat Apr 26 2014 22:02:51

Asterisk developer's documentation


func_lock.c File Reference

Dialplan mutexes. More...

#include "asterisk.h"
#include <signal.h>
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
#include "asterisk/astobj2.h"
#include "asterisk/utils.h"
Include dependency graph for func_lock.c:

Go to the source code of this file.

Data Structures

struct  channel_lock_frame
struct  lock_frame
struct  locklist

Functions

static void __reg_module (void)
static void __unreg_module (void)
static int ast_channel_cmp_cb (void *obj, void *arg, int flags)
static int ast_channel_hash_cb (const void *obj, const int flags)
static int get_lock (struct ast_channel *chan, char *lockname, int trylock)
static int load_module (void)
static void * lock_broker (void *unused)
static void lock_fixup (void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
static void lock_free (void *data)
static int lock_read (struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
static int trylock_read (struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
static int unload_module (void)
static int unlock_read (struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)

Variables

static struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Dialplan mutexes" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, }
static struct ast_module_infoast_module_info = &__mod_info
static pthread_t broker_tid = AST_PTHREADT_NULL
static struct ast_custom_function lock_function
static struct ast_datastore_info lock_info
static struct locklist locklist
static struct ast_custom_function trylock_function
static int unloading = 0
static struct ast_custom_function unlock_function

Detailed Description

Dialplan mutexes.

Author:
Tilghman Lesher <func_lock_2007@the-tilghman.com>

Definition in file func_lock.c.


Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 534 of file func_lock.c.

static void __unreg_module ( void  ) [static]

Definition at line 534 of file func_lock.c.

static int ast_channel_cmp_cb ( void *  obj,
void *  arg,
int  flags 
) [static]

Definition at line 226 of file func_lock.c.

References ast_channel_name(), and CMP_MATCH.

Referenced by get_lock().

{
   struct ast_channel *chan = obj, *cmp_args = arg;
   return strcasecmp(ast_channel_name(chan), ast_channel_name(cmp_args)) ? 0 : CMP_MATCH;
}
static int ast_channel_hash_cb ( const void *  obj,
const int  flags 
) [static]

Definition at line 220 of file func_lock.c.

References ast_channel_name(), and ast_str_case_hash().

Referenced by get_lock().

{
   const struct ast_channel *chan = obj;
   return ast_str_case_hash(ast_channel_name(chan));
}
static int get_lock ( struct ast_channel chan,
char *  lockname,
int  trylock 
) [static]

Definition at line 232 of file func_lock.c.

References ao2_container_alloc, ao2_link, ao2_unlink, ast_calloc, ast_channel_cmp_cb(), ast_channel_datastore_add(), ast_channel_datastore_find(), ast_channel_hash_cb(), ast_channel_name(), ast_cond_destroy, ast_cond_init, ast_cond_timedwait, ast_datastore_alloc(), ast_datastore_free(), ast_debug, ast_free, AST_LIST_HEAD, AST_LIST_HEAD_INIT, AST_LIST_INSERT_TAIL, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, ast_log(), ast_mutex_destroy, ast_mutex_init, ast_mutex_lock, ast_mutex_unlock, ast_tvnow(), channel_lock_frame::channel, lock_frame::cond, lock_frame::count, ast_datastore::data, channel_lock_frame::list, channel_lock_frame::lock_frame, LOG_ERROR, lock_frame::mutex, lock_frame::name, lock_frame::owner, and lock_frame::requesters.

Referenced by lock_read(), and trylock_read().

{
   struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
   struct lock_frame *current;
   struct channel_lock_frame *clframe = NULL;
   AST_LIST_HEAD(, channel_lock_frame) *list;
   int res = 0;
   struct timespec timeout = { 0, };
   struct timeval now;

   if (!lock_store) {
      ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", ast_channel_name(chan));
      lock_store = ast_datastore_alloc(&lock_info, NULL);
      if (!lock_store) {
         ast_log(LOG_ERROR, "Unable to allocate new datastore.  No locks will be obtained.\n");
         return -1;
      }

      list = ast_calloc(1, sizeof(*list));
      if (!list) {
         ast_log(LOG_ERROR,
            "Unable to allocate datastore list head.  %sLOCK will fail.\n",
            trylock ? "TRY" : "");
         ast_datastore_free(lock_store);
         return -1;
      }

      lock_store->data = list;
      AST_LIST_HEAD_INIT(list);
      ast_channel_datastore_add(chan, lock_store);
   } else
      list = lock_store->data;

   /* Lock already exists? */
   AST_LIST_LOCK(&locklist);
   AST_LIST_TRAVERSE(&locklist, current, entries) {
      if (strcmp(current->name, lockname) == 0) {
         break;
      }
   }

   if (!current) {
      if (unloading) {
         /* Don't bother */
         AST_LIST_UNLOCK(&locklist);
         return -1;
      }

      /* Create new lock entry */
      current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
      if (!current) {
         AST_LIST_UNLOCK(&locklist);
         return -1;
      }

      strcpy(current->name, lockname); /* SAFE */
      if ((res = ast_mutex_init(&current->mutex))) {
         ast_log(LOG_ERROR, "Unable to initialize mutex: %s\n", strerror(res));
         ast_free(current);
         AST_LIST_UNLOCK(&locklist);
         return -1;
      }
      if ((res = ast_cond_init(&current->cond, NULL))) {
         ast_log(LOG_ERROR, "Unable to initialize condition variable: %s\n", strerror(res));
         ast_mutex_destroy(&current->mutex);
         ast_free(current);
         AST_LIST_UNLOCK(&locklist);
         return -1;
      }
      if (!(current->requesters = ao2_container_alloc(1, ast_channel_hash_cb, ast_channel_cmp_cb))) {
         ast_mutex_destroy(&current->mutex);
         ast_cond_destroy(&current->cond);
         ast_free(current);
         AST_LIST_UNLOCK(&locklist);
         return -1;
      }
      AST_LIST_INSERT_TAIL(&locklist, current, entries);
   }
   AST_LIST_UNLOCK(&locklist);

   /* Found lock or created one - now find or create the corresponding link in the channel */
   AST_LIST_LOCK(list);
   AST_LIST_TRAVERSE(list, clframe, list) {
      if (clframe->lock_frame == current) {
         break;
      }
   }

   if (!clframe) {
      if (unloading) {
         /* Don't bother */
         AST_LIST_UNLOCK(list);
         return -1;
      }

      if (!(clframe = ast_calloc(1, sizeof(*clframe)))) {
         ast_log(LOG_ERROR,
            "Unable to allocate channel lock frame.  %sLOCK will fail.\n",
            trylock ? "TRY" : "");
         AST_LIST_UNLOCK(list);
         return -1;
      }

      clframe->lock_frame = current;
      clframe->channel = chan;
      AST_LIST_INSERT_TAIL(list, clframe, list);
   }
   AST_LIST_UNLOCK(list);

   /* If we already own the lock, then we're being called recursively.
    * Keep track of how many times that is, because we need to unlock
    * the same amount, before we'll release this one.
    */
   if (current->owner == chan) {
      current->count++;
      return 0;
   }

   /* Okay, we have both frames, so now we need to try to lock.
    *
    * Locking order: always lock locklist first.  We need the
    * locklist lock because the broker thread counts whether
    * there are requesters with the locklist lock held, and we
    * need to hold it, so that when we send our signal, below,
    * to wake up the broker thread, it definitely will see that
    * a requester exists at that point in time.  Otherwise, we
    * could add to the requesters after it has already seen that
    * that lock is unoccupied and wait forever for another signal.
    */
   AST_LIST_LOCK(&locklist);
   ast_mutex_lock(&current->mutex);
   /* Add to requester list */
   ao2_link(current->requesters, chan);
   pthread_kill(broker_tid, SIGURG);
   AST_LIST_UNLOCK(&locklist);

   /* Wait up to three seconds from now for LOCK. */
   now = ast_tvnow();
   timeout.tv_sec = now.tv_sec + 3;
   timeout.tv_nsec = now.tv_usec * 1000;

   if (!current->owner
      || (!trylock
         && !(res = ast_cond_timedwait(&current->cond, &current->mutex, &timeout)))) {
      res = 0;
      current->owner = chan;
      current->count++;
   } else {
      res = -1;
   }
   /* Remove from requester list */
   ao2_unlink(current->requesters, chan);
   ast_mutex_unlock(&current->mutex);

   return res;
}
static void* lock_broker ( void *  unused) [static]

Definition at line 184 of file func_lock.c.

References ao2_container_count(), ast_cond_signal, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, ast_mutex_lock, ast_mutex_unlock, lock_frame::cond, lock_frame::mutex, lock_frame::owner, and lock_frame::requesters.

Referenced by load_module().

{
   struct lock_frame *frame;
   struct timespec forever = { 1000000, 0 };
   for (;;) {
      int found_requester = 0;

      /* Test for cancel outside of the lock */
      pthread_testcancel();
      AST_LIST_LOCK(&locklist);

      AST_LIST_TRAVERSE(&locklist, frame, entries) {
         if (ao2_container_count(frame->requesters)) {
            found_requester++;
            ast_mutex_lock(&frame->mutex);
            if (!frame->owner) {
               ast_cond_signal(&frame->cond);
            }
            ast_mutex_unlock(&frame->mutex);
         }
      }

      AST_LIST_UNLOCK(&locklist);
      pthread_testcancel();

      /* If there are no requesters, then wait for a signal */
      if (!found_requester) {
         nanosleep(&forever, NULL);
      } else {
         sched_yield();
      }
   }
   /* Not reached */
   return NULL;
}
static void lock_fixup ( void *  data,
struct ast_channel oldchan,
struct ast_channel newchan 
) [static]

Definition at line 162 of file func_lock.c.

References ast_channel_datastore_find(), AST_LIST_HEAD, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, channel_lock_frame::channel, ast_datastore::data, channel_lock_frame::list, channel_lock_frame::lock_frame, and lock_frame::owner.

{
   struct ast_datastore *lock_store = ast_channel_datastore_find(oldchan, &lock_info, NULL);
   AST_LIST_HEAD(, channel_lock_frame) *list;
   struct channel_lock_frame *clframe = NULL;

   if (!lock_store) {
      return;
   }
   list = lock_store->data;

   AST_LIST_LOCK(list);
   AST_LIST_TRAVERSE(list, clframe, list) {
      if (clframe->lock_frame->owner == oldchan) {
         clframe->lock_frame->owner = newchan;
      }
      /* We don't move requesters, because the thread stack is different */
      clframe->channel = newchan;
   }
   AST_LIST_UNLOCK(list);
}
static void lock_free ( void *  data) [static]

Definition at line 144 of file func_lock.c.

References ast_free, AST_LIST_HEAD, AST_LIST_HEAD_DESTROY, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, channel_lock_frame::channel, lock_frame::count, channel_lock_frame::lock_frame, and lock_frame::owner.

{
   AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
   struct channel_lock_frame *clframe;
   AST_LIST_LOCK(oldlist);
   while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
      /* Only unlock if we own the lock */
      if (clframe->channel == clframe->lock_frame->owner) {
         clframe->lock_frame->count = 0;
         clframe->lock_frame->owner = NULL;
      }
      ast_free(clframe);
   }
   AST_LIST_UNLOCK(oldlist);
   AST_LIST_HEAD_DESTROY(oldlist);
   ast_free(oldlist);
}
static int lock_read ( struct ast_channel chan,
const char *  cmd,
char *  data,
char *  buf,
size_t  len 
) [static]

Definition at line 438 of file func_lock.c.

References ast_autoservice_start(), ast_autoservice_stop(), ast_copy_string(), and get_lock().

{
   if (!chan) {
      return -1;
   }
   ast_autoservice_start(chan);
   ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
   ast_autoservice_stop(chan);

   return 0;
}
static int trylock_read ( struct ast_channel chan,
const char *  cmd,
char *  data,
char *  buf,
size_t  len 
) [static]

Definition at line 450 of file func_lock.c.

References ast_autoservice_start(), ast_autoservice_stop(), ast_copy_string(), and get_lock().

{
   if (!chan) {
      return -1;
   }
   ast_autoservice_start(chan);
   ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
   ast_autoservice_stop(chan);

   return 0;
}
static int unload_module ( void  ) [static]

Definition at line 480 of file func_lock.c.

References ao2_container_count(), ao2_ref, ast_custom_function_unregister(), ast_free, AST_LIST_INSERT_HEAD, AST_LIST_LOCK, AST_LIST_REMOVE_HEAD, AST_LIST_UNLOCK, ast_mutex_destroy, AST_PTHREADT_NULL, lock_frame::mutex, lock_frame::owner, and lock_frame::requesters.

Referenced by load_module().

{
   struct lock_frame *current;

   /* Module flag */
   unloading = 1;

   AST_LIST_LOCK(&locklist);
   while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
      /* If any locks are currently in use, then we cannot unload this module */
      if (current->owner || ao2_container_count(current->requesters)) {
         /* Put it back */
         AST_LIST_INSERT_HEAD(&locklist, current, entries);
         AST_LIST_UNLOCK(&locklist);
         unloading = 0;
         return -1;
      }
      ast_mutex_destroy(&current->mutex);
      ao2_ref(current->requesters, -1);
      ast_free(current);
   }

   /* No locks left, unregister functions */
   ast_custom_function_unregister(&lock_function);
   ast_custom_function_unregister(&trylock_function);
   ast_custom_function_unregister(&unlock_function);

   if (broker_tid != AST_PTHREADT_NULL) {
      pthread_cancel(broker_tid);
      pthread_kill(broker_tid, SIGURG);
      pthread_join(broker_tid, NULL);
   }

   AST_LIST_UNLOCK(&locklist);

   return 0;
}
static int unlock_read ( struct ast_channel chan,
const char *  cmd,
char *  data,
char *  buf,
size_t  len 
) [static]

Definition at line 389 of file func_lock.c.

References ast_channel_datastore_find(), ast_copy_string(), ast_debug, AST_LIST_HEAD, AST_LIST_LOCK, AST_LIST_TRAVERSE, AST_LIST_UNLOCK, ast_log(), lock_frame::count, ast_datastore::data, channel_lock_frame::list, channel_lock_frame::lock_frame, LOG_WARNING, lock_frame::name, and lock_frame::owner.

{
   struct ast_datastore *lock_store;
   struct channel_lock_frame *clframe;
   AST_LIST_HEAD(, channel_lock_frame) *list;

   if (!chan) {
      return -1;
   }

   lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
   if (!lock_store) {
      ast_log(LOG_WARNING, "No datastore for dialplan locks.  Nothing was ever locked!\n");
      ast_copy_string(buf, "0", len);
      return 0;
   }

   if (!(list = lock_store->data)) {
      ast_debug(1, "This should NEVER happen\n");
      ast_copy_string(buf, "0", len);
      return 0;
   }

   /* Find item in the channel list */
   AST_LIST_LOCK(list);
   AST_LIST_TRAVERSE(list, clframe, list) {
      if (clframe->lock_frame && clframe->lock_frame->owner == chan && strcmp(clframe->lock_frame->name, data) == 0) {
         break;
      }
   }
   /* We never destroy anything until channel destruction, which will never
    * happen while this routine is executing, so we don't need to hold the
    * lock beyond this point. */
   AST_LIST_UNLOCK(list);

   if (!clframe) {
      /* We didn't have this lock in the first place */
      ast_copy_string(buf, "0", len);
      return 0;
   }

   if (--clframe->lock_frame->count == 0) {
      clframe->lock_frame->owner = NULL;
   }

   ast_copy_string(buf, "1", len);
   return 0;
}

Variable Documentation

struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_LOAD_ORDER , .description = "Dialplan mutexes" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .load_pri = AST_MODPRI_DEFAULT, } [static]

Definition at line 534 of file func_lock.c.

Definition at line 534 of file func_lock.c.

pthread_t broker_tid = AST_PTHREADT_NULL [static]

Definition at line 115 of file func_lock.c.

Initial value:
 {
   .name = "LOCK",
   .read = lock_read,
   .read_max = 2,
}

Definition at line 462 of file func_lock.c.

struct ast_datastore_info lock_info [static]
Initial value:
 {
   .type = "MUTEX",
   .destroy = lock_free,
   .chan_fixup = lock_fixup,
}

Definition at line 117 of file func_lock.c.

Referenced by dummy_start().

struct locklist locklist [static]
Initial value:
 {
   .name = "TRYLOCK",
   .read = trylock_read,
   .read_max = 2,
}

Definition at line 468 of file func_lock.c.

int unloading = 0 [static]

Definition at line 114 of file func_lock.c.

Initial value:
 {
   .name = "UNLOCK",
   .read = unlock_read,
   .read_max = 2,
}

Definition at line 474 of file func_lock.c.