Newer
Older
Tilghman Lesher
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2007, Tilghman Lesher
*
* Tilghman Lesher <func_lock_2007@the-tilghman.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief Dialplan mutexes
*
* \author Tilghman Lesher <func_lock_2007@the-tilghman.com>
*
* \ingroup functions
*
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/lock.h"
#include "asterisk/file.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/linkedlists.h"
AST_LIST_HEAD_STATIC(locklist, lock_frame);
static void lock_free(void *data);
static int unloading = 0;
static struct ast_datastore_info lock_info = {
.type = "MUTEX",
.destroy = lock_free,
};
struct lock_frame {
AST_LIST_ENTRY(lock_frame) entries;
ast_mutex_t mutex;
Tilghman Lesher
committed
/*! count is needed so if a recursive mutex exits early, we know how many times to unlock it. */
unsigned int count;
/*! who owns us */
Tilghman Lesher
committed
struct ast_channel *channel;
Tilghman Lesher
committed
/*! name of the lock */
Tilghman Lesher
committed
char name[0];
};
Tilghman Lesher
committed
struct channel_lock_frame {
AST_LIST_ENTRY(channel_lock_frame) list;
/*! Need to save channel pointer here, because during destruction, we won't have it. */
struct ast_channel *channel;
struct lock_frame *lock_frame;
};
Tilghman Lesher
committed
static void lock_free(void *data)
{
Tilghman Lesher
committed
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->channel) {
clframe->lock_frame->channel = NULL;
while (clframe->lock_frame->count > 0) {
clframe->lock_frame->count--;
ast_mutex_unlock(&clframe->lock_frame->mutex);
}
}
ast_free(clframe);
}
AST_LIST_UNLOCK(oldlist);
AST_LIST_HEAD_DESTROY(oldlist);
ast_free(oldlist);
Tilghman Lesher
committed
}
static int get_lock(struct ast_channel *chan, char *lockname, int try)
{
struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
struct lock_frame *current;
Tilghman Lesher
committed
struct channel_lock_frame *clframe = NULL, *save_clframe = NULL;
Tilghman Lesher
committed
AST_LIST_HEAD(, channel_lock_frame) *list;
int res, count_channel_locks = 0;
Tilghman Lesher
committed
if (!lock_store) {
ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
Kevin P. Fleming
committed
lock_store = ast_datastore_alloc(&lock_info, NULL);
Tilghman Lesher
committed
if (!lock_store) {
ast_log(LOG_ERROR, "Unable to allocate new datastore. No locks will be obtained.\n");
return -1;
}
Tilghman Lesher
committed
list = ast_calloc(1, sizeof(*list));
if (!list) {
ast_log(LOG_ERROR, "Unable to allocate datastore list head. %sLOCK will fail.\n", try ? "TRY" : "");
Kevin P. Fleming
committed
ast_datastore_free(lock_store);
Tilghman Lesher
committed
return -1;
}
Tilghman Lesher
committed
Tilghman Lesher
committed
lock_store->data = list;
AST_LIST_HEAD_INIT(list);
ast_channel_datastore_add(chan, lock_store);
} else
list = lock_store->data;
/* Lock already exists? */
Tilghman Lesher
committed
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((char *)current + sizeof(*current), lockname);
ast_mutex_init(¤t->mutex);
AST_LIST_INSERT_TAIL(&locklist, current, entries);
}
Tilghman Lesher
committed
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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)
save_clframe = clframe;
/* Only count mutexes that we currently hold */
if (clframe->lock_frame->channel == chan)
count_channel_locks++;
}
if (save_clframe) {
clframe = save_clframe;
} else {
if (unloading) {
/* Don't bother */
AST_LIST_UNLOCK(list);
return -1;
}
clframe = ast_calloc(1, sizeof(*clframe));
if (!clframe) {
ast_log(LOG_ERROR, "Unable to allocate channel lock frame. %sLOCK will fail.\n", try ? "TRY" : "");
AST_LIST_UNLOCK(list);
return -1;
}
clframe->lock_frame = current;
clframe->channel = chan;
/* Count the lock just created */
count_channel_locks++;
AST_LIST_INSERT_TAIL(list, clframe, list);
}
AST_LIST_UNLOCK(list);
/* Okay, we have both frames, so now we need to try to lock the mutex. */
if (count_channel_locks > 1) {
Russell Bryant
committed
struct timeval start = ast_tvnow();
for (;;) {
Tilghman Lesher
committed
if ((res = ast_mutex_trylock(¤t->mutex)) == 0)
break;
Russell Bryant
committed
if (ast_tvdiff_ms(ast_tvnow(), start) > 3000)
break; /* bail after 3 seconds of waiting */
Tilghman Lesher
committed
usleep(1);
}
} else {
/* If the channel doesn't have any locks so far, then there's no possible deadlock. */
res = try ? ast_mutex_trylock(¤t->mutex) : ast_mutex_lock(¤t->mutex);
}
Tilghman Lesher
committed
if (res == 0) {
Tilghman Lesher
committed
current->count++;
Tilghman Lesher
committed
current->channel = chan;
}
return res;
}
static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
Tilghman Lesher
committed
struct channel_lock_frame *clframe;
AST_LIST_HEAD(, channel_lock_frame) *list;
Tilghman Lesher
committed
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;
}
Tilghman Lesher
committed
if (!(list = lock_store->data)) {
ast_debug(1, "This should NEVER happen\n");
ast_copy_string(buf, "0", len);
return 0;
}
Tilghman Lesher
committed
Tilghman Lesher
committed
/* Find item in the channel list */
AST_LIST_LOCK(list);
AST_LIST_TRAVERSE(list, clframe, list) {
if (clframe->lock_frame && clframe->lock_frame->channel == 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 */
Tilghman Lesher
committed
ast_copy_string(buf, "0", len);
return 0;
}
Tilghman Lesher
committed
/* Decrement before we release, because if a channel is waiting on the
* mutex, there's otherwise a race to alter count. */
clframe->lock_frame->count--;
/* If we get another lock, this one shouldn't count against us for deadlock avoidance. */
clframe->lock_frame->channel = NULL;
ast_mutex_unlock(&clframe->lock_frame->mutex);
Tilghman Lesher
committed
ast_copy_string(buf, "1", len);
return 0;
}
static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
if (chan)
ast_autoservice_start(chan);
Tilghman Lesher
committed
ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
if (chan)
ast_autoservice_stop(chan);
Russell Bryant
committed
Tilghman Lesher
committed
return 0;
}
static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
{
if (chan)
ast_autoservice_start(chan);
Tilghman Lesher
committed
ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
if (chan)
ast_autoservice_stop(chan);
Russell Bryant
committed
Tilghman Lesher
committed
return 0;
}
static struct ast_custom_function lock_function = {
.name = "LOCK",
.synopsis = "Attempt to obtain a named mutex",
.desc =
Tilghman Lesher
committed
"Attempts to grab a named lock exclusively, and prevents other channels from\n"
"obtaining the same lock. LOCK will wait for the lock to become available.\n"
"Returns 1 if the lock was obtained or 0 on error.\n\n"
Tilghman Lesher
committed
"Note: to avoid the possibility of a deadlock, LOCK will only attempt to\n"
Tilghman Lesher
committed
"obtain the lock for 3 seconds if the channel already has another lock.\n",
Tilghman Lesher
committed
.syntax = "LOCK(<lockname>)",
.read = lock_read,
};
static struct ast_custom_function trylock_function = {
.name = "TRYLOCK",
.synopsis = "Attempt to obtain a named mutex",
.desc =
"Attempts to grab a named lock exclusively, and prevents other channels\n"
"from obtaining the same lock. Returns 1 if the lock was available or 0\n"
Tilghman Lesher
committed
"otherwise.\n",
Tilghman Lesher
committed
.syntax = "TRYLOCK(<lockname>)",
.read = trylock_read,
};
static struct ast_custom_function unlock_function = {
.name = "UNLOCK",
.synopsis = "Unlocks a named mutex",
.desc =
Tilghman Lesher
committed
"Unlocks a previously locked mutex. Note that it is generally unnecessary to\n"
Tilghman Lesher
committed
"unlock in a hangup routine, as any locks held are automatically freed when the\n"
Tilghman Lesher
committed
"channel is destroyed. Returns 1 if the channel had a lock or 0 otherwise.\n",
Tilghman Lesher
committed
.syntax = "UNLOCK(<lockname>)",
Tilghman Lesher
committed
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
.read = unlock_read,
};
static int unload_module(void)
{
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->channel) {
/* Put it back */
AST_LIST_INSERT_HEAD(&locklist, current, entries);
AST_LIST_UNLOCK(&locklist);
unloading = 0;
return -1;
}
ast_mutex_destroy(¤t->mutex);
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);
AST_LIST_UNLOCK(&locklist);
return 0;
}
static int load_module(void)
{
int res = ast_custom_function_register(&lock_function);
res |= ast_custom_function_register(&trylock_function);
res |= ast_custom_function_register(&unlock_function);
return res;
}
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");