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);
}