<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/17644">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
Kevin Harwell: Looks good to me, approved
Friendly Automation: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">sched: fix and test a double deref on delete of an executing call back<br><br>sched: Avoid a double deref when AST_SCHED_DEL_UNREF is called on an<br>executing call-back. This is done by adding a new variable 'rescheduled'<br>to the struct sched which is set in ast_sched_runq and checked in<br>ast_sched_del_nonrunning. ast_sched_del_nonrunning is a replacement for<br>now deprecated ast_sched_del which returns a new possible value -2<br>if called on an executing call-back with rescheduled set. ast_sched_del<br>is modified to call ast_sched_del_nonrunning to maintain existing code.<br>AST_SCHED_DEL_UNREF is also updated to look for the -2 in which case it<br>will not throw a warning or invoke refcall.<br>test_sched: Add a new unit test sched_test_freebird that will check the<br>reference count in the resolved scenario.<br><br>ASTERISK-29698<br><br>Change-Id: Icfb16b3acbc29cf5b4cef74183f7531caaefe21d<br>---<br>M include/asterisk/sched.h<br>M main/sched.c<br>M tests/test_sched.c<br>3 files changed, 196 insertions(+), 8 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/asterisk/sched.h b/include/asterisk/sched.h</span><br><span>index bbef914..af9b76c 100644</span><br><span>--- a/include/asterisk/sched.h</span><br><span>+++ b/include/asterisk/sched.h</span><br><span>@@ -72,20 +72,22 @@</span><br><span> /*!</span><br><span> * \brief schedule task to get deleted and call unref function</span><br><span> *</span><br><span style="color: hsl(0, 100%, 40%);">- * Only calls unref function if the delete succeeded.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Only calls the unref function if the task is actually deleted by</span><br><span style="color: hsl(120, 100%, 40%);">+ * ast_sched_del_nonrunning. If a failure occurs or the task is</span><br><span style="color: hsl(120, 100%, 40%);">+ * currently running and not rescheduled then refcall is not invoked.</span><br><span> *</span><br><span> * \sa AST_SCHED_DEL</span><br><span> * \since 1.6.1</span><br><span> */</span><br><span> #define AST_SCHED_DEL_UNREF(sched, id, refcall) \</span><br><span> do { \</span><br><span style="color: hsl(0, 100%, 40%);">- int _count = 0, _id; \</span><br><span style="color: hsl(0, 100%, 40%);">- while ((_id = id) > -1 && ast_sched_del(sched, _id) && ++_count < 10) { \</span><br><span style="color: hsl(120, 100%, 40%);">+ int _count = 0, _id, _ret = 0; \</span><br><span style="color: hsl(120, 100%, 40%);">+ while ((_id = id) > -1 && (( _ret = ast_sched_del_nonrunning(sched, _id)) == -1) && ++_count < 10) { \</span><br><span> usleep(1); \</span><br><span> } \</span><br><span> if (_count == 10) { \</span><br><span> ast_log(LOG_WARNING, "Unable to cancel schedule ID %d. This is probably a bug (%s: %s, line %d).\n", _id, __FILE__, __PRETTY_FUNCTION__, __LINE__); \</span><br><span style="color: hsl(0, 100%, 40%);">- } else if (_id > -1) { \</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (_id > -1 && _ret >-2) { \</span><br><span> refcall; \</span><br><span> id = -1; \</span><br><span> } \</span><br><span>@@ -294,10 +296,30 @@</span><br><span> *</span><br><span> * \retval -1 on failure</span><br><span> * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \deprecated in favor of ast_sched_del_nonrunning which checks if the event is running and rescheduled</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span> */</span><br><span> int ast_sched_del(struct ast_sched_context *con, int id) attribute_warn_unused_result;</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Deletes a scheduled event with care against the event running</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * Remove this event from being run. A procedure should not remove its own</span><br><span style="color: hsl(120, 100%, 40%);">+ * event, but return 0 instead. In most cases, you should not call this</span><br><span style="color: hsl(120, 100%, 40%);">+ * routine directly, but use the AST_SCHED_DEL() macro instead (especially if</span><br><span style="color: hsl(120, 100%, 40%);">+ * you don't intend to do something different when it returns failure).</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param con scheduling context to delete item from</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param id ID of the scheduled item to delete</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -1 on failure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -2 event was running but was deleted because it was not rescheduled</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 on success</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_sched_del_nonrunning(struct ast_sched_context *con, int id) attribute_warn_unused_result;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span> * \brief Determines number of seconds until the next outstanding event to take place</span><br><span> *</span><br><span> * Determine the number of seconds until the next outstanding event</span><br><span>diff --git a/main/sched.c b/main/sched.c</span><br><span>index e3a7d30..45e0677 100644</span><br><span>--- a/main/sched.c</span><br><span>+++ b/main/sched.c</span><br><span>@@ -98,6 +98,8 @@</span><br><span> ast_cond_t cond;</span><br><span> /*! Indication that a running task was deleted. */</span><br><span> unsigned int deleted:1;</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! Indication that a running task was rescheduled. */</span><br><span style="color: hsl(120, 100%, 40%);">+ unsigned int rescheduled:1;</span><br><span> };</span><br><span> </span><br><span> struct sched_thread {</span><br><span>@@ -606,11 +608,27 @@</span><br><span> * "id". It's nearly impossible that there</span><br><span> * would be two or more in the list with that</span><br><span> * id.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Deprecated in favor of ast_sched_del_nonrunning</span><br><span style="color: hsl(120, 100%, 40%);">+ * which checks running event status.</span><br><span> */</span><br><span> int ast_sched_del(struct ast_sched_context *con, int id)</span><br><span> {</span><br><span style="color: hsl(120, 100%, 40%);">+ return ast_sched_del_nonrunning(con, id) ? -1 : 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief</span><br><span style="color: hsl(120, 100%, 40%);">+ * Delete the schedule entry with number "id".</span><br><span style="color: hsl(120, 100%, 40%);">+ * If running, wait for the task to complete,</span><br><span style="color: hsl(120, 100%, 40%);">+ * check to see if it is rescheduled then</span><br><span style="color: hsl(120, 100%, 40%);">+ * schedule the release.</span><br><span style="color: hsl(120, 100%, 40%);">+ * It's nearly impossible that there would be</span><br><span style="color: hsl(120, 100%, 40%);">+ * two or more in the list with that id.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int ast_sched_del_nonrunning(struct ast_sched_context *con, int id)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span> struct sched *s = NULL;</span><br><span> int *last_id = ast_threadstorage_get(&last_del_id, sizeof(int));</span><br><span style="color: hsl(120, 100%, 40%);">+ int res = 0;</span><br><span> </span><br><span> DEBUG(ast_debug(1, "ast_sched_del(%d)\n", id));</span><br><span> </span><br><span>@@ -645,7 +663,17 @@</span><br><span> while (con->currently_executing && (id == con->currently_executing->sched_id->id)) {</span><br><span> ast_cond_wait(&s->cond, &con->lock);</span><br><span> }</span><br><span style="color: hsl(0, 100%, 40%);">- /* Do not sched_release() here because ast_sched_runq() will do it */</span><br><span style="color: hsl(120, 100%, 40%);">+ /* This is not rescheduled so the caller of ast_sched_del_nonrunning needs to know</span><br><span style="color: hsl(120, 100%, 40%);">+ * that it was still deleted</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!s->rescheduled) {</span><br><span style="color: hsl(120, 100%, 40%);">+ res = -2;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ /* ast_sched_runq knows we are waiting on this item and is passing responsibility for</span><br><span style="color: hsl(120, 100%, 40%);">+ * its destruction to us</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ sched_release(con, s);</span><br><span style="color: hsl(120, 100%, 40%);">+ s = NULL;</span><br><span> }</span><br><span> }</span><br><span> </span><br><span>@@ -658,7 +686,10 @@</span><br><span> }</span><br><span> ast_mutex_unlock(&con->lock);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- if (!s && *last_id != id) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if(res == -2){</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ else if (!s && *last_id != id) {</span><br><span> ast_debug(1, "Attempted to delete nonexistent schedule entry %d!\n", id);</span><br><span> /* Removing nonexistent schedule entry shouldn't trigger assert (it was enabled in DEV_MODE);</span><br><span> * because in many places entries is deleted without having valid id. */</span><br><span>@@ -668,7 +699,7 @@</span><br><span> return -1;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span> }</span><br><span> </span><br><span> void ast_sched_report(struct ast_sched_context *con, struct ast_str **buf, struct ast_cb_names *cbnames)</span><br><span>@@ -793,7 +824,13 @@</span><br><span> con->currently_executing = NULL;</span><br><span> ast_cond_signal(¤t->cond);</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">- if (res && !current->deleted) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (current->deleted) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /*</span><br><span style="color: hsl(120, 100%, 40%);">+ * Another thread is waiting on this scheduled item. That thread</span><br><span style="color: hsl(120, 100%, 40%);">+ * will be responsible for it's destruction</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ current->rescheduled = res ? 1 : 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (res) {</span><br><span> /*</span><br><span> * If they return non-zero, we should schedule them to be</span><br><span> * run again.</span><br><span>diff --git a/tests/test_sched.c b/tests/test_sched.c</span><br><span>index e995c2c..cff8d65 100644</span><br><span>--- a/tests/test_sched.c</span><br><span>+++ b/tests/test_sched.c</span><br><span>@@ -37,6 +37,7 @@</span><br><span> #include "asterisk/sched.h"</span><br><span> #include "asterisk/test.h"</span><br><span> #include "asterisk/cli.h"</span><br><span style="color: hsl(120, 100%, 40%);">+#include "asterisk/astobj2.h"</span><br><span> </span><br><span> static int sched_cb(const void *data)</span><br><span> {</span><br><span>@@ -336,6 +337,132 @@</span><br><span> return CLI_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+struct test_obj {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_t lock;</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_cond_t cond;</span><br><span style="color: hsl(120, 100%, 40%);">+ int scheduledCBstarted;</span><br><span style="color: hsl(120, 100%, 40%);">+ int id;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static void test_obj_cleanup(void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct test_obj *obj = data;</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_destroy(&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_cond_destroy(&obj->cond);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+static int lockingcb(const void *data)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct test_obj *obj = (struct test_obj *)data;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct timespec delay = {3,0};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_lock(&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ obj->scheduledCBstarted = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_cond_signal(&obj->cond);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_unlock(&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(obj, -1);</span><br><span style="color: hsl(120, 100%, 40%);">+ while (nanosleep(&delay, &delay));</span><br><span style="color: hsl(120, 100%, 40%);">+ /* sleep to force this scheduled event to remain running long</span><br><span style="color: hsl(120, 100%, 40%);">+ * enough for the scheduling thread to unlock and call</span><br><span style="color: hsl(120, 100%, 40%);">+ * AST_SCHED_DEL_UNREF</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+AST_TEST_DEFINE(sched_test_freebird)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct test_obj * obj;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct ast_sched_context * con;</span><br><span style="color: hsl(120, 100%, 40%);">+ enum ast_test_result_state res = AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ int refs;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (cmd) {</span><br><span style="color: hsl(120, 100%, 40%);">+ case TEST_INIT:</span><br><span style="color: hsl(120, 100%, 40%);">+ info->name = "sched_test_freebird";</span><br><span style="color: hsl(120, 100%, 40%);">+ info->category = "/main/sched/";</span><br><span style="color: hsl(120, 100%, 40%);">+ info->summary = "Test deadlock avoidance and double-unref";</span><br><span style="color: hsl(120, 100%, 40%);">+ info->description =</span><br><span style="color: hsl(120, 100%, 40%);">+ "This tests a call to AST_SCHED_DEL_UNREF on a running event.";</span><br><span style="color: hsl(120, 100%, 40%);">+ return AST_TEST_NOT_RUN;</span><br><span style="color: hsl(120, 100%, 40%);">+ case TEST_EXECUTE:</span><br><span style="color: hsl(120, 100%, 40%);">+ res = AST_TEST_PASS;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ obj = ao2_alloc(sizeof(struct test_obj), test_obj_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!obj) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test,</span><br><span style="color: hsl(120, 100%, 40%);">+ "ao2_alloc() did not return an object\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ obj->scheduledCBstarted = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ con = ast_sched_context_create();</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!con) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test,</span><br><span style="color: hsl(120, 100%, 40%);">+ "ast_sched_context_create() did not return a context\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_cleanup(obj);</span><br><span style="color: hsl(120, 100%, 40%);">+ return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_sched_start_thread(con)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "Failed to start test thread\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_cleanup(obj);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_sched_context_destroy(con);</span><br><span style="color: hsl(120, 100%, 40%);">+ return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* This double reference is to ensure that the object isn't destroyed prematurely</span><br><span style="color: hsl(120, 100%, 40%);">+ * in a case where it is unreffed an additional time.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(obj, +2);</span><br><span style="color: hsl(120, 100%, 40%);">+ if ((obj->id = ast_sched_add(con, 0, lockingcb, obj)) == -1) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "Failed to add scheduler entry\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_ref(obj, -3);</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_sched_context_destroy(con);</span><br><span style="color: hsl(120, 100%, 40%);">+ return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_lock(&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+ while(obj->scheduledCBstarted == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Wait for the scheduled thread to indicate that it has started so we can</span><br><span style="color: hsl(120, 100%, 40%);">+ * then call the AST_SCHED_DEL_UNREF macro</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_cond_wait(&obj->cond,&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_mutex_unlock(&obj->lock);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "Received signal, calling Scedule and UNREF\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "ID: %d\n", obj->id);</span><br><span style="color: hsl(120, 100%, 40%);">+ AST_SCHED_DEL_UNREF(con, obj->id, ao2_ref(obj, -1));</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ refs = ao2_ref(obj, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch(refs){</span><br><span style="color: hsl(120, 100%, 40%);">+ case 2:</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "Correct number of references '2'\n");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ default:</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_test_status_update(test, "Incorrect number of references '%d'\n",</span><br><span style="color: hsl(120, 100%, 40%);">+ refs);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /* Based on success or failure, the refcount could change</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+ while(ao2_ref(obj, -1) > 1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_sched_context_destroy(con);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static struct ast_cli_entry cli_sched[] = {</span><br><span> AST_CLI_DEFINE(handle_cli_sched_bench, "Benchmark ast_sched add/del performance"),</span><br><span> };</span><br><span>@@ -343,6 +470,7 @@</span><br><span> static int unload_module(void)</span><br><span> {</span><br><span> AST_TEST_UNREGISTER(sched_test_order);</span><br><span style="color: hsl(120, 100%, 40%);">+ AST_TEST_UNREGISTER(sched_test_freebird);</span><br><span> ast_cli_unregister_multiple(cli_sched, ARRAY_LEN(cli_sched));</span><br><span> return 0;</span><br><span> }</span><br><span>@@ -350,6 +478,7 @@</span><br><span> static int load_module(void)</span><br><span> {</span><br><span> AST_TEST_REGISTER(sched_test_order);</span><br><span style="color: hsl(120, 100%, 40%);">+ AST_TEST_REGISTER(sched_test_freebird);</span><br><span> ast_cli_register_multiple(cli_sched, ARRAY_LEN(cli_sched));</span><br><span> return AST_MODULE_LOAD_SUCCESS;</span><br><span> }</span><br><span></span><br></pre><div style="white-space:pre-wrap"></div><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/17644">change 17644</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/17644"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 16 </div>
<div style="display:none"> Gerrit-Change-Id: Icfb16b3acbc29cf5b4cef74183f7531caaefe21d </div>
<div style="display:none"> Gerrit-Change-Number: 17644 </div>
<div style="display:none"> Gerrit-PatchSet: 11 </div>
<div style="display:none"> Gerrit-Owner: Michael Bradeen <mbradeen@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Sean Bright <sean@seanbright.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>