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