Mon Mar 12 2012 21:21:17

Asterisk developer's documentation


func_lock.c
Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2007, Tilghman Lesher
00005  *
00006  * Tilghman Lesher <func_lock_2007@the-tilghman.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Dialplan mutexes
00022  *
00023  * \author Tilghman Lesher <func_lock_2007@the-tilghman.com>
00024  *
00025  * \ingroup functions
00026  * 
00027  */
00028 
00029 /*** MODULEINFO
00030    <support_level>core</support_level>
00031  ***/
00032 
00033 #include "asterisk.h"
00034 
00035 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 328209 $")
00036 
00037 #include <signal.h>
00038 
00039 #include "asterisk/lock.h"
00040 #include "asterisk/file.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/pbx.h"
00043 #include "asterisk/module.h"
00044 #include "asterisk/linkedlists.h"
00045 #include "asterisk/astobj2.h"
00046 #include "asterisk/utils.h"
00047 
00048 /*** DOCUMENTATION
00049    <function name="LOCK" language="en_US">
00050       <synopsis>
00051          Attempt to obtain a named mutex.
00052       </synopsis>
00053       <syntax>
00054          <parameter name="lockname" required="true" />
00055       </syntax>
00056       <description>
00057          <para>Attempts to grab a named lock exclusively, and prevents other channels from
00058          obtaining the same lock.  LOCK will wait for the lock to become available.
00059          Returns <literal>1</literal> if the lock was obtained or <literal>0</literal> on error.</para>
00060          <note><para>To avoid the possibility of a deadlock, LOCK will only attempt to
00061          obtain the lock for 3 seconds if the channel already has another lock.</para></note>
00062       </description>
00063    </function>
00064    <function name="TRYLOCK" language="en_US">
00065       <synopsis>
00066          Attempt to obtain a named mutex.
00067       </synopsis>
00068       <syntax>
00069          <parameter name="lockname" required="true" />
00070       </syntax>
00071       <description>
00072          <para>Attempts to grab a named lock exclusively, and prevents other channels
00073          from obtaining the same lock.  Returns <literal>1</literal> if the lock was 
00074          available or <literal>0</literal> otherwise.</para>
00075       </description>
00076    </function>
00077    <function name="UNLOCK" language="en_US">
00078       <synopsis>
00079          Unlocks a named mutex.
00080       </synopsis>
00081       <syntax>
00082          <parameter name="lockname" required="true" />
00083       </syntax>
00084       <description>
00085          <para>Unlocks a previously locked mutex. Returns <literal>1</literal> if the channel 
00086          had a lock or <literal>0</literal> otherwise.</para>
00087          <note><para>It is generally unnecessary to unlock in a hangup routine, as any locks 
00088          held are automatically freed when the channel is destroyed.</para></note>
00089       </description>
00090    </function>
00091  ***/
00092 
00093 
00094 
00095 static AST_LIST_HEAD_STATIC(locklist, lock_frame);
00096 
00097 static void lock_free(void *data);
00098 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan);
00099 static int unloading = 0;
00100 static pthread_t broker_tid = AST_PTHREADT_NULL;
00101 
00102 static struct ast_datastore_info lock_info = {
00103    .type = "MUTEX",
00104    .destroy = lock_free,
00105    .chan_fixup = lock_fixup,
00106 };
00107 
00108 struct lock_frame {
00109    AST_LIST_ENTRY(lock_frame) entries;
00110    ast_mutex_t mutex;
00111    ast_cond_t cond;
00112    /*! count is needed so if a recursive mutex exits early, we know how many times to unlock it. */
00113    unsigned int count;
00114    /*! Container of requesters for the named lock */
00115    struct ao2_container *requesters;
00116    /*! who owns us */
00117    struct ast_channel *owner;
00118    /*! name of the lock */
00119    char name[0];
00120 };
00121 
00122 struct channel_lock_frame {
00123    AST_LIST_ENTRY(channel_lock_frame) list;
00124    /*! Need to save channel pointer here, because during destruction, we won't have it. */
00125    struct ast_channel *channel;
00126    struct lock_frame *lock_frame;
00127 };
00128 
00129 static void lock_free(void *data)
00130 {
00131    AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
00132    struct channel_lock_frame *clframe;
00133    AST_LIST_LOCK(oldlist);
00134    while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
00135       /* Only unlock if we own the lock */
00136       if (clframe->channel == clframe->lock_frame->owner) {
00137          clframe->lock_frame->count = 0;
00138          clframe->lock_frame->owner = NULL;
00139       }
00140       ast_free(clframe);
00141    }
00142    AST_LIST_UNLOCK(oldlist);
00143    AST_LIST_HEAD_DESTROY(oldlist);
00144    ast_free(oldlist);
00145 }
00146 
00147 static void lock_fixup(void *data, struct ast_channel *oldchan, struct ast_channel *newchan)
00148 {
00149    struct ast_datastore *lock_store = ast_channel_datastore_find(oldchan, &lock_info, NULL);
00150    AST_LIST_HEAD(, channel_lock_frame) *list;
00151    struct channel_lock_frame *clframe = NULL;
00152 
00153    if (!lock_store) {
00154       return;
00155    }
00156    list = lock_store->data;
00157 
00158    AST_LIST_LOCK(list);
00159    AST_LIST_TRAVERSE(list, clframe, list) {
00160       if (clframe->lock_frame->owner == oldchan) {
00161          clframe->lock_frame->owner = newchan;
00162       }
00163       /* We don't move requesters, because the thread stack is different */
00164       clframe->channel = newchan;
00165    }
00166    AST_LIST_UNLOCK(list);
00167 }
00168 
00169 static void *lock_broker(void *unused)
00170 {
00171    struct lock_frame *frame;
00172    struct timespec forever = { 1000000, 0 };
00173    for (;;) {
00174       int found_requester = 0;
00175 
00176       /* Test for cancel outside of the lock */
00177       pthread_testcancel();
00178       AST_LIST_LOCK(&locklist);
00179 
00180       AST_LIST_TRAVERSE(&locklist, frame, entries) {
00181          if (ao2_container_count(frame->requesters)) {
00182             found_requester++;
00183             ast_mutex_lock(&frame->mutex);
00184             if (!frame->owner) {
00185                ast_cond_signal(&frame->cond);
00186             }
00187             ast_mutex_unlock(&frame->mutex);
00188          }
00189       }
00190 
00191       AST_LIST_UNLOCK(&locklist);
00192       pthread_testcancel();
00193 
00194       /* If there are no requesters, then wait for a signal */
00195       if (!found_requester) {
00196          nanosleep(&forever, NULL);
00197       } else {
00198          sched_yield();
00199       }
00200    }
00201    /* Not reached */
00202    return NULL;
00203 }
00204 
00205 static int ast_channel_hash_cb(const void *obj, const int flags)
00206 {
00207    const struct ast_channel *chan = obj;
00208    return ast_str_case_hash(chan->name);
00209 }
00210 
00211 static int ast_channel_cmp_cb(void *obj, void *arg, int flags)
00212 {
00213    struct ast_channel *chan = obj, *cmp_args = arg;
00214    return strcasecmp(chan->name, cmp_args->name) ? 0 : CMP_MATCH;
00215 }
00216 
00217 static int get_lock(struct ast_channel *chan, char *lockname, int try)
00218 {
00219    struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00220    struct lock_frame *current;
00221    struct channel_lock_frame *clframe = NULL;
00222    AST_LIST_HEAD(, channel_lock_frame) *list;
00223    int res = 0;
00224    struct timespec three_seconds = { .tv_sec = 3 };
00225 
00226    if (!lock_store) {
00227       ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
00228       lock_store = ast_datastore_alloc(&lock_info, NULL);
00229       if (!lock_store) {
00230          ast_log(LOG_ERROR, "Unable to allocate new datastore.  No locks will be obtained.\n");
00231          return -1;
00232       }
00233 
00234       list = ast_calloc(1, sizeof(*list));
00235       if (!list) {
00236          ast_log(LOG_ERROR, "Unable to allocate datastore list head.  %sLOCK will fail.\n", try ? "TRY" : "");
00237          ast_datastore_free(lock_store);
00238          return -1;
00239       }
00240 
00241       lock_store->data = list;
00242       AST_LIST_HEAD_INIT(list);
00243       ast_channel_datastore_add(chan, lock_store);
00244    } else
00245       list = lock_store->data;
00246 
00247    /* Lock already exists? */
00248    AST_LIST_LOCK(&locklist);
00249    AST_LIST_TRAVERSE(&locklist, current, entries) {
00250       if (strcmp(current->name, lockname) == 0) {
00251          break;
00252       }
00253    }
00254 
00255    if (!current) {
00256       if (unloading) {
00257          /* Don't bother */
00258          AST_LIST_UNLOCK(&locklist);
00259          return -1;
00260       }
00261 
00262       /* Create new lock entry */
00263       current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
00264       if (!current) {
00265          AST_LIST_UNLOCK(&locklist);
00266          return -1;
00267       }
00268 
00269       strcpy(current->name, lockname); /* SAFE */
00270       if ((res = ast_mutex_init(&current->mutex))) {
00271          ast_log(LOG_ERROR, "Unable to initialize mutex: %s\n", strerror(res));
00272          ast_free(current);
00273          AST_LIST_UNLOCK(&locklist);
00274          return -1;
00275       }
00276       if ((res = ast_cond_init(&current->cond, NULL))) {
00277          ast_log(LOG_ERROR, "Unable to initialize condition variable: %s\n", strerror(res));
00278          ast_mutex_destroy(&current->mutex);
00279          ast_free(current);
00280          AST_LIST_UNLOCK(&locklist);
00281          return -1;
00282       }
00283       if (!(current->requesters = ao2_container_alloc(1, ast_channel_hash_cb, ast_channel_cmp_cb))) {
00284          ast_mutex_destroy(&current->mutex);
00285          ast_cond_destroy(&current->cond);
00286          ast_free(current);
00287          AST_LIST_UNLOCK(&locklist);
00288          return -1;
00289       }
00290       AST_LIST_INSERT_TAIL(&locklist, current, entries);
00291    }
00292    AST_LIST_UNLOCK(&locklist);
00293 
00294    /* Found lock or created one - now find or create the corresponding link in the channel */
00295    AST_LIST_LOCK(list);
00296    AST_LIST_TRAVERSE(list, clframe, list) {
00297       if (clframe->lock_frame == current) {
00298          break;
00299       }
00300    }
00301 
00302    if (!clframe) {
00303       if (unloading) {
00304          /* Don't bother */
00305          AST_LIST_UNLOCK(list);
00306          return -1;
00307       }
00308 
00309       if (!(clframe = ast_calloc(1, sizeof(*clframe)))) {
00310          ast_log(LOG_ERROR, "Unable to allocate channel lock frame.  %sLOCK will fail.\n", try ? "TRY" : "");
00311          AST_LIST_UNLOCK(list);
00312          return -1;
00313       }
00314 
00315       clframe->lock_frame = current;
00316       clframe->channel = chan;
00317       AST_LIST_INSERT_TAIL(list, clframe, list);
00318    }
00319    AST_LIST_UNLOCK(list);
00320 
00321    /* If we already own the lock, then we're being called recursively.
00322     * Keep track of how many times that is, because we need to unlock
00323     * the same amount, before we'll release this one.
00324     */
00325    if (current->owner == chan) {
00326       current->count++;
00327       return 0;
00328    }
00329 
00330    /* Okay, we have both frames, so now we need to try to lock.
00331     *
00332     * Locking order: always lock locklist first.  We need the
00333     * locklist lock because the broker thread counts whether
00334     * there are requesters with the locklist lock held, and we
00335     * need to hold it, so that when we send our signal, below,
00336     * to wake up the broker thread, it definitely will see that
00337     * a requester exists at that point in time.  Otherwise, we
00338     * could add to the requesters after it has already seen that
00339     * that lock is unoccupied and wait forever for another signal.
00340     */
00341    AST_LIST_LOCK(&locklist);
00342    ast_mutex_lock(&current->mutex);
00343    /* Add to requester list */
00344    ao2_link(current->requesters, chan);
00345    pthread_kill(broker_tid, SIGURG);
00346    AST_LIST_UNLOCK(&locklist);
00347 
00348    if ((!current->owner) ||
00349       (!try && !(res = ast_cond_timedwait(&current->cond, &current->mutex, &three_seconds)))) {
00350       res = 0;
00351       current->owner = chan;
00352       current->count++;
00353    } else {
00354       res = -1;
00355    }
00356    /* Remove from requester list */
00357    ao2_unlink(current->requesters, chan);
00358    ast_mutex_unlock(&current->mutex);
00359 
00360    return res;
00361 }
00362 
00363 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00364 {
00365    struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
00366    struct channel_lock_frame *clframe;
00367    AST_LIST_HEAD(, channel_lock_frame) *list;
00368 
00369    if (!lock_store) {
00370       ast_log(LOG_WARNING, "No datastore for dialplan locks.  Nothing was ever locked!\n");
00371       ast_copy_string(buf, "0", len);
00372       return 0;
00373    }
00374 
00375    if (!(list = lock_store->data)) {
00376       ast_debug(1, "This should NEVER happen\n");
00377       ast_copy_string(buf, "0", len);
00378       return 0;
00379    }
00380 
00381    /* Find item in the channel list */
00382    AST_LIST_LOCK(list);
00383    AST_LIST_TRAVERSE(list, clframe, list) {
00384       if (clframe->lock_frame && clframe->lock_frame->owner == chan && strcmp(clframe->lock_frame->name, data) == 0) {
00385          break;
00386       }
00387    }
00388    /* We never destroy anything until channel destruction, which will never
00389     * happen while this routine is executing, so we don't need to hold the
00390     * lock beyond this point. */
00391    AST_LIST_UNLOCK(list);
00392 
00393    if (!clframe) {
00394       /* We didn't have this lock in the first place */
00395       ast_copy_string(buf, "0", len);
00396       return 0;
00397    }
00398 
00399    if (--clframe->lock_frame->count == 0) {
00400       clframe->lock_frame->owner = NULL;
00401    }
00402 
00403    ast_copy_string(buf, "1", len);
00404    return 0;
00405 }
00406 
00407 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00408 {
00409    if (chan)
00410       ast_autoservice_start(chan);
00411 
00412    ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
00413 
00414    if (chan)
00415       ast_autoservice_stop(chan);
00416 
00417    return 0;
00418 }
00419 
00420 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
00421 {
00422    if (chan)
00423       ast_autoservice_start(chan);
00424 
00425    ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
00426 
00427    if (chan)
00428       ast_autoservice_stop(chan);
00429 
00430    return 0;
00431 }
00432 
00433 static struct ast_custom_function lock_function = {
00434    .name = "LOCK",
00435    .read = lock_read,
00436    .read_max = 2,
00437 };
00438 
00439 static struct ast_custom_function trylock_function = {
00440    .name = "TRYLOCK",
00441    .read = trylock_read,
00442    .read_max = 2,
00443 };
00444 
00445 static struct ast_custom_function unlock_function = {
00446    .name = "UNLOCK",
00447    .read = unlock_read,
00448    .read_max = 2,
00449 };
00450 
00451 static int unload_module(void)
00452 {
00453    struct lock_frame *current;
00454 
00455    /* Module flag */
00456    unloading = 1;
00457 
00458    AST_LIST_LOCK(&locklist);
00459    while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
00460       /* If any locks are currently in use, then we cannot unload this module */
00461       if (current->owner || ao2_container_count(current->requesters)) {
00462          /* Put it back */
00463          AST_LIST_INSERT_HEAD(&locklist, current, entries);
00464          AST_LIST_UNLOCK(&locklist);
00465          unloading = 0;
00466          return -1;
00467       }
00468       ast_mutex_destroy(&current->mutex);
00469       ao2_ref(current->requesters, -1);
00470       ast_free(current);
00471    }
00472 
00473    /* No locks left, unregister functions */
00474    ast_custom_function_unregister(&lock_function);
00475    ast_custom_function_unregister(&trylock_function);
00476    ast_custom_function_unregister(&unlock_function);
00477 
00478    pthread_cancel(broker_tid);
00479    pthread_kill(broker_tid, SIGURG);
00480    pthread_join(broker_tid, NULL);
00481 
00482    AST_LIST_UNLOCK(&locklist);
00483 
00484    return 0;
00485 }
00486 
00487 static int load_module(void)
00488 {
00489    int res = ast_custom_function_register(&lock_function);
00490    res |= ast_custom_function_register(&trylock_function);
00491    res |= ast_custom_function_register(&unlock_function);
00492    ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL);
00493    return res;
00494 }
00495 
00496 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");