static void resolver_thread_fn()

in source/host_resolver.c [964:1216]


static void resolver_thread_fn(void *arg) {
    struct host_entry *host_entry = arg;

    size_t unsolicited_resolve_max = host_entry->resolution_config.max_ttl;
    if (unsolicited_resolve_max == 0) {
        unsolicited_resolve_max = 1;
    }

    uint64_t max_no_solicitation_interval =
        aws_timestamp_convert(unsolicited_resolve_max, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL);

    struct aws_linked_list listener_list;
    aws_linked_list_init(&listener_list);

    struct aws_linked_list listener_destroy_list;
    aws_linked_list_init(&listener_destroy_list);

    bool keep_going = true;

    struct aws_array_list address_list;
    AWS_ZERO_STRUCT(address_list);
    struct aws_array_list new_address_list;
    AWS_ZERO_STRUCT(new_address_list);
    struct aws_array_list expired_address_list;
    AWS_ZERO_STRUCT(expired_address_list);

    if (aws_array_list_init_dynamic(&address_list, host_entry->allocator, 4, sizeof(struct aws_host_address))) {
        goto done;
    }

    if (aws_array_list_init_dynamic(&new_address_list, host_entry->allocator, 4, sizeof(struct aws_host_address))) {
        goto done;
    }

    if (aws_array_list_init_dynamic(&expired_address_list, host_entry->allocator, 4, sizeof(struct aws_host_address))) {
        goto done;
    }

    while (keep_going) {

        /* resolve and then process each record */
        int err_code = AWS_ERROR_SUCCESS;
        if (host_entry->resolution_config.impl(
                host_entry->allocator, host_entry->host_name, &address_list, host_entry->resolution_config.impl_data)) {

            err_code = aws_last_error();
        }

        if (err_code == AWS_ERROR_SUCCESS) {
            AWS_LOGF_DEBUG(
                AWS_LS_IO_DNS,
                "static, resolving host %s successful, returned %d addresses",
                aws_string_c_str(host_entry->host_name),
                (int)aws_array_list_length(&address_list));
        } else {
            AWS_LOGF_WARN(
                AWS_LS_IO_DNS,
                "static, resolving host %s failed, ec %d (%s)",
                aws_string_c_str(host_entry->host_name),
                err_code,
                aws_error_debug_str(err_code));
        }

        uint64_t timestamp = s_get_system_time_for_default_resolver(host_entry->resolver);
        uint64_t new_expiry = timestamp + (host_entry->resolution_config.max_ttl * NS_PER_SEC);

        struct aws_linked_list pending_resolve_copy;
        aws_linked_list_init(&pending_resolve_copy);

        /*
         * Within the lock we
         *  (1) Update the cache with the newly resolved addresses
         *  (2) Process all held addresses looking for expired or promotable ones
         *  (3) Prep for callback invocations
         */
        aws_mutex_lock(&host_entry->entry_lock);

        if (!err_code) {
            s_update_address_cache(host_entry, &address_list, new_expiry);
        }

        /*
         * process and clean_up records in the entry. occasionally, failed connect records will be upgraded
         * for retry.
         */
        process_records(host_entry, host_entry->aaaa_records, host_entry->failed_connection_aaaa_records);
        process_records(host_entry, host_entry->a_records, host_entry->failed_connection_a_records);

        aws_linked_list_swap_contents(&pending_resolve_copy, &host_entry->pending_resolution_callbacks);

        aws_mutex_unlock(&host_entry->entry_lock);

        /*
         * Clean up resolved addressed outside of the lock
         */
        s_clear_address_list(&address_list);

        struct aws_host_address address_array[2];
        AWS_ZERO_ARRAY(address_array);

        /*
         * Perform the actual subscriber notifications
         */
        while (!aws_linked_list_empty(&pending_resolve_copy)) {
            struct aws_linked_list_node *resolution_callback_node = aws_linked_list_pop_front(&pending_resolve_copy);
            struct pending_callback *pending_callback =
                AWS_CONTAINER_OF(resolution_callback_node, struct pending_callback, node);

            struct aws_array_list callback_address_list;
            aws_array_list_init_static(&callback_address_list, address_array, 2, sizeof(struct aws_host_address));

            aws_mutex_lock(&host_entry->entry_lock);
            s_copy_address_into_callback_set(
                s_get_lru_address(host_entry, AWS_ADDRESS_RECORD_TYPE_AAAA),
                &callback_address_list,
                host_entry->host_name);
            s_copy_address_into_callback_set(
                s_get_lru_address(host_entry, AWS_ADDRESS_RECORD_TYPE_A),
                &callback_address_list,
                host_entry->host_name);
            aws_mutex_unlock(&host_entry->entry_lock);

            size_t callback_address_list_size = aws_array_list_length(&callback_address_list);
            if (callback_address_list_size > 0) {
                AWS_LOGF_DEBUG(
                    AWS_LS_IO_DNS,
                    "static, invoking resolution callback for host %s with %d addresses",
                    aws_string_c_str(host_entry->host_name),
                    (int)callback_address_list_size);
            } else {
                AWS_LOGF_DEBUG(
                    AWS_LS_IO_DNS,
                    "static, invoking resolution callback for host %s with failure",
                    aws_string_c_str(host_entry->host_name));
            }

            if (callback_address_list_size > 0) {
                pending_callback->callback(
                    host_entry->resolver,
                    host_entry->host_name,
                    AWS_OP_SUCCESS,
                    &callback_address_list,
                    pending_callback->user_data);

            } else {
                int error_code = (err_code != AWS_ERROR_SUCCESS) ? err_code : AWS_IO_DNS_QUERY_FAILED;
                pending_callback->callback(
                    host_entry->resolver, host_entry->host_name, error_code, NULL, pending_callback->user_data);
            }

            s_clear_address_list(&callback_address_list);

            aws_mem_release(host_entry->allocator, pending_callback);
        }

        aws_mutex_lock(&host_entry->entry_lock);

        ++host_entry->resolves_since_last_request;

        /* wait for a quit notification or the base resolve frequency time interval */
        aws_condition_variable_wait_for_pred(
            &host_entry->entry_signal,
            &host_entry->entry_lock,
            host_entry->resolve_frequency_ns,
            s_host_entry_finished_pred,
            host_entry);

        aws_mutex_unlock(&host_entry->entry_lock);

        /*
         * This is a bit awkward that we unlock the entry and then relock both the resolver and the entry, but it
         * is mandatory that -- in order to maintain the consistent view of the resolver table (entry exist => entry
         * is alive and can be queried) -- we have the resolver lock as well before making the decision to remove
         * the entry from the table and terminate the thread.
         */
        struct default_host_resolver *resolver = host_entry->resolver->impl;
        aws_mutex_lock(&resolver->resolver_lock);

        /* Remove any listeners from our listener list that have been marked pending destroy, moving them into the
         * destroy list. */
        s_resolver_thread_cull_pending_destroy_listeners(&listener_list, &listener_destroy_list);

        /* Grab any listeners on the listener entry, moving them into the local list. */
        s_resolver_thread_move_listeners_from_listener_entry(resolver, host_entry->host_name, &listener_list);

        aws_mutex_lock(&host_entry->entry_lock);

        uint64_t now = s_get_system_time_for_default_resolver(host_entry->resolver);
        bool pinned = s_is_host_entry_pinned_by_listener(&listener_list);

        /*
         * Ideally this should just be time-based, but given the non-determinism of waits (and spurious wake ups) and
         * clock time, I feel much more comfortable keeping an additional constraint in terms of iterations.
         *
         * Note that we have the entry lock now and if any queries have arrived since our last resolution,
         * resolves_since_last_request will be 0 or 1 (depending on timing) and so, regardless of wait and wake up
         * timings, this check will always fail in that case leading to another iteration to satisfy the pending
         * query(ies).
         *
         * The only way we terminate the loop with pending queries is if the resolver itself has no more references
         * to it and is going away.  In that case, the pending queries will be completed (with failure) by the
         * final clean up of this entry.
         */
        if (host_entry->resolves_since_last_request > unsolicited_resolve_max &&
            host_entry->last_resolve_request_timestamp_ns + max_no_solicitation_interval < now && !pinned) {
            host_entry->state = DRS_SHUTTING_DOWN;
        }

        keep_going = host_entry->state == DRS_ACTIVE;
        if (!keep_going) {
            aws_hash_table_remove(&resolver->host_entry_table, host_entry->host_name, NULL, NULL);

            /* Move any local listeners we have back to the listener entry */
            if (s_resolver_thread_move_listeners_to_listener_entry(resolver, host_entry->host_name, &listener_list)) {
                AWS_LOGF_ERROR(AWS_LS_IO_DNS, "static: could not clean up all listeners from resolver thread.");
            }
        }

        aws_array_list_swap_contents(&host_entry->new_addresses, &new_address_list);
        aws_array_list_swap_contents(&host_entry->expired_addresses, &expired_address_list);

        aws_mutex_unlock(&host_entry->entry_lock);
        aws_mutex_unlock(&resolver->resolver_lock);

        /* Destroy any listeners in our destroy list. */
        s_resolver_thread_destroy_listeners(&listener_destroy_list);

        /* Notify our local listeners of new addresses. */
        s_resolver_thread_notify_listeners(&listener_list, &new_address_list, &expired_address_list);

        s_clear_address_list(&new_address_list);
        s_clear_address_list(&expired_address_list);
    }

    AWS_LOGF_DEBUG(
        AWS_LS_IO_DNS,
        "static: Either no requests have been made for an address for %s for the duration "
        "of the ttl, or this thread is being forcibly shutdown. Killing thread.",
        host_entry->host_name->bytes)

done:

    AWS_FATAL_ASSERT(aws_array_list_length(&address_list) == 0);
    AWS_FATAL_ASSERT(aws_array_list_length(&new_address_list) == 0);
    AWS_FATAL_ASSERT(aws_array_list_length(&expired_address_list) == 0);

    aws_array_list_clean_up(&address_list);
    aws_array_list_clean_up(&new_address_list);
    aws_array_list_clean_up(&expired_address_list);

    /* please don't fail */
    aws_thread_current_at_exit(s_on_host_entry_shutdown_completion, host_entry);
}