Index: apps/app_macro.c =================================================================== --- apps/app_macro.c (revision 34239) +++ apps/app_macro.c (working copy) @@ -70,6 +70,13 @@ "(otherwise if provided)\n" "Arguments and return values as in application macro()\n"; +static char *exclusive_descrip = +" MacroExclusive(macroname|arg1|arg2...):\n" +"Executes macro defined in the context 'macro-macroname'\n" +"Only one call at a time may run the macro.\n" +"(we'll wait if another call is busy executing in the Macro)\n" +"Arguments and return values as in application Macro()\n"; + static char *exit_descrip = " MacroExit():\n" "Causes the currently running macro to exit as if it had\n" @@ -79,15 +86,17 @@ static char *app = "Macro"; static char *if_app = "MacroIf"; +static char *exclusive_app = "MacroExclusive"; static char *exit_app = "MacroExit"; static char *synopsis = "Macro Implementation"; static char *if_synopsis = "Conditional Macro Implementation"; +static char *exclusive_synopsis = "Exclusive Macro Implementation"; static char *exit_synopsis = "Exit From Macro"; LOCAL_USER_DECL; -static int macro_exec(struct ast_channel *chan, void *data) +static int _macro_exec(struct ast_channel *chan, void *data, int exclusive) { const char *s; @@ -140,15 +149,26 @@ LOCAL_USER_REMOVE(u); return 0; } + snprintf(fullmacro, sizeof(fullmacro), "macro-%s", macro); if (!ast_exists_extension(chan, fullmacro, "s", 1, chan->cid.cid_num)) { - if (!ast_context_find(fullmacro)) + if (!ast_context_find(fullmacro)) ast_log(LOG_WARNING, "No such context '%s' for macro '%s'\n", fullmacro, macro); else - ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro); + ast_log(LOG_WARNING, "Context '%s' for macro '%s' lacks 's' extension, priority 1\n", fullmacro, macro); LOCAL_USER_REMOVE(u); return 0; } + + /* If we are to run the macro exclusively, take the mutex */ + if (exclusive) { + ast_log(LOG_DEBUG, "Locking macrolock for '%s'\n", fullmacro); + if (ast_context_lockmacro(fullmacro)) { + ast_log(LOG_WARNING, "Failed to lock macro '%s' as in-use\n", fullmacro); + LOCAL_USER_REMOVE(u); + return 0; + } + } /* Save old info */ oldpriority = chan->priority; @@ -243,7 +263,6 @@ snprintf(depthc, sizeof(depthc), "%d", depth); if (!dead) { pbx_builtin_setvar_helper(chan, "MACRO_DEPTH", depthc); - ast_set2_flag(chan, autoloopflag, AST_FLAG_IN_AUTOLOOP); } @@ -302,10 +321,30 @@ pbx_builtin_setvar_helper(chan, "MACRO_OFFSET", save_macro_offset); if (save_macro_offset) free(save_macro_offset); + + /* Unlock the macro */ + if (exclusive) { + ast_log(LOG_DEBUG, "Unlocking macrolock for '%s'\n", fullmacro); + if (ast_context_unlockmacro(fullmacro)) { + ast_log(LOG_ERROR, "Failed to unlock macro '%s' - that isn't good\n", fullmacro); + res = 0; + } + } + LOCAL_USER_REMOVE(u); return res; } +static int macro_exec(struct ast_channel *chan, void *data) +{ + _macro_exec(chan, data, 0); +} + +static int macroexclusive_exec(struct ast_channel *chan, void *data) +{ + _macro_exec(chan, data, 1); +} + static int macroif_exec(struct ast_channel *chan, void *data) { char *expr = NULL, *label_a = NULL, *label_b = NULL; @@ -362,6 +401,7 @@ res = ast_register_application(exit_app, macro_exit_exec, exit_synopsis, exit_descrip); res |= ast_register_application(if_app, macroif_exec, if_synopsis, if_descrip); + res |= ast_register_application(exclusive_app, macroexclusive_exec, exclusive_synopsis, exclusive_descrip); res |= ast_register_application(app, macro_exec, synopsis, descrip); return res; Index: doc/macroexclusive.txt =================================================================== --- doc/macroexclusive.txt (revision 0) +++ doc/macroexclusive.txt (revision 0) @@ -0,0 +1,78 @@ +About the MacroExclusive application +------------------------------------ + +Steve Davies s,1,Set(RESULT=${COUNT}) +exten => s,n,SetGlobalVar(COUNT=$[${COUNT} + 1]) + +The problem is that in a box with high activity, you can be sure +that two calls will come along together - both will get the same +"RESULT", or the "COUNT" value will get mangled. + +Calling this Macro via MacroExclusive will use a mutex to make sure +that only one call executes in the Macro at a time. This ensures +that the two lines execute as a unit. + +Note that even the s,2 line above has its own race problem. Two +calls running that line at once will step on each other and +the count will end up as +1 rather than +2. + +I've also been able to use MacroExclusive where I have two Macros +that need to be mutually exclusive. + +Here's the example: + +[macro-push] +; push value ${ARG2} onto stack ${ARG1} +exten => s,1,Set(DB(STACK/${ARG1})=${ARG2}^${DB(STACK/${ARG1})}) + +[macro-pop] +; pop top value from stack ${ARG1} +exten => s,1,Set(RESULT=${DB(STACK/${ARG1})}) +exten => s,n,Set(DB(STACK/${ARG1})=${CUT(RESULT,^,2)}) +exten => s,n,Set(RESULT=${CUT(RESULT,^,1)}) + +All that futzing with the STACK/${ARG1} in the astdb needs protecting +if this is to work. But neither push nor pop can run together. + +So add this "pattern": + +[macro-stack] +exten => Macro(${ARG1},${ARG2},${ARG3}) + +... and use it like so: + +exten => s,1,MacroExclusive(stack,push,MYSTACK,bananas) +exten => s,n,MacroExclusive(stack,push,MYSTACK,apples) +exten => s,n,MacroExclusive(stack,push,MYSTACK,guavas) +exten => s,n,MacroExclusive(stack,push,MYSTACK,pawpaws) +exten => s,n,MacroExclusive(stack,pop,MYSTACK) ; RESULT gets pawpaws (yum) +exten => s,n,MacroExclusive(stack,pop,MYSTACK) ; RESULT gets guavas +exten => s,n,MacroExclusive(stack,pop,MYSTACK) ; RESULT gets apples +exten => s,n,MacroExclusive(stack,pop,MYSTACK) ; RESULT gets bananas + +We get to the push and pop macros "via" the stack macro. But only one call +can execute the stack macro at a time; ergo, only one of push OR pop can +run at a time. + +Hope people find this useful. + +Lastly, its worth pointing out that only Macros that access shared data +will require this MacroExclusive protection. And Macro's that you call +with macroExclusive should run quickly or you will clog up your Asterisk +system. + +Regards, +Steve Index: include/asterisk/pbx.h =================================================================== --- include/asterisk/pbx.h (revision 34239) +++ include/asterisk/pbx.h (working copy) @@ -680,6 +680,20 @@ */ int ast_unlock_context(struct ast_context *con); +/*! Locks the given macro-context to ensure only one thread (call) can execute it at a time */ +/*! + * \param macrocontext name of the macro-context to lock + * locks the macrolock in the given given context + * Returns 0 on success, -1 on failure + */ +int ast_context_lockmacro(const char *macrocontext); +/*! Unlocks the given macro-context so that another thread (call) can execute it */ +/*! + * \param macrocontext name of the macro-context to unlock + * Unlocks the macrolock in the given context + * Returns 0 on success, -1 on failure + */ +int ast_context_unlockmacro(const char *macrocontext); int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority); Index: pbx.c =================================================================== --- pbx.c (revision 34239) +++ pbx.c (working copy) @@ -164,6 +164,7 @@ struct ast_ignorepat *ignorepats; /*!< Patterns for which to continue playing dialtone */ const char *registrar; /*!< Registrar */ AST_LIST_HEAD_NOLOCK(, ast_sw) alts; /*!< Alternative switches */ + ast_mutex_t macrolock; /*!< A lock to implement "exclusive" macros - held whilst a call is executing in the macro */ char name[0]; /*!< Name of the context */ }; @@ -2725,6 +2726,64 @@ } +/*! + * \note This function locks contexts list by &conlist, searches for the right context + * structure, and locks the macrolock mutex in that context. + * macrolock is used to limit a macro to be executed by one call at a time. + */ +int ast_context_lockmacro(const char *context) +{ + struct ast_context *c; + int ret = -1; + + if (ast_lock_contexts()) return -1; + + c = ast_walk_contexts(NULL); + while (c) { + if (!strcmp(ast_get_context_name(c), context)) { + ret = 0; + break; + } + c = ast_walk_contexts(c); + } + + ast_unlock_contexts(); + + /* if we found context, lock macrolock */ + if (ret == 0) ret = ast_mutex_lock(&c->macrolock); + + return ret; +} + +/*! + * \note This function locks contexts list by &conlist, searches for the right context + * structure, and unlocks the macrolock mutex in that context. + * macrolock is used to limit a macro to be executed by one call at a time. + */ +int ast_context_unlockmacro(const char *context) +{ + struct ast_context *c; + int ret = -1; + + if (ast_lock_contexts()) return -1; + + c = ast_walk_contexts(NULL); + while (c) { + if (!strcmp(ast_get_context_name(c), context)) { + ret = 0; + break; + } + c = ast_walk_contexts(c); + } + + ast_unlock_contexts(); + + /* if we found context, unlock macrolock */ + if (ret == 0) ret = ast_mutex_unlock(&c->macrolock); + + return ret; +} + /*! \brief Dynamically register a new dial plan application */ int ast_register_application(const char *app, int (*execute)(struct ast_channel *, void *), const char *synopsis, const char *description) { @@ -3434,6 +3493,7 @@ } if ((tmp = ast_calloc(1, length))) { ast_mutex_init(&tmp->lock); + ast_mutex_init(&tmp->macrolock); strcpy(tmp->name, name); tmp->root = NULL; tmp->registrar = registrar;