[asterisk-commits] tilghman: branch tilghman/odbc_tx_support r117327 - in /team/tilghman/odbc_tx...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue May 20 15:23:10 CDT 2008


Author: tilghman
Date: Tue May 20 15:23:10 2008
New Revision: 117327

URL: http://svn.digium.com/view/asterisk?view=rev&rev=117327
Log:
Merged revisions 117262,117264,117266,117295,117297 via svnmerge from 
https://origsvn.digium.com/svn/asterisk/trunk

........
r117262 | tilghman | 2008-05-20 11:13:48 -0500 (Tue, 20 May 2008) | 4 lines

Revert part of previous fix, and heavily comment the logic for object
destruction, for future users.
(Closes issue #12677)

........
r117264 | tilghman | 2008-05-20 11:25:16 -0500 (Tue, 20 May 2008) | 3 lines

Increase limit of unshared connections from 1023 to 4.2 billion.
(Related to issue #12677)

........
r117266 | rizzo | 2008-05-20 11:39:39 -0500 (Tue, 20 May 2008) | 5 lines

Reverse the check for Cookie: and remove leftover code implementing
the same thing.
Add an ast_debug() call to help debugging the url matching.


........
r117295 | rizzo | 2008-05-20 12:48:43 -0500 (Tue, 20 May 2008) | 10 lines

Document the possible presence of multiple variables with the
same name in http queries, which might confuse the manager.

Replace calls to ast_uri_decode() with a local function that also
replaces '+' with ' ', as this is the normal encoding for
spaces in http requests.
This allows passing cli commands to the manager through the
http interface.


........
r117297 | rizzo | 2008-05-20 13:07:24 -0500 (Tue, 20 May 2008) | 18 lines

+ Implement a variant of astman_get_header() to return the first or
  last match, and possibly skip empty fields.  The function is useful
  (and used here) when a form submits multiple 'Action' fields to the
  Manager.

  This change slightly modifies the current behaviour, but only in the
  case the user supplies multiple 'Action: ' lines and the first
  ones are empty, so the change is totally harmless.
 
+ Fix style on a couple of "if (displayconnects)" statements;
 
+ Expand a bit the 'Manager Test' interface, to make it slightly
  more user friendly. But also comment that the HTML should not
  be embedded in the C source.
 
None of this stuff needs to be applied to 1.4.


........

Modified:
    team/tilghman/odbc_tx_support/   (props changed)
    team/tilghman/odbc_tx_support/CHANGES
    team/tilghman/odbc_tx_support/funcs/func_odbc.c
    team/tilghman/odbc_tx_support/main/http.c
    team/tilghman/odbc_tx_support/main/manager.c
    team/tilghman/odbc_tx_support/res/res_odbc.c

Propchange: team/tilghman/odbc_tx_support/
------------------------------------------------------------------------------
--- svnmerge-integrated (original)
+++ svnmerge-integrated Tue May 20 15:23:10 2008
@@ -1,1 +1,1 @@
-/trunk:1-117241
+/trunk:1-117325

Modified: team/tilghman/odbc_tx_support/CHANGES
URL: http://svn.digium.com/view/asterisk/team/tilghman/odbc_tx_support/CHANGES?view=diff&rev=117327&r1=117326&r2=117327
==============================================================================
--- team/tilghman/odbc_tx_support/CHANGES (original)
+++ team/tilghman/odbc_tx_support/CHANGES Tue May 20 15:23:10 2008
@@ -14,6 +14,13 @@
    based on other parameters.  The default is still to search based on the
    forwarding station ID.  However, there are new options that allow you to search
    based on the message desk terminal ID, or the message desk number.
+ * TIMEOUT() has been modified to be accurate down to the millisecond.
+ * ENUM*() functions now include the following new options:
+     - 'u' returns the full URI and does not strip off the URI-scheme.
+	 - 's' triggers ISN specific rewriting
+	 - 'i' looks for branches into an Infrastructure ENUM tree
+	 - 'd' for a direct DNS lookup without any flipping of digits.
+ * TXCIDNAME() has a new zone-suffix parameter (which defaults to 'e164.arpa')
 
 Zaptel channel driver (chan_zap) Changes
 ----------------------------------------
@@ -106,20 +113,16 @@
   * Addresses managed by DNS manager now can check to see if there is a DNS
     SRV record for a given domain and will use that hostname/port if present.
 
-Dialplan function changes
--------------------------
- * TIMEOUT() has been modified to be accurate down to the millisecond.
- * ENUM*() functions now include the following new options:
-     - 'u' returns the full URI and does not strip off the URI-scheme.
-	 - 's' triggers ISN specific rewriting
-	 - 'i' looks for branches into an Infrastructure ENUM tree
-	 - 'd' for a direct DNS lookup without any flipping of digits.
- * TXCIDNAME() has a new zone-suffix parameter (which defaults to 'e164.arpa')
-
 AMI - The manager (TCP/TLS/HTTP)
 --------------------------------
   * The Status command now takes an optional list of variables to display
     along with channel status.
+
+ODBC Changes
+------------
+  * res_odbc no longer has a limit of 1023 total possible unshared connections,
+    as some people were running into this limit.  This limit has been increased
+    to 4.2 billion.
 
 ------------------------------------------------------------------------------
 --- Functionality changes from Asterisk 1.4.X to Asterisk 1.6.0  -------------

Modified: team/tilghman/odbc_tx_support/funcs/func_odbc.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/odbc_tx_support/funcs/func_odbc.c?view=diff&rev=117327&r1=117326&r2=117327
==============================================================================
--- team/tilghman/odbc_tx_support/funcs/func_odbc.c (original)
+++ team/tilghman/odbc_tx_support/funcs/func_odbc.c Tue May 20 15:23:10 2008
@@ -63,6 +63,40 @@
 	struct ast_custom_function *acf;
 };
 
+static const char *isolation2text(int iso)
+{
+	if (iso == 0) {
+		return "read_committed";
+	} else if (iso == 1) {
+		return "read_uncommitted";
+	} else if (iso == 2) {
+		return "serializable";
+	} else if (iso == 3) {
+		return "repeatable_read";
+	} else {
+		return "unknown";
+	}
+}
+
+static int text2isolation(const char *txt)
+{
+	if (strncasecmp(txt, "read_", 5) == 0) {
+		if (strncasecmp(txt + 5, "c", 1) == 0) {
+			return 0;
+		} else if (strncasecmp(txt + 5, "u", 1) == 0) {
+			return 1;
+		} else {
+			return -1;
+		}
+	} else if (strncasecmp(txt, "ser", 3) == 0) {
+		return 2;
+	} else if (strncasecmp(txt, "rep", 3) == 0) {
+		return 3;
+	} else {
+		return -1;
+	}
+}
+
 static void odbc_datastore_free(void *data);
 static void odbc_txn_free(void *data);
 
@@ -93,7 +127,7 @@
 	enum { ODBC_STATUS_TXCLOSED = 0, ODBC_STATUS_TXOPEN = 1 } status; /*!< Status of this transaction */
 	struct odbc_obj *obj;                                             /*!< Database handle within which transacted statements are run */
 	unsigned int active:1;                                            /*!< Is this record the current active transaction within the channel? */
-	char transaction_id[0];                                           /*!< Name of this transaction ID */
+	char name[0];                                                     /*!< Name of this transaction ID */
 };
 
 static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, struct odbc_obj *obj, const char *name, int active)
@@ -144,7 +178,7 @@
 	}
 
 	/* Nothing found, create one */
-	if (name && obj && (txn = ast_calloc(sizeof(*txn) + strlen(name) + 1))) {
+	if (name && obj && (txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1))) {
 		struct odbc_txn_frame *otxn;
 
 		strcpy(txn->name, name); /* SAFE */
@@ -161,7 +195,7 @@
 	return NULL;
 }
 
-static int mark_transaction_active(struct ast_channel *chan, struct odbj_obj *obj)
+static int mark_transaction_active(struct ast_channel *chan, struct odbc_obj *obj)
 {
 	struct ast_datastore *txn_store = ast_channel_datastore_find(chan, &odbc_info, NULL);
 	AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
@@ -626,6 +660,38 @@
 	return 0;
 }
 
+static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
+{
+	/* TODO */
+	return 0;
+}
+
+static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
+{
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(property);
+		AST_APP_ARG(opt);
+	);
+
+	AST_STANDARD_APP_ARGS(args, s);
+	if (strcasecmp(args.property, "transaction") == 0) {
+		/* Set active transaction */
+
+		return 0;
+	} else if (strcasecmp(args.property, "forcecommit") == 0) {
+		/* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
+
+		return 0;
+	} else if (strcasecmp(args.property, "isolation") == 0) {
+		/* How do uncommitted transactions affect reads? */
+
+		return 0;
+	} else {
+		ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
+		return -1;
+	}
+}
+
 static struct ast_custom_function escape_function = {
 	.name = "SQL_ESC",
 	.synopsis = "Escapes single ticks for use in SQL statements",
@@ -649,7 +715,7 @@
 "  transaction - gets or sets the active transaction ID.  If set, and the\n"
 "                transaction ID does not exist and a database name is specified\n"
 "                as an argument, it will be created.\n"
-"  autocommit  - controls whether a transaction will be automatically committed\n"
+"  forcecommit - controls whether a transaction will be automatically committed\n"
 "                when the channel hangs up.  Defaults to 0.  If a transaction\n"
 "                ID is specified in the optional argument, the property will be\n"
 "                applied to that ID, otherwise to the current active ID.\n"

Modified: team/tilghman/odbc_tx_support/main/http.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/odbc_tx_support/main/http.c?view=diff&rev=117327&r1=117326&r2=117327
==============================================================================
--- team/tilghman/odbc_tx_support/main/http.c (original)
+++ team/tilghman/odbc_tx_support/main/http.c Tue May 20 15:23:10 2008
@@ -370,6 +370,20 @@
 	AST_RWLIST_TRAVERSE_SAFE_END
 }
 
+/*
+ * Decode special characters in http uri.
+ * We have ast_uri_decode to handle %XX sequences, but spaces
+ * are encoded as a '+' so we need to replace them beforehand.
+ */
+static void http_decode(char *s)
+{
+	for (;*s; s++) {
+		if (*s == '+')
+			*s = ' ';
+	}
+	ast_uri_decode(s);
+}
+
 static struct ast_str *handle_uri(struct ast_tcptls_session_instance *ser, char *uri, enum ast_http_method method,
 				  int *status, char **title, int *contentlength, struct ast_variable **cookies, struct ast_variable *headers, 
 				  unsigned int *static_content)
@@ -387,18 +401,22 @@
 	if (method == AST_HTTP_GET) {
 		strsep(&params, "?");
 		
-		/* Extract arguments from the request and store them in variables. */
+		/* Extract arguments from the request and store them in variables.
+		 * Note that a request can have multiple arguments with the same
+		 * name, and we store them all in the list of variables.
+		 * It is up to the application to handle multiple values.
+		 */
 		if (params) {
 			char *var, *val;
 			
 			while ((val = strsep(&params, "&"))) {
 				var = strsep(&val, "=");
 				if (val) {
-					ast_uri_decode(val);
+					http_decode(val);
 				} else {
 					val = "";
 				}
-				ast_uri_decode(var);
+				http_decode(var);
 				if ((v = ast_variable_new(var, val, ""))) {
 					if (vars) {
 						prev->next = v;
@@ -412,9 +430,11 @@
 	}
 
 	/*
-	 * Append the cookies to the variables (the only reason to have them
-	 * at the end is to avoid another pass of the cookies list to find
-	 * the tail).
+	 * Append the cookies to the list of variables.
+	 * This saves a pass in the cookies list, but has the side effect
+	 * that a variable might mask a cookie with the same name if the
+	 * application stops at the first match.
+	 * Note that this is the same behaviour as $_REQUEST variables in PHP.
 	 */
 	if (prev) {
 		prev->next = *cookies;
@@ -423,7 +443,7 @@
 	}
 	*cookies = NULL;
 
-	ast_uri_decode(uri);
+	http_decode(uri);
 
 	AST_RWLIST_RDLOCK(&uri_redirects);
 	AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
@@ -451,6 +471,7 @@
 		/* scan registered uris to see if we match one. */
 		AST_RWLIST_RDLOCK(&uris);
 		AST_RWLIST_TRAVERSE(&uris, urih, entry) {
+			ast_debug(2, "match request [%s] with handler [%s] len %d\n", uri, urih->uri, l);
 			if (!saw_method) {
 				switch (method) {
 				case AST_HTTP_GET:
@@ -632,33 +653,9 @@
 		if (ast_strlen_zero(cookie)) {
 			break;
 		}
-		if (strncasecmp(cookie, "Cookie: ", 8)) {
-			char *name, *value;
-			struct ast_variable *var;
-
-			value = ast_strdupa(cookie);
-			name = strsep(&value, ":");
-			if (!value) {
-				continue;
-			}
-			value = ast_skip_blanks(value);
-			if (ast_strlen_zero(value)) {
-				continue;
-			}
-			var = ast_variable_new(name, value, "");
-			if (!var) { 
-				continue;
-			}
-			var->next = headers;
-			headers = var;
-
-			continue;
-		}
-
-		if (vars) {
-			ast_variables_destroy(vars);
-		}
-		vars = parse_cookies(cookie);
+		if (!strncasecmp(cookie, "Cookie: ", 8)) {
+			vars = parse_cookies(cookie);
+		}
 	}
 
 	if (!*uri) {

Modified: team/tilghman/odbc_tx_support/main/manager.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/odbc_tx_support/main/manager.c?view=diff&rev=117327&r1=117326&r2=117327
==============================================================================
--- team/tilghman/odbc_tx_support/main/manager.c (original)
+++ team/tilghman/odbc_tx_support/main/manager.c Tue May 20 15:23:10 2008
@@ -799,18 +799,47 @@
 	AST_LIST_UNLOCK(&sessions);
 }
 
-const char *astman_get_header(const struct message *m, char *var)
+/*
+ * Generic function to return either the first or the last matching header
+ * from a list of variables, possibly skipping empty strings.
+ * At the moment there is only one use of this function in this file,
+ * so we make it static.
+ */
+#define	GET_HEADER_FIRST_MATCH	0
+#define	GET_HEADER_LAST_MATCH	1
+#define	GET_HEADER_SKIP_EMPTY	2
+static const char *__astman_get_header(const struct message *m, char *var, int mode)
 {
 	int x, l = strlen(var);
+	const char *result = "";
 
 	for (x = 0; x < m->hdrcount; x++) {
 		const char *h = m->headers[x];
-		if (!strncasecmp(var, h, l) && h[l] == ':' && h[l+1] == ' ')
-			return h + l + 2;
+		if (!strncasecmp(var, h, l) && h[l] == ':' && h[l+1] == ' ') {
+			const char *x = h + l + 2;
+			/* found a potential candidate */
+			if (mode & GET_HEADER_SKIP_EMPTY && ast_strlen_zero(x))
+				continue;	/* not interesting */
+			if (mode & GET_HEADER_LAST_MATCH)
+				result = x;	/* record the last match so far */
+			else
+				return x;
+		}
 	}
 
 	return "";
 }
+
+/*
+ * Return the first matching variable from an array.
+ * This is the legacy function and is implemented in therms of
+ * __astman_get_header().
+ */
+const char *astman_get_header(const struct message *m, char *var)
+{
+	return __astman_get_header(m, var, GET_HEADER_FIRST_MATCH);
+}
+
 
 struct ast_variable *astman_get_variables(const struct message *m)
 {
@@ -2789,7 +2818,7 @@
 	struct manager_action *tmp;
 	const char *user = astman_get_header(m, "Username");
 
-	ast_copy_string(action, astman_get_header(m, "Action"), sizeof(action));
+	ast_copy_string(action, __astman_get_header(m, "Action", GET_HEADER_SKIP_EMPTY), sizeof(action));
 	ast_debug(1, "Manager received command '%s'\n", action);
 
 	if (ast_strlen_zero(action)) {
@@ -3617,17 +3646,20 @@
 		hdrlen = strlen(v->name) + strlen(v->value) + 3;
 		m.headers[m.hdrcount] = alloca(hdrlen);
 		snprintf((char *) m.headers[m.hdrcount], hdrlen, "%s: %s", v->name, v->value);
+		ast_verb(4, "HTTP Manager add header %s\n", m.headers[m.hdrcount]);
 		m.hdrcount = x + 1;
 	}
 
 	if (process_message(s, &m)) {
 		if (s->authenticated) {
-				if (manager_displayconnects(s))
+			if (manager_displayconnects(s)) {
 				ast_verb(2, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
+			}
 			ast_log(LOG_EVENT, "HTTP Manager '%s' logged off from %s\n", s->username, ast_inet_ntoa(s->sin.sin_addr));
 		} else {
-				if (displayconnects)
+			if (displayconnects) {
 				ast_verb(2, "HTTP Connect attempt from '%s' unable to authenticate\n", ast_inet_ntoa(s->sin.sin_addr));
+			}
 			ast_log(LOG_EVENT, "HTTP Failed attempt from %s\n", ast_inet_ntoa(s->sin.sin_addr));
 		}
 		s->needdestroy = 1;
@@ -3644,12 +3676,26 @@
 	if (format == FORMAT_XML) {
 		ast_str_append(&out, 0, "<ajax-response>\n");
 	} else if (format == FORMAT_HTML) {
+		/*
+		 * When handling AMI-over-HTTP in HTML format, we provide a simple form for
+		 * debugging purposes. This HTML code should not be here, we
+		 * should read from some config file...
+		 */
 
 #define ROW_FMT	"<tr><td colspan=\"2\" bgcolor=\"#f1f1ff\">%s</td></tr>\r\n"
 #define TEST_STRING \
-	"<form action=\"manager\">action: <input name=\"action\"> cmd <input name=\"command\"><br> \
-	user <input name=\"username\"> pass <input type=\"password\" name=\"secret\"><br> \
-	<input type=\"submit\"></form>"
+	"<form action=\"manager\">\n\
+	Action: <select name=\"action\">\n\
+		<option value=\"\">-----&gt;</option>\n\
+		<option value=\"login\">login</option>\n\
+		<option value=\"command\">Command</option>\n\
+		<option value=\"waitevent\">waitevent</option>\n\
+		<option value=\"listcommands\">listcommands</option>\n\
+	</select>\n\
+	or <input name=\"action\"><br/>\n\
+	CLI Command <input name=\"command\"><br>\n\
+	user <input name=\"username\"> pass <input type=\"password\" name=\"secret\"><br>\n\
+	<input type=\"submit\">\n</form>\n"
 
 		ast_str_append(&out, 0, "<title>Asterisk&trade; Manager Interface</title>");
 		ast_str_append(&out, 0, "<body bgcolor=\"#ffffff\"><table align=center bgcolor=\"#f1f1f1\" width=\"500\">\r\n");

Modified: team/tilghman/odbc_tx_support/res/res_odbc.c
URL: http://svn.digium.com/view/asterisk/team/tilghman/odbc_tx_support/res/res_odbc.c?view=diff&rev=117327&r1=117326&r2=117327
==============================================================================
--- team/tilghman/odbc_tx_support/res/res_odbc.c (original)
+++ team/tilghman/odbc_tx_support/res/res_odbc.c Tue May 20 15:23:10 2008
@@ -59,12 +59,12 @@
 	char *sanitysql;
 	SQLHENV env;
 	unsigned int haspool:1;              /*!< Boolean - TDS databases need this */
-	unsigned int limit:10;               /*!< Gives a limit of 1023 maximum */
-	unsigned int count:10;               /*!< Running count of pooled connections */
 	unsigned int delme:1;                /*!< Purge the class */
 	unsigned int backslash_is_escape:1;  /*!< On this database, the backslash is a native escape sequence */
 	unsigned int commit_uncommitted:1;   /*!< Should uncommitted transactions be auto-committed on handle release? */
 	unsigned int isolation:2;            /*!< Flags for how the DB should deal with data in uncommitted transactions */
+	unsigned int limit;                  /*!< 1023 wasn't enough for some people */
+	unsigned int count;                  /*!< Running count of pooled connections */
 	unsigned int idlecheck;              /*!< Recheck the connection if it is idle for this long */
 	struct ao2_container *obj_container;
 };
@@ -507,8 +507,9 @@
 	struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
 
 	while ((class = ao2_iterator_next(&aoi))) {
-		if (!strcmp(class->name, name))
+		if (!strcmp(class->name, name) && !class->delme) {
 			break;
+		}
 		ao2_ref(class, -1);
 	}
 
@@ -538,7 +539,6 @@
 			obj->parent = class;
 			if (odbc_obj_connect(obj) == ODBC_FAIL) {
 				ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
-				ast_mutex_destroy(&obj->lock);
 				ao2_ref(obj, -1);
 				obj = NULL;
 				class->count--;
@@ -663,27 +663,10 @@
 	return ODBC_SUCCESS;
 }
 
-static int class_is_delme(void *classobj, void *arg, int flags)
-{
-	struct odbc_class *class = classobj;
-
-	if (class->delme) {
-		struct odbc_obj *obj;
-		struct ao2_iterator aoi = ao2_iterator_init(class->obj_container, OBJ_UNLINK);
-		while ((obj = ao2_iterator_next(&aoi))) {
-			ao2_ref(obj, -2);
-		}
-		return CMP_MATCH;
-	}
-
-	return 0;
-}
-
-
-
 static int reload(void)
 {
 	struct odbc_class *class;
+	struct odbc_obj *current;
 	struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
 
 	/* First, mark all to be purged */
@@ -695,7 +678,50 @@
 	load_odbc_config();
 
 	/* Purge remaining classes */
-	ao2_callback(class_container, OBJ_NODATA | OBJ_UNLINK, class_is_delme, 0);
+
+	/* Note on how this works; this is a case of circular references, so we
+	 * explicitly do NOT want to use a callback here (or we wind up in
+	 * recursive hell).
+	 *
+	 * 1. Iterate through all the classes.  Note that the classes will currently
+	 * contain two classes of the same name, one of which is marked delme and
+	 * will be purged when all remaining objects of the class are released, and
+	 * the other, which was created above when we re-parsed the config file.
+	 * 2. On each class, there is a reference held by the master container and
+	 * a reference held by each connection object.  There are two cases for
+	 * destruction of the class, noted below.  However, in all cases, all O-refs
+	 * (references to objects) will first be freed, which will cause the C-refs
+	 * (references to classes) to be decremented (but never to 0, because the
+	 * class container still has a reference).
+	 *    a) If the class has outstanding objects, the C-ref by the class
+	 *    container will then be freed, which leaves only C-refs by any
+	 *    outstanding objects.  When the final outstanding object is released
+	 *    (O-refs held by applications and dialplan functions), it will in turn
+	 *    free the final C-ref, causing class destruction.
+	 *    b) If the class has no outstanding objects, when the class container
+	 *    removes the final C-ref, the class will be destroyed.
+	 */
+	aoi = ao2_iterator_init(class_container, 0);
+	while ((class = ao2_iterator_next(&aoi))) { /* C-ref++ (by iterator) */
+		if (class->delme) {
+			struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
+			while ((current = ao2_iterator_next(&aoi2))) { /* O-ref++ (by iterator) */
+				ao2_unlink(class->obj_container, current); /* unlink O-ref from class (reference handled implicitly) */
+				ao2_ref(current, -1); /* O-ref-- (by iterator) */
+				/* At this point, either
+				 * a) there's an outstanding O-ref, or
+				 * b) the object has already been destroyed.
+				 */
+			}
+			ao2_unlink(class_container, class); /* unlink C-ref from container (reference handled implicitly) */
+			/* At this point, either
+			 * a) there's an outstanding O-ref, which holds an outstanding C-ref, or
+			 * b) the last remaining C-ref is held by the iterator, which will be
+			 * destroyed in the next step.
+			 */
+		}
+		ao2_ref(class, -1); /* C-ref-- (by iterator) */
+	}
 
 	return 0;
 }




More information about the asterisk-commits mailing list