Wed Aug 18 22:33:52 2010

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

Generated on Wed Aug 18 22:33:52 2010 for Asterisk - the Open Source PBX by  doxygen 1.4.7