pjsip_options.c: Fix race condition stopping periodic out of dialog OPTIONS request.
[asterisk/asterisk.git] / res / res_pjsip / pjsip_options.c
index 1c26251..453b21e 100644 (file)
@@ -113,7 +113,7 @@ static void update_contact_status(const struct ast_sip_contact *contact,
 
        /* if the contact is available calculate the rtt as
           the diff between the last start time and "now" */
-       update->rtt = update->status ?
+       update->rtt = update->status == AVAILABLE ?
                ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0;
 
        update->rtt_start = ast_tv(0, 0);
@@ -240,18 +240,21 @@ static struct ast_sip_endpoint *find_an_endpoint(struct ast_sip_contact *contact
 
 /*!
  * \internal
- * \brief Receive an response to the qualify contact request.
+ * \brief Receive a response to the qualify contact request.
  */
 static void qualify_contact_cb(void *token, pjsip_event *e)
 {
        struct ast_sip_contact *contact = token;
 
        switch(e->body.tsx_state.type) {
+       default:
+               ast_log(LOG_ERROR, "Unexpected PJSIP event %d\n", e->body.tsx_state.type);
+               /* Fall through */
        case PJSIP_EVENT_TRANSPORT_ERROR:
        case PJSIP_EVENT_TIMER:
                update_contact_status(contact, UNAVAILABLE);
                break;
-       default:
+       case PJSIP_EVENT_RX_MSG:
                update_contact_status(contact, AVAILABLE);
                break;
        }
@@ -308,6 +311,7 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
                != PJ_SUCCESS) {
                ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
                        contact->uri);
+               update_contact_status(contact, UNAVAILABLE);
                ao2_ref(contact, -1);
                return -1;
        }
@@ -354,10 +358,12 @@ static void sched_data_destructor(void *obj)
  */
 static struct sched_data *sched_data_create(struct ast_sip_contact *contact)
 {
-       struct sched_data *data = ao2_alloc(sizeof(*data), sched_data_destructor);
+       struct sched_data *data;
 
+       data = ao2_t_alloc(sizeof(*data), sched_data_destructor, contact->uri);
        if (!data) {
-               ast_log(LOG_ERROR, "Unable to create schedule qualify data\n");
+               ast_log(LOG_ERROR, "Unable to create schedule qualify data for contact %s\n",
+                       contact->uri);
                return NULL;
        }
 
@@ -392,11 +398,14 @@ static int qualify_contact_sched(const void *obj)
        ao2_ref(data->contact, +1);
        if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) {
                ao2_ref(data->contact, -1);
-               ao2_cleanup(data);
-               return 0;
        }
 
-       return data->contact->qualify_frequency * 1000;
+       /*
+        * Always reschedule rather than have a potential race cleaning
+        * up the data object ref between self deletion and an external
+        * deletion.
+        */
+       return 1;
 }
 
 /*!
@@ -405,24 +414,27 @@ static int qualify_contact_sched(const void *obj)
  */
 static void schedule_qualify(struct ast_sip_contact *contact)
 {
-       RAII_VAR(struct sched_data *, data, sched_data_create(contact), ao2_cleanup);
+       struct sched_data *data;
 
+       data = sched_data_create(contact);
        if (!data) {
                return;
        }
 
-       ao2_ref(data, +1);
-       if ((data->id = ast_sched_add_variable(
-                   sched, contact->qualify_frequency * 1000,
-                   qualify_contact_sched, data, 1)) < 0) {
+       ast_assert(contact->qualify_frequency != 0);
 
-               ao2_ref(data, -1);
+       ao2_t_ref(data, +1, "Ref for qualify_contact_sched() scheduler entry");
+       data->id = ast_sched_add_variable(sched, contact->qualify_frequency * 1000,
+               qualify_contact_sched, data, 0);
+       if (data->id < 0) {
+               ao2_t_ref(data, -1, "Cleanup failed scheduler add");
                ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n",
                        contact->uri);
-               return;
+       } else if (!ao2_link(sched_qualifies, data)) {
+               AST_SCHED_DEL_UNREF(sched, data->id,
+                       ao2_t_ref(data, -1, "Cleanup scheduler for failed ao2_link"));
        }
-
-       ao2_link(sched_qualifies, data);
+       ao2_t_ref(data, -1, "Done setting up scheduler entry");
 }
 
 /*!
@@ -438,8 +450,9 @@ static void unschedule_qualify(struct ast_sip_contact *contact)
                return;
        }
 
-       AST_SCHED_DEL_UNREF(sched, data->id, ao2_cleanup(data));
-       ao2_ref(data, -1);
+       AST_SCHED_DEL_UNREF(sched, data->id,
+               ao2_t_ref(data, -1, "Delete scheduler entry ref"));
+       ao2_t_ref(data, -1, "Done with ao2_find ref");
 }
 
 /*!
@@ -452,7 +465,9 @@ static void qualify_and_schedule(struct ast_sip_contact *contact)
 
        if (contact->qualify_frequency) {
                ao2_ref(contact, +1);
-               ast_sip_push_task(NULL, qualify_contact_task, contact);
+               if (ast_sip_push_task(NULL, qualify_contact_task, contact)) {
+                       ao2_ref(contact, -1);
+               }
 
                schedule_qualify(contact);
        }
@@ -498,24 +513,45 @@ static const struct ast_sorcery_observer contact_observer = {
 
 static pj_bool_t options_start(void)
 {
-       if (!(sched = ast_sched_context_create()) ||
-           ast_sched_start_thread(sched)) {
+       sched = ast_sched_context_create();
+       if (!sched) {
+               return -1;
+       }
+       if (ast_sched_start_thread(sched)) {
+               ast_sched_context_destroy(sched);
+               sched = NULL;
+               return -1;
+       }
+
+       if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer)) {
+               ast_log(LOG_WARNING, "Unable to add contact observer\n");
+               ast_sched_context_destroy(sched);
+               sched = NULL;
                return -1;
        }
 
        return PJ_SUCCESS;
 }
 
+static int sched_qualifies_empty(void *obj, void *arg, int flags)
+{
+       ao2_t_ref(obj, -1, "Release ref held by destroyed scheduler context.");
+       return CMP_MATCH;
+}
+
 static pj_bool_t options_stop(void)
 {
        ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer);
 
-       ao2_t_ref(sched_qualifies, -1, "Remove scheduled qualifies on module stop");
-
        if (sched) {
                ast_sched_context_destroy(sched);
+               sched = NULL;
        }
 
+       /* Empty the container of scheduling data refs. */
+       ao2_callback(sched_qualifies, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,
+               sched_qualifies_empty, NULL);
+
        return PJ_SUCCESS;
 }
 
@@ -1022,39 +1058,39 @@ static struct ast_sip_endpoint_formatter contact_status_formatter = {
 
 int ast_res_pjsip_init_options_handling(int reload)
 {
-       const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
+       static const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
 
        if (reload) {
                qualify_and_schedule_all();
                return 0;
        }
 
-       if (!(sched_qualifies = ao2_t_container_alloc(
-               QUALIFIED_BUCKETS, sched_qualifies_hash_fn, sched_qualifies_cmp_fn,
-               "Create container for scheduled qualifies"))) {
+       sched_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS,
+               sched_qualifies_hash_fn, sched_qualifies_cmp_fn,
+               "Create container for scheduled qualifies");
+       if (!sched_qualifies) {
                return -1;
        }
 
        if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
-               options_stop();
+               ao2_cleanup(sched_qualifies);
+               sched_qualifies = NULL;
                return -1;
        }
 
-       if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
+       if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW,
+               NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
                pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
-               return -1;
-       }
-
-       if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer)) {
-               ast_log(LOG_WARNING, "Unable to add contact observer\n");
+               ao2_cleanup(sched_qualifies);
+               sched_qualifies = NULL;
                return -1;
        }
 
        ast_sip_register_endpoint_formatter(&contact_status_formatter);
+       ast_manager_register2("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_sip_qualify, NULL, NULL, NULL);
+       ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
 
        qualify_and_schedule_all();
-       ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
-       ast_manager_register2("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_sip_qualify, NULL, NULL, NULL);
 
        return 0;
 }
@@ -1064,4 +1100,8 @@ void ast_res_pjsip_cleanup_options_handling(void)
        ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));
        ast_manager_unregister("PJSIPQualify");
        ast_sip_unregister_endpoint_formatter(&contact_status_formatter);
+
+       pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
+       ao2_cleanup(sched_qualifies);
+       sched_qualifies = NULL;
 }