Index: tests/test_gosub.c
===================================================================
--- tests/test_gosub.c	(revision 358725)
+++ tests/test_gosub.c	(working copy)
@@ -51,12 +51,24 @@
 		const char *args;
 		const char *expected_value;
 	} testplan[] = {
+		{ NULL, "${STACK_PEEK(1,e,1)}", "" },         /* Stack is empty */
 		{ "Gosub", "tests_test_gosub_virtual_context,s,1" },
+		{ NULL, "${PRIORITY}", "1" },
 		{ NULL, "${EXTEN}", "s" },
+		{ NULL, "${STACK_PEEK(1,e,1)}", "" },         /* No extension originally */
 		{ "Gosub", "test,dne,1", (const char *) -1 }, /* This is the only invocation that should fail. */
+		{ NULL, "${PRIORITY}", "1" },
 		{ NULL, "${EXTEN}", "s" },
 		{ "Gosub", "tests_test_gosub_virtual_context,s,1(5,5,5,5,5)" },
+		{ NULL, "${PRIORITY}", "1" },
 		{ NULL, "$[0${ARG1} + 0${ARG5}]", "10" },
+		{ NULL, "${STACK_PEEK(1,e)}", "s" },
+		{ NULL, "${STACK_PEEK(1,c)}", "tests_test_gosub_virtual_context" },
+		{ NULL, "${STACK_PEEK(1,p)}", "1" },
+		{ NULL, "${STACK_PEEK(1,l)}", "tests_test_gosub_virtual_context,s,1" },
+		{ "StackPop", "" },
+		{ NULL, "${STACK_PEEK(1,e,1)}", "" },         /* Only 1 frame deep, my caller is top-level */
+		{ "Gosub", "tests_test_gosub_virtual_context,s,1(5,5,5,5,5)" },
 		{ "Gosub", "tests_test_gosub_virtual_context,s,1(4,4,4,4)" },
 		{ NULL, "$[0${ARG1} + 0${ARG5}]", "4" },
 		{ NULL, "$[0${ARG1} + 0${ARG4}]", "8" },
Index: apps/app_stack.c
===================================================================
--- apps/app_stack.c	(revision 358725)
+++ apps/app_stack.c	(working copy)
@@ -165,6 +165,24 @@
 			<ref type="application">Return</ref>
 		</see-also>
 	</function>
+	<function name="STACK_PEEK" language="en_US">
+		<synopsis>
+			View info about the location which called Gosub
+		</synopsis>
+		<syntax>
+			<parameter name="n" required="true" />
+			<parameter name="which" required="true" />
+			<parameter name="suppress" required="false" />
+		</syntax>
+		<description>
+			<para>Read the calling <literal>c</literal>ontext, <literal>e</literal>xtension,
+			<literal>p</literal>riority, or <literal>l</literal>abel, as specified by
+			<replaceable>which</replaceable>, by going up <replaceable>n</replaceable> frames
+			in the Gosub stack.  If <replaceable>suppress</replaceable> is true, then if the
+			number of available stack frames is exceeded, then no error message will be
+			printed.</para>
+		</description>
+	</function>
 	<agi name="gosub" language="en_US">
 		<synopsis>
 			Cause the channel to execute the specified dialplan subroutine.
@@ -309,14 +327,16 @@
 
 static int return_exec(struct ast_channel *chan, const char *data)
 {
-	struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+	struct ast_datastore *stack_store;
 	struct gosub_stack_frame *oldframe;
 	AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
 	const char *retval = data;
 	int res = 0;
 
-	if (!stack_store) {
+	ast_channel_lock(chan);
+	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
 		ast_log(LOG_ERROR, "Return without Gosub: stack is unallocated\n");
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
@@ -327,6 +347,7 @@
 
 	if (!oldframe) {
 		ast_log(LOG_ERROR, "Return without Gosub: stack is empty\n");
+		ast_channel_unlock(chan);
 		return -1;
 	} else if (oldframe->is_agi) {
 		/* Exit from AGI */
@@ -338,12 +359,13 @@
 
 	/* Set a return value, if any */
 	pbx_builtin_setvar_helper(chan, "GOSUB_RETVAL", S_OR(retval, ""));
+	ast_channel_unlock(chan);
 	return res;
 }
 
 static int gosub_exec(struct ast_channel *chan, const char *data)
 {
-	struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+	struct ast_datastore *stack_store;
 	AST_LIST_HEAD(,gosub_stack_frame) *oldlist;
 	struct gosub_stack_frame *newframe, *lastframe;
 	char argname[15], *tmp = ast_strdupa(data), *label, *endparen;
@@ -357,11 +379,13 @@
 		return -1;
 	}
 
-	if (!stack_store) {
+	ast_channel_lock(chan);
+	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
 		ast_debug(1, "Channel %s has no datastore, so we're allocating one.\n", ast_channel_name(chan));
 		stack_store = ast_datastore_alloc(&stack_info, NULL);
 		if (!stack_store) {
 			ast_log(LOG_ERROR, "Unable to allocate new datastore.  Gosub will fail.\n");
+			ast_channel_unlock(chan);
 			return -1;
 		}
 
@@ -369,6 +393,7 @@
 		if (!oldlist) {
 			ast_log(LOG_ERROR, "Unable to allocate datastore list head.  Gosub will fail.\n");
 			ast_datastore_free(stack_store);
+			ast_channel_unlock(chan);
 			return -1;
 		}
 
@@ -405,12 +430,14 @@
 	newframe = gosub_allocate_frame(ast_channel_context(chan), ast_channel_exten(chan), ast_channel_priority(chan) + 1, max_argc);
 
 	if (!newframe) {
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
 	if (ast_parseable_goto(chan, label)) {
 		ast_log(LOG_ERROR, "Gosub address is invalid: '%s'\n", (char *)data);
 		ast_free(newframe);
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
@@ -423,6 +450,7 @@
 		ast_channel_exten_set(chan, newframe->extension);
 		ast_channel_priority_set(chan, newframe->priority - 1);
 		ast_free(newframe);
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
@@ -440,6 +468,7 @@
 	AST_LIST_LOCK(oldlist);
 	AST_LIST_INSERT_HEAD(oldlist, newframe, entries);
 	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
 
 	return 0;
 }
@@ -483,44 +512,49 @@
 
 static int local_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
-	struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+	struct ast_datastore *stack_store;
 	AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
 	struct gosub_stack_frame *frame;
 	struct ast_var_t *variables;
 
-	if (!stack_store)
+	ast_channel_lock(chan);
+	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
+		ast_channel_unlock(chan);
 		return -1;
+	}
 
 	oldlist = stack_store->data;
 	AST_LIST_LOCK(oldlist);
 	if (!(frame = AST_LIST_FIRST(oldlist))) {
 		/* Not within a Gosub routine */
 		AST_LIST_UNLOCK(oldlist);
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
 	AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
 		if (!strcmp(data, ast_var_name(variables))) {
 			const char *tmp;
-			ast_channel_lock(chan);
 			tmp = pbx_builtin_getvar_helper(chan, data);
 			ast_copy_string(buf, S_OR(tmp, ""), len);
-			ast_channel_unlock(chan);
 			break;
 		}
 	}
 	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
 	return 0;
 }
 
 static int local_write(struct ast_channel *chan, const char *cmd, char *var, const char *value)
 {
-	struct ast_datastore *stack_store = ast_channel_datastore_find(chan, &stack_info, NULL);
+	struct ast_datastore *stack_store;
 	AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
 	struct gosub_stack_frame *frame;
 
-	if (!stack_store) {
+	ast_channel_lock(chan);
+	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
 		ast_log(LOG_ERROR, "Tried to set LOCAL(%s), but we aren't within a Gosub routine\n", var);
+		ast_channel_unlock(chan);
 		return -1;
 	}
 
@@ -528,10 +562,12 @@
 	AST_LIST_LOCK(oldlist);
 	frame = AST_LIST_FIRST(oldlist);
 
-	if (frame)
+	if (frame) {
 		frame_set_var(chan, frame, var, value);
+	}
 
 	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
 
 	return 0;
 }
@@ -576,6 +612,89 @@
 	.read = peek_read,
 };
 
+static int stackpeek_read(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **str, ssize_t len)
+{
+	struct ast_datastore *stack_store;
+	AST_LIST_HEAD(, gosub_stack_frame) *oldlist;
+	struct gosub_stack_frame *frame;
+	int n;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(n);
+		AST_APP_ARG(which);
+		AST_APP_ARG(suppress);
+	);
+
+	if (!chan) {
+		ast_log(LOG_ERROR, "STACK_PEEK must be called on an active channel\n");
+		return -1;
+	}
+
+	data = ast_strdupa(data);
+	AST_STANDARD_APP_ARGS(args, data);
+
+	n = atoi(args.n);
+	if (n <= 0) {
+		ast_log(LOG_ERROR, "STACK_PEEK must be called with a positive peek value\n");
+		return -1;
+	}
+
+	ast_channel_lock(chan);
+	if (!(stack_store = ast_channel_datastore_find(chan, &stack_info, NULL))) {
+		if (!ast_true(args.suppress)) {
+			ast_log(LOG_ERROR, "STACK_PEEK called on a channel without a gosub stack\n");
+		}
+		ast_channel_unlock(chan);
+		return -1;
+	}
+
+	oldlist = stack_store->data;
+
+	AST_LIST_LOCK(oldlist);
+	AST_LIST_TRAVERSE(oldlist, frame, entries) {
+		if (--n == 0) {
+			break;
+		}
+	}
+
+	if (!frame) {
+		/* Too deep */
+		if (!ast_true(args.suppress)) {
+			ast_log(LOG_ERROR, "Stack peek of '%s' is more stack frames than I have\n", args.n);
+		}
+		ast_channel_unlock(chan);
+		return -1;
+	}
+
+	args.which = ast_skip_blanks(args.which);
+
+	switch (args.which[0]) {
+	case 'l': /* label */
+		ast_str_set(str, len, "%s,%s,%d", frame->context, frame->extension, frame->priority - 1);
+		break;
+	case 'c': /* context */
+		ast_str_set(str, len, "%s", frame->context);
+		break;
+	case 'e': /* extension */
+		ast_str_set(str, len, "%s", frame->extension);
+		break;
+	case 'p': /* priority */
+		ast_str_set(str, len, "%d", frame->priority - 1);
+		break;
+	default:
+		ast_log(LOG_ERROR, "Unknown argument '%s' to STACK_PEEK\n", args.which);
+	}
+
+	AST_LIST_UNLOCK(oldlist);
+	ast_channel_unlock(chan);
+
+	return 0;
+}
+
+static struct ast_custom_function stackpeek_function = {
+	.name = "STACK_PEEK",
+	.read2 = stackpeek_read,
+};
+
 static int handle_gosub(struct ast_channel *chan, AGI *agi, int argc, const char * const *argv)
 {
 	int old_priority, priority;
@@ -687,6 +806,7 @@
 	ast_unregister_application(app_gosub);
 	ast_custom_function_unregister(&local_function);
 	ast_custom_function_unregister(&peek_function);
+	ast_custom_function_unregister(&stackpeek_function);
 
 	return 0;
 }
@@ -701,12 +821,14 @@
 	ast_register_application_xml(app_gosub, gosub_exec);
 	ast_custom_function_register(&local_function);
 	ast_custom_function_register(&peek_function);
+	ast_custom_function_register(&stackpeek_function);
 
 	return 0;
 }
 
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Dialplan subroutines (Gosub, Return, etc)",
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT | AST_MODFLAG_LOAD_ORDER, "Dialplan subroutines (Gosub, Return, etc)",
 		.load = load_module,
 		.unload = unload_module,
+		.load_pri = AST_MODPRI_APP_DEPEND,
 		.nonoptreq = "res_agi",
 		);
Index: funcs/func_dialplan.c
===================================================================
--- funcs/func_dialplan.c	(revision 358725)
+++ funcs/func_dialplan.c	(working copy)
@@ -185,4 +185,8 @@
 	return res;
 }
 
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialplan Context/Extension/Priority Checking Functions");
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Dialplan Context/Extension/Priority Checking Functions",
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_APP_DEPEND,
+	);
Index: res/ael/pval.c
===================================================================
--- res/ael/pval.c	(revision 358725)
+++ res/ael/pval.c	(working copy)
@@ -4423,6 +4423,20 @@
 	}
 }
 
+static int context_used(struct ael_extension *exten_list, struct ast_context *context)
+{
+	struct ael_extension *exten;
+	/* Check the simple elements first */
+	if (ast_walk_context_extensions(context, NULL) || ast_walk_context_includes(context, NULL) || ast_walk_context_ignorepats(context, NULL) || ast_walk_context_switches(context, NULL)) {
+		return 1;
+	}
+	for (exten = exten_list; exten; exten = exten->next_exten) {
+		if (exten->context == context) {
+			return 1;
+		}
+	}
+	return 0;
+}
 
 int ast_compile_ael2(struct ast_context **local_contexts, struct ast_hashtab *local_table, struct pval *root)
 {
@@ -4604,6 +4618,71 @@
 			
 		}
 	}
+
+	/* Create default "h" bubble context */
+	if (ast_custom_function_find("DIALPLAN_EXISTS") && ast_custom_function_find("STACK_PEEK")) {
+		int i;
+		const char *h_context = "ael-builtin-h-bubble";
+		struct ael_priority *np;
+		struct {
+			int priority;
+			const char *app;
+			const char *arg;
+		} steps[] = {
+			/* Start high, to avoid conflict with existing h extensions */
+			{ 1, "Goto", "9991" },
+			/* Save the context, because after the StackPop, it disappears */
+			{ 9991, "Set", "~~parentcxt~~=${STACK_PEEK(1,c,1)}" },
+			/* If we're not in a Gosub frame, exit */
+			{ 9992, "GotoIf", "$[\"${~~parentcxt~~}\"=\"\"]?9996" },
+			/* Check for an "h" extension in that context */
+			{ 9993, "GotoIf", "${DIALPLAN_EXISTS(${~~parentcxt~~},h,1)}?9994:9996" },
+			/* Pop off the stack frame to prevent an infinite loop */
+			{ 9994, "StackPop", "" },
+			/* Finally, go there. */
+			{ 9995, "Goto", "${~~parentcxt~~},h,1" },
+			/* Just an empty priority for jumping out early */
+			{ 9996, "NoOp", "" }
+		};
+		context = ast_context_find_or_create(local_contexts, local_table, h_context, registrar);
+		if (context_used(exten_list, context)) {
+			int found = 0;
+			while (!found) {
+				/* Pick a new context name that is not used. */
+				char h_context_template[] = "/tmp/ael-builtin-h-bubble-XXXXXX";
+				int fd = mkstemp(h_context_template);
+				unlink(h_context_template);
+				close(fd);
+				context = ast_context_find_or_create(local_contexts, local_table, h_context_template + 5, registrar);
+				found = !context_used(exten_list, context);
+			}
+			h_context = ast_get_context_name(context);
+		}
+		exten = new_exten();
+		exten->context = context;
+		exten->name = strdup("h");
+
+		for (i = 0; i < ARRAY_LEN(steps); i++) {
+			np = new_prio();
+			np->type = AEL_APPCALL;
+			np->priority_num = steps[i].priority;
+			np->app = strdup(steps[i].app);
+			np->appargs = strdup(steps[i].arg);
+			linkprio(exten, np, NULL);
+		}
+		attach_exten(&exten_list, exten);
+
+		/* Include the default "h" bubble context in each macro context */
+		for (exten = exten_list; exten; exten = exten->next_exten) {
+			/* All macros contain a "~~s~~" extension, and it's the first created.  If
+			 * we perchance get a non-macro context, it's no big deal; the logic is
+			 * designed to exit out smoothly if not called from within a Gosub. */
+			if (!strcmp(exten->name, "~~s~~")) {
+				ast_context_add_include2(exten->context, h_context, registrar);
+			}
+		}
+	}
+
 	/* moved these from being done after a macro or extension were processed,
 	   to after all processing is done, for the sake of fixing gotos to labels inside cases... */
 	/* I guess this would be considered 2nd pass of compiler now... */
Index: utils/ael_main.c
===================================================================
--- utils/ael_main.c	(revision 358725)
+++ utils/ael_main.c	(working copy)
@@ -425,6 +425,36 @@
 		printf("Executed ast_context_destroy();\n");
 }
 
+const char *ast_get_context_name(struct ast_context *con);
+const char *ast_get_context_name(struct ast_context *con)
+{
+	return con ? con->name : NULL;
+}
+
+struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten);
+struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten)
+{
+	return NULL;
+}
+
+struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc);
+struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc)
+{
+	return NULL;
+}
+
+struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip);
+struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip)
+{
+	return NULL;
+}
+
+struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw);
+struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw)
+{
+	return NULL;
+}
+
 void filter_leading_space_from_exprs(char *str)
 {
 	/*  Mainly for aesthetics */
Index: utils/conf2ael.c
===================================================================
--- utils/conf2ael.c	(revision 358725)
+++ utils/conf2ael.c	(working copy)
@@ -668,6 +668,36 @@
 	localized_merge_contexts_and_delete(extcontexts, exttable, registrar);
 }
 
+const char *ast_get_context_name(struct ast_context *con);
+const char *ast_get_context_name(struct ast_context *con)
+{
+	return con ? con->name : NULL;
+}
+
+struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten);
+struct ast_exten *ast_walk_context_extensions(struct ast_context *con, struct ast_exten *exten)
+{
+	return NULL;
+}
+
+struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc);
+struct ast_include *ast_walk_context_includes(struct ast_context *con, struct ast_include *inc)
+{
+	return NULL;
+}
+
+struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip);
+struct ast_ignorepat *ast_walk_context_ignorepats(struct ast_context *con, struct ast_ignorepat *ip)
+{
+	return NULL;
+}
+
+struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw);
+struct ast_sw *ast_walk_context_switches(struct ast_context *con, struct ast_sw *sw)
+{
+	return NULL;
+}
+
 struct ast_exten *pbx_find_extension(struct ast_channel *chan,
 									 struct ast_context *bypass,
 									 struct pbx_find_info *q,
