diff --git a/doc/CHANGES-staging/xmldoc.txt b/doc/CHANGES-staging/xmldoc.txt
new file mode 100644
index 0000000000..50324e46f5
--- /dev/null
+++ b/doc/CHANGES-staging/xmldoc.txt
@@ -0,0 +1,5 @@
+Subject: xmldocs
+
+The XML documentation can now be reloaded without restarting
+Asterisk, which makes it possible to load new modules that
+enforce documentation without restarting Asterisk.
diff --git a/main/config_options.c b/main/config_options.c
index c59a8b5806..d258f607dc 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -1099,7 +1099,9 @@ static int xmldoc_update_config_type(const char *module, const char *name, const
 	}
 
 	if (!(results = ast_xmldoc_query("/docs/configInfo[@name='%s']/configFile/configObject[@name='%s']", module, name))) {
-		ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!\n", name, module);
+		ast_log(LOG_WARNING, "Cannot update type '%s' in module '%s' because it has no existing documentation!"
+			" If this module was recently built, run 'xmldoc reload' to refresh documentation, then load the module again\n",
+			name, module);
 		return XMLDOC_STRICT ? -1 : 0;
 	}
 
diff --git a/main/xmldoc.c b/main/xmldoc.c
index f924717e9a..2b75523402 100644
--- a/main/xmldoc.c
+++ b/main/xmldoc.c
@@ -68,9 +68,8 @@ static int xmldoc_parse_specialtags(struct ast_xml_node *fixnode, const char *ta
 /*!
  * \brief Container of documentation trees
  *
- * \note A RWLIST is a sufficient container type to use here for now.
- *       However, some changes will need to be made to implement ref counting
- *       if reload support is added in the future.
+ * \note A RWLIST is a sufficient container type to use, provided
+ *       the list lock is always held while there are references to the list.
  */
 static AST_RWLIST_HEAD_STATIC(xmldoc_tree, documentation_tree);
 
@@ -430,6 +429,8 @@ static int xmldoc_attribute_match(struct ast_xml_node *node, const char *attr, c
  *
  * \retval NULL on error.
  * \retval A node of type ast_xml_node.
+ *
+ * \note Must be called with a RDLOCK held on xmldoc_tree
  */
 static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name, const char *module, const char *language)
 {
@@ -438,7 +439,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name,
 	struct ast_xml_node *lang_match = NULL;
 	struct documentation_tree *doctree;
 
-	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	AST_LIST_TRAVERSE(&xmldoc_tree, doctree, entry) {
 		/* the core xml documents have priority over thirdparty document. */
 		node = ast_xml_get_root(doctree->doc);
@@ -497,7 +497,6 @@ static struct ast_xml_node *xmldoc_get_node(const char *type, const char *name,
 			break;
 		}
 	}
-	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	return node;
 }
@@ -1253,13 +1252,18 @@ static char *_ast_xmldoc_build_syntax(struct ast_xml_node *root_node, const char
 char *ast_xmldoc_build_syntax(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	char *syntax;
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 	if (!node) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return _ast_xmldoc_build_syntax(node, type, name);
+	syntax = _ast_xmldoc_build_syntax(node, type, name);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return syntax;
 }
 
 /*!
@@ -1705,12 +1709,15 @@ char *ast_xmldoc_build_seealso(const char *type, const char *name, const char *m
 	}
 
 	/* get the application/function root node. */
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
 	output = _ast_xmldoc_build_seealso(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	return output;
 }
@@ -2077,18 +2084,23 @@ static char *_ast_xmldoc_build_arguments(struct ast_xml_node *node)
 char *ast_xmldoc_build_arguments(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	char *arguments;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return _ast_xmldoc_build_arguments(node);
+	arguments = _ast_xmldoc_build_arguments(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return arguments;
 }
 
 /*!
@@ -2192,20 +2204,27 @@ static char *_xmldoc_build_field(struct ast_xml_node *node, const char *var, int
 static char *xmldoc_build_field(const char *type, const char *name, const char *module, const char *var, int raw)
 {
 	struct ast_xml_node *node;
+	char *field;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		ast_log(LOG_ERROR, "Tried to look in XML tree with faulty values.\n");
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node) {
-		ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation\n", type, name);
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
+		ast_log(LOG_WARNING, "Couldn't find %s %s in XML documentation"
+			" If this module was recently built, run 'xmldoc reload' to refresh documentation\n",
+			type, name);
 		return NULL;
 	}
 
-	return _xmldoc_build_field(node, var, raw);
+	field = _xmldoc_build_field(node, var, raw);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return field;
 }
 
 /*!
@@ -2465,18 +2484,23 @@ static struct ast_xml_doc_item *xmldoc_build_list_responses(struct ast_xml_node
 struct ast_xml_doc_item *ast_xmldoc_build_list_responses(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	struct ast_xml_doc_item *responses;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return xmldoc_build_list_responses(node);
+	responses = xmldoc_build_list_responses(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return responses;
 }
 
 /*!
@@ -2530,18 +2554,23 @@ static struct ast_xml_doc_item *xmldoc_build_final_response(struct ast_xml_node
 struct ast_xml_doc_item *ast_xmldoc_build_final_response(const char *type, const char *name, const char *module)
 {
 	struct ast_xml_node *node;
+	static struct ast_xml_doc_item *response;
 
 	if (ast_strlen_zero(type) || ast_strlen_zero(name)) {
 		return NULL;
 	}
 
+	AST_RWLIST_RDLOCK(&xmldoc_tree);
 	node = xmldoc_get_node(type, name, module, documentation_language);
 
 	if (!node || !ast_xml_node_get_children(node)) {
+		AST_RWLIST_UNLOCK(&xmldoc_tree);
 		return NULL;
 	}
 
-	return xmldoc_build_final_response(node);
+	response = xmldoc_build_final_response(node);
+	AST_RWLIST_UNLOCK(&xmldoc_tree);
+	return response;
 }
 
 struct ast_xml_xpath_results *__attribute__((format(printf, 1, 2))) ast_xmldoc_query(const char *fmt, ...)
@@ -2860,25 +2889,35 @@ static char *handle_dump_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_a
 
 static struct ast_cli_entry cli_dump_xmldocs = AST_CLI_DEFINE(handle_dump_docs, "Dump the XML docs to the specified file");
 
-/*! \brief Close and unload XML documentation. */
-static void xmldoc_unload_documentation(void)
+static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
+static struct ast_cli_entry cli_reload_xmldocs = AST_CLI_DEFINE(handle_reload_docs, "Reload the XML docs");
+
+/*! \note Must be called with xmldoc_tree locked */
+static void xmldoc_purge_documentation(void)
 {
 	struct documentation_tree *doctree;
 
-	ast_cli_unregister(&cli_dump_xmldocs);
-
-	AST_RWLIST_WRLOCK(&xmldoc_tree);
 	while ((doctree = AST_RWLIST_REMOVE_HEAD(&xmldoc_tree, entry))) {
 		ast_free(doctree->filename);
 		ast_xml_close(doctree->doc);
 		ast_free(doctree);
 	}
+}
+
+/*! \brief Close and unload XML documentation. */
+static void xmldoc_unload_documentation(void)
+{
+	ast_cli_unregister(&cli_reload_xmldocs);
+	ast_cli_unregister(&cli_dump_xmldocs);
+
+	AST_RWLIST_WRLOCK(&xmldoc_tree);
+	xmldoc_purge_documentation();
 	AST_RWLIST_UNLOCK(&xmldoc_tree);
 
 	ast_xml_finish();
 }
 
-int ast_xmldoc_load_documentation(void)
+static int xmldoc_load_documentation(int first_time)
 {
 	struct ast_xml_node *root_node;
 	struct ast_xml_doc *tmpdoc;
@@ -2907,12 +2946,14 @@ int ast_xmldoc_load_documentation(void)
 		ast_config_destroy(cfg);
 	}
 
-	/* initialize the XML library. */
-	ast_xml_init();
-
-	ast_cli_register(&cli_dump_xmldocs);
-	/* register function to be run when asterisk finish. */
-	ast_register_cleanup(xmldoc_unload_documentation);
+	if (first_time) {
+		/* initialize the XML library. */
+		ast_xml_init();
+		ast_cli_register(&cli_dump_xmldocs);
+		ast_cli_register(&cli_reload_xmldocs);
+		/* register function to be run when asterisk finish. */
+		ast_register_cleanup(xmldoc_unload_documentation);
+	}
 
 	globbuf.gl_offs = 0;    /* slots to reserve in gl_pathv */
 
@@ -2942,6 +2983,16 @@ int ast_xmldoc_load_documentation(void)
 	ast_free(xmlpattern);
 
 	AST_RWLIST_WRLOCK(&xmldoc_tree);
+
+	if (!first_time) {
+		/* If we're reloading, purge the existing documentation.
+		 * We do this with the lock held so that if somebody
+		 * else tries to get documentation, there's no chance
+		 * of retrieiving it after we purged the old docs
+		 * but before we loaded the new ones. */
+		xmldoc_purge_documentation();
+	}
+
 	/* loop over expanded files */
 	for (i = 0; i < globbuf.gl_pathc; i++) {
 		/* check for duplicates (if we already [try to] open the same file. */
@@ -2993,4 +3044,31 @@ int ast_xmldoc_load_documentation(void)
 	return 0;
 }
 
+int ast_xmldoc_load_documentation(void)
+{
+	return xmldoc_load_documentation(1);
+}
+
+static int xmldoc_reload_documentation(void)
+{
+	return xmldoc_load_documentation(0);
+}
+
+static char *handle_reload_docs(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "xmldoc reload";
+		e->usage =
+			"Usage: xmldoc reload\n"
+			"  Reload XML documentation\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	xmldoc_reload_documentation();
+	return CLI_SUCCESS;
+}
+
 #endif /* AST_XML_DOCS */