8c08f85c6ca967b7e6704fc669a978bb5cce0db3
[asterisk/asterisk.git] / funcs / func_lock.c
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 #include "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <string.h>
36
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/options.h"
41 #include "asterisk/channel.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/options.h"
45 #include "asterisk/linkedlists.h"
46
47 AST_LIST_HEAD_STATIC(locklist, lock_frame);
48
49 static void lock_free(void *data);
50 static int unloading = 0;
51
52 static struct ast_datastore_info lock_info = {
53         .type = "MUTEX",
54         .destroy = lock_free,
55 };
56
57 struct lock_frame {
58         AST_LIST_ENTRY(lock_frame) entries;
59         ast_mutex_t mutex;
60         /*! count is needed so if a recursive mutex exits early, we know how many times to unlock it. */
61         unsigned int count;
62         /*! who owns us */
63         struct ast_channel *channel;
64         /*! name of the lock */
65         char name[0];
66 };
67
68 struct channel_lock_frame {
69         AST_LIST_ENTRY(channel_lock_frame) list;
70         /*! Need to save channel pointer here, because during destruction, we won't have it. */
71         struct ast_channel *channel;
72         struct lock_frame *lock_frame;
73 };
74
75 static void lock_free(void *data)
76 {
77         AST_LIST_HEAD(, channel_lock_frame) *oldlist = data;
78         struct channel_lock_frame *clframe;
79         AST_LIST_LOCK(oldlist);
80         while ((clframe = AST_LIST_REMOVE_HEAD(oldlist, list))) {
81                 /* Only unlock if we own the lock */
82                 if (clframe->channel == clframe->lock_frame->channel) {
83                         clframe->lock_frame->channel = NULL;
84                         while (clframe->lock_frame->count > 0) {
85                                 clframe->lock_frame->count--;
86                                 ast_mutex_unlock(&clframe->lock_frame->mutex);
87                         }
88                 }
89                 ast_free(clframe);
90         }
91         AST_LIST_UNLOCK(oldlist);
92         AST_LIST_HEAD_DESTROY(oldlist);
93         ast_free(oldlist);
94 }
95
96 static int get_lock(struct ast_channel *chan, char *lockname, int try)
97 {
98         struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
99         struct lock_frame *current;
100         struct channel_lock_frame *clframe = NULL, *save_clframe = NULL;
101         AST_LIST_HEAD(, channel_lock_frame) *list;
102         int res, count_channel_locks = 0;
103
104         if (!lock_store) {
105                 ast_debug(1, "Channel %s has no lock datastore, so we're allocating one.\n", chan->name);
106                 lock_store = ast_channel_datastore_alloc(&lock_info, NULL);
107                 if (!lock_store) {
108                         ast_log(LOG_ERROR, "Unable to allocate new datastore.  No locks will be obtained.\n");
109                         return -1;
110                 }
111
112                 list = ast_calloc(1, sizeof(*list));
113                 if (!list) {
114                         ast_log(LOG_ERROR, "Unable to allocate datastore list head.  %sLOCK will fail.\n", try ? "TRY" : "");
115                         ast_channel_datastore_free(lock_store);
116                         return -1;
117                 }
118
119                 lock_store->data = list;
120                 AST_LIST_HEAD_INIT(list);
121                 ast_channel_datastore_add(chan, lock_store);
122         } else
123                 list = lock_store->data;
124
125         /* Lock already exists? */
126         AST_LIST_LOCK(&locklist);
127         AST_LIST_TRAVERSE(&locklist, current, entries) {
128                 if (strcmp(current->name, lockname) == 0) {
129                         break;
130                 }
131         }
132
133         if (!current) {
134                 if (unloading) {
135                         /* Don't bother */
136                         AST_LIST_UNLOCK(&locklist);
137                         return -1;
138                 }
139
140                 /* Create new lock entry */
141                 current = ast_calloc(1, sizeof(*current) + strlen(lockname) + 1);
142                 if (!current) {
143                         AST_LIST_UNLOCK(&locklist);
144                         return -1;
145                 }
146
147                 strcpy((char *)current + sizeof(*current), lockname);
148                 ast_mutex_init(&current->mutex);
149                 AST_LIST_INSERT_TAIL(&locklist, current, entries);
150         }
151         AST_LIST_UNLOCK(&locklist);
152
153         /* Found lock or created one - now find or create the corresponding link in the channel */
154         AST_LIST_LOCK(list);
155         AST_LIST_TRAVERSE(list, clframe, list) {
156                 if (clframe->lock_frame == current)
157                         save_clframe = clframe;
158
159                 /* Only count mutexes that we currently hold */
160                 if (clframe->lock_frame->channel == chan)
161                         count_channel_locks++;
162         }
163
164         if (save_clframe) {
165                 clframe = save_clframe;
166         } else {
167                 if (unloading) {
168                         /* Don't bother */
169                         AST_LIST_UNLOCK(list);
170                         return -1;
171                 }
172
173                 clframe = ast_calloc(1, sizeof(*clframe));
174                 if (!clframe) {
175                         ast_log(LOG_ERROR, "Unable to allocate channel lock frame.  %sLOCK will fail.\n", try ? "TRY" : "");
176                         AST_LIST_UNLOCK(list);
177                         return -1;
178                 }
179
180                 clframe->lock_frame = current;
181                 clframe->channel = chan;
182                 /* Count the lock just created */
183                 count_channel_locks++;
184                 AST_LIST_INSERT_TAIL(list, clframe, list);
185         }
186         AST_LIST_UNLOCK(list);
187
188         /* Okay, we have both frames, so now we need to try to lock the mutex. */
189         if (count_channel_locks > 1) {
190                 /* If we fail after a certain number of attempts, assume a possible deadlock and bail. */
191                 int x;
192                 for (x = 0; x < 30; x++) {
193                         if ((res = ast_mutex_trylock(&current->mutex)) == 0)
194                                 break;
195                         usleep(1);
196                 }
197         } else {
198                 /* If the channel doesn't have any locks so far, then there's no possible deadlock. */
199                 res = try ? ast_mutex_trylock(&current->mutex) : ast_mutex_lock(&current->mutex);
200         }
201
202         if (res == 0) {
203                 current->count++;
204                 current->channel = chan;
205         }
206
207         return res;
208 }
209
210 static int unlock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
211 {
212         struct ast_datastore *lock_store = ast_channel_datastore_find(chan, &lock_info, NULL);
213         struct channel_lock_frame *clframe;
214         AST_LIST_HEAD(, channel_lock_frame) *list;
215
216         if (!lock_store) {
217                 ast_log(LOG_WARNING, "No datastore for dialplan locks.  Nothing was ever locked!\n");
218                 ast_copy_string(buf, "0", len);
219                 return 0;
220         }
221
222         if (!(list = lock_store->data)) {
223                 ast_debug(1, "This should NEVER happen\n");
224                 ast_copy_string(buf, "0", len);
225                 return 0;
226         }
227
228         /* Find item in the channel list */
229         AST_LIST_LOCK(list);
230         AST_LIST_TRAVERSE(list, clframe, list) {
231                 if (clframe->lock_frame && clframe->lock_frame->channel == chan && strcmp(clframe->lock_frame->name, data) == 0) {
232                         break;
233                 }
234         }
235         /* We never destroy anything until channel destruction, which will never
236          * happen while this routine is executing, so we don't need to hold the
237          * lock beyond this point. */
238         AST_LIST_UNLOCK(list);
239
240         if (!clframe) {
241                 /* We didn't have this lock in the first place */
242                 ast_copy_string(buf, "0", len);
243                 return 0;
244         }
245
246         /* Decrement before we release, because if a channel is waiting on the
247          * mutex, there's otherwise a race to alter count. */
248         clframe->lock_frame->count--;
249         /* If we get another lock, this one shouldn't count against us for deadlock avoidance. */
250         clframe->lock_frame->channel = NULL;
251         ast_mutex_unlock(&clframe->lock_frame->mutex);
252
253         ast_copy_string(buf, "1", len);
254         return 0;
255 }
256
257 static int lock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
258 {
259         ast_copy_string(buf, get_lock(chan, data, 0) ? "0" : "1", len);
260         return 0;
261 }
262
263 static int trylock_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
264 {
265         ast_copy_string(buf, get_lock(chan, data, 1) ? "0" : "1", len);
266         return 0;
267 }
268
269 static struct ast_custom_function lock_function = {
270         .name = "LOCK",
271         .synopsis = "Attempt to obtain a named mutex",
272         .desc =
273 "Attempts to grab a named lock exclusively, and prevents other channels from\n"
274 "obtaining the same lock.  LOCK will wait for the lock to become available.\n"
275 "Returns 1 if the lock was obtained or 0 on error.\n\n"
276 "Note: to avoid the possibility of a deadlock, LOCK will only attempt to\n"
277 "obtain the lock for 3 seconds if the channel already has another lock.\n",
278         .syntax = "LOCK(<lockname>)",
279         .read = lock_read,
280 };
281
282 static struct ast_custom_function trylock_function = {
283         .name = "TRYLOCK",
284         .synopsis = "Attempt to obtain a named mutex",
285         .desc =
286 "Attempts to grab a named lock exclusively, and prevents other channels\n"
287 "from obtaining the same lock.  Returns 1 if the lock was available or 0\n"
288 "otherwise.\n",
289         .syntax = "TRYLOCK(<lockname>)",
290         .read = trylock_read,
291 };
292
293 static struct ast_custom_function unlock_function = {
294         .name = "UNLOCK",
295         .synopsis = "Unlocks a named mutex",
296         .desc =
297 "Unlocks a previously locked mutex.  Note that it is generally unnecessary to\n"
298 "unlock in a hangup routine, as any locks held are automatically freed when the\n"
299 "channel is destroyed.  Returns 1 if the channel had a lock or 0 otherwise.\n",
300         .syntax = "UNLOCK(<lockname>)",
301         .read = unlock_read,
302 };
303
304 static int unload_module(void)
305 {
306         struct lock_frame *current;
307
308         /* Module flag */
309         unloading = 1;
310
311         AST_LIST_LOCK(&locklist);
312         while ((current = AST_LIST_REMOVE_HEAD(&locklist, entries))) {
313                 /* If any locks are currently in use, then we cannot unload this module */
314                 if (current->channel) {
315                         /* Put it back */
316                         AST_LIST_INSERT_HEAD(&locklist, current, entries);
317                         AST_LIST_UNLOCK(&locklist);
318                         unloading = 0;
319                         return -1;
320                 }
321                 ast_mutex_destroy(&current->mutex);
322                 ast_free(current);
323         }
324
325         /* No locks left, unregister functions */
326         ast_custom_function_unregister(&lock_function);
327         ast_custom_function_unregister(&trylock_function);
328         ast_custom_function_unregister(&unlock_function);
329
330         AST_LIST_UNLOCK(&locklist);
331         return 0;
332 }
333
334 static int load_module(void)
335 {
336         int res = ast_custom_function_register(&lock_function);
337         res |= ast_custom_function_register(&trylock_function);
338         res |= ast_custom_function_register(&unlock_function);
339         return res;
340 }
341
342 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan mutexes");