<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(&current->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>