diff options
Diffstat (limited to 'src/lib-http/http-client-queue.c')
-rw-r--r-- | src/lib-http/http-client-queue.c | 1065 |
1 files changed, 1065 insertions, 0 deletions
diff --git a/src/lib-http/http-client-queue.c b/src/lib-http/http-client-queue.c new file mode 100644 index 0000000..5c7915a --- /dev/null +++ b/src/lib-http/http-client-queue.c @@ -0,0 +1,1065 @@ +/* Copyright (c) 2013-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "str.h" +#include "str-sanitize.h" +#include "hash.h" +#include "array.h" +#include "bsearch-insert-pos.h" +#include "llist.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "time-util.h" +#include "dns-lookup.h" +#include "http-response-parser.h" + +#include "http-client-private.h" + +#define TIMEOUT_CMP_MARGIN_USECS 2000 + +static void +http_client_queue_fail_full(struct http_client_queue *queue, + unsigned int status, const char *error, bool all); +static void +http_client_queue_set_delay_timer(struct http_client_queue *queue, + struct timeval time); +static void +http_client_queue_set_request_timer(struct http_client_queue *queue, + const struct timeval *time); + +/* + * Queue object + */ + +static struct http_client_queue * +http_client_queue_find(struct http_client_host *host, + const struct http_client_peer_addr *addr) +{ + struct http_client_queue *queue; + + array_foreach_elem(&host->queues, queue) { + if (http_client_peer_addr_cmp(&queue->addr, addr) == 0) + return queue; + } + + return NULL; +} + +static struct http_client_queue * +http_client_queue_create(struct http_client_host *host, + const struct http_client_peer_addr *addr) +{ + const char *hostname = host->shared->name; + struct http_client_queue *queue; + + queue = i_new(struct http_client_queue, 1); + queue->client = host->client; + queue->host = host; + queue->addr = *addr; + + switch (addr->type) { + case HTTP_CLIENT_PEER_ADDR_RAW: + queue->name = i_strdup_printf("raw://%s:%u", + hostname, addr->a.tcp.port); + queue->addr.a.tcp.https_name = NULL; + break; + case HTTP_CLIENT_PEER_ADDR_HTTPS_TUNNEL: + case HTTP_CLIENT_PEER_ADDR_HTTPS: + queue->name = i_strdup_printf("https://%s:%u", + hostname, addr->a.tcp.port); + queue->addr_name = i_strdup(addr->a.tcp.https_name); + queue->addr.a.tcp.https_name = queue->addr_name; + break; + case HTTP_CLIENT_PEER_ADDR_HTTP: + queue->name = i_strdup_printf("http://%s:%u", + hostname, addr->a.tcp.port); + queue->addr.a.tcp.https_name = NULL; + break; + case HTTP_CLIENT_PEER_ADDR_UNIX: + queue->name = i_strdup_printf("unix:%s", addr->a.un.path); + queue->addr_name = i_strdup(addr->a.un.path); + queue->addr.a.un.path = queue->addr_name; + break; + default: + i_unreached(); + } + + queue->event = event_create(queue->client->event); + event_set_append_log_prefix(queue->event, + t_strdup_printf("queue %s: ", str_sanitize(queue->name, 256))); + queue->ips_connect_idx = 0; + i_array_init(&queue->pending_peers, 8); + i_array_init(&queue->requests, 16); + i_array_init(&queue->queued_requests, 16); + i_array_init(&queue->queued_urgent_requests, 16); + i_array_init(&queue->delayed_requests, 4); + array_push_back(&host->queues, &queue); + + return queue; +} + +struct http_client_queue * +http_client_queue_get(struct http_client_host *host, + const struct http_client_peer_addr *addr) +{ + struct http_client_queue *queue; + + queue = http_client_queue_find(host, addr); + if (queue == NULL) + queue = http_client_queue_create(host, addr); + + return queue; +} + +void http_client_queue_free(struct http_client_queue *queue) +{ + struct http_client_peer *peer; + ARRAY_TYPE(http_client_peer) peers; + + e_debug(queue->event, "Destroy"); + + /* Currently only called when peer is freed, so there is no need to + unlink from the peer */ + + /* Unlink all peers */ + if (queue->cur_peer != NULL) { + struct http_client_peer *peer = queue->cur_peer; + + queue->cur_peer = NULL; + http_client_peer_unlink_queue(peer, queue); + } + t_array_init(&peers, array_count(&queue->pending_peers)); + array_copy(&peers.arr, 0, &queue->pending_peers.arr, 0, + array_count(&queue->pending_peers)); + array_foreach_elem(&peers, peer) + http_client_peer_unlink_queue(peer, queue); + array_free(&queue->pending_peers); + + /* Abort all requests */ + http_client_queue_fail_full(queue, HTTP_CLIENT_REQUEST_ERROR_ABORTED, + "Aborted", TRUE); + array_free(&queue->requests); + array_free(&queue->queued_requests); + array_free(&queue->queued_urgent_requests); + array_free(&queue->delayed_requests); + + /* Cancel timeouts */ + timeout_remove(&queue->to_connect); + timeout_remove(&queue->to_delayed); + + /* Free */ + event_unref(&queue->event); + i_free(queue->addr_name); + i_free(queue->name); + i_free(queue); +} + +/* + * Error handling + */ + +static void +http_client_queue_fail_full(struct http_client_queue *queue, + unsigned int status, const char *error, bool all) +{ + ARRAY_TYPE(http_client_request) *req_arr, treqs; + struct http_client_request *req; + unsigned int retained = 0; + + /* Abort requests */ + req_arr = &queue->requests; + t_array_init(&treqs, array_count(req_arr)); + array_copy(&treqs.arr, 0, &req_arr->arr, 0, array_count(req_arr)); + array_foreach_elem(&treqs, req) { + i_assert(req->state >= HTTP_REQUEST_STATE_QUEUED); + if (!all && + req->state != HTTP_REQUEST_STATE_QUEUED) + retained++; + else + http_client_request_error(&req, status, error); + } + + /* All queues should be empty now... unless new requests were submitted + from the callback. this invariant captures it all: */ + i_assert((retained + + array_count(&queue->delayed_requests) + + array_count(&queue->queued_requests) + + array_count(&queue->queued_urgent_requests)) == + array_count(&queue->requests)); +} + +static void +http_client_queue_fail(struct http_client_queue *queue, + unsigned int status, const char *error) +{ + http_client_queue_fail_full(queue, status, error, FALSE); +} + +/* + * Connection management + */ + +static bool +http_client_queue_is_last_connect_ip(struct http_client_queue *queue) +{ + const struct http_client_settings *set = + &queue->client->set; + struct http_client_host *host = queue->host; + unsigned int ips_count = http_client_host_get_ips_count(host); + + i_assert(queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX); + i_assert(queue->ips_connect_idx < ips_count); + i_assert(queue->ips_connect_start_idx < ips_count); + + /* If a maximum connect attempts > 1 is set, enforce it directly */ + if (set->max_connect_attempts > 1 && + queue->connect_attempts >= set->max_connect_attempts) + return TRUE; + + /* Otherwise, we'll always go through all the IPs. we don't necessarily + start connecting from the first IP, so we'll need to treat the IPs as + a ring buffer where we automatically wrap back to the first IP + when necessary. */ + return ((queue->ips_connect_idx + 1) % ips_count == + queue->ips_connect_start_idx); +} + +static void +http_client_queue_recover_from_lookup(struct http_client_queue *queue) +{ + struct http_client_host *host = queue->host; + unsigned int ip_idx; + + i_assert(queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX); + + if (queue->cur_peer == NULL) { + queue->ips_connect_idx = queue->ips_connect_start_idx = 0; + return; + } + + if (http_client_host_get_ip_idx( + host, &queue->cur_peer->shared->addr.a.tcp.ip, &ip_idx)) { + /* Continue with current peer */ + queue->ips_connect_idx = queue->ips_connect_start_idx = ip_idx; + } else { + /* Reset connect attempts */ + queue->ips_connect_idx = queue->ips_connect_start_idx = 0; + } +} + +static void +http_client_queue_soft_connect_timeout(struct http_client_queue *queue) +{ + struct http_client_host *host = queue->host; + const struct http_client_peer_addr *addr = &queue->addr; + unsigned int ips_count = http_client_host_get_ips_count(host); + const char *https_name; + + i_assert(queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX); + + timeout_remove(&queue->to_connect); + + if (http_client_queue_is_last_connect_ip(queue)) { + /* No more IPs to try */ + return; + } + + /* If our our previous connection attempt takes longer than the + soft_connect_timeout, we start a connection attempt to the next IP in + parallel */ + https_name = http_client_peer_addr_get_https_name(addr); + e_debug(queue->event, "Connection to %s%s is taking a long time; " + "starting parallel connection attempt to next IP", + http_client_peer_addr2str(addr), + (https_name == NULL ? + "" : t_strdup_printf(" (SSL=%s)", https_name))); + + /* Next IP */ + queue->ips_connect_idx = (queue->ips_connect_idx + 1) % ips_count; + + /* Setup connection to new peer (can start new soft timeout) */ + http_client_queue_connection_setup(queue); +} + +static struct http_client_peer * +http_client_queue_connection_attempt(struct http_client_queue *queue) +{ + struct http_client *client = queue->client; + struct http_client_host *host = queue->host; + struct http_client_peer *peer; + struct http_client_peer_addr *addr = &queue->addr; + unsigned int num_requests = + array_count(&queue->queued_requests) + + array_count(&queue->queued_urgent_requests); + const char *ssl = ""; + int ret; + + if (num_requests == 0) + return NULL; + + /* Check whether host IPs are still up-to-date */ + ret = http_client_host_refresh(host); + if (ret < 0) { + /* Performing asynchronous lookup */ + timeout_remove(&queue->to_connect); + return NULL; + } + if (ret > 0) { + /* New lookup performed */ + http_client_queue_recover_from_lookup(queue); + } + + /* Update our peer address */ + if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) { + const struct ip_addr *ip = + http_client_host_get_ip(host, queue->ips_connect_idx); + + queue->addr.a.tcp.ip = *ip; + ssl = http_client_peer_addr_get_https_name(addr); + ssl = (ssl == NULL ? "" : t_strdup_printf(" (SSL=%s)", ssl)); + } + + /* Already got a peer? */ + peer = NULL; + if (queue->cur_peer != NULL) { + i_assert(array_count(&queue->pending_peers) == 0); + + /* Is it still the one we want? */ + if (http_client_peer_addr_cmp( + addr, &queue->cur_peer->shared->addr) == 0) { + /* Is it still connected? */ + if (http_client_peer_is_connected(queue->cur_peer)) { + /* Yes */ + e_debug(queue->event, + "Using existing connection to %s%s " + "(%u requests pending)", + http_client_peer_addr2str(addr), + ssl, num_requests); + + /* Handle requests; */ + http_client_peer_trigger_request_handler( + queue->cur_peer); + return queue->cur_peer; + } + /* No */ + peer = queue->cur_peer; + } else { + /* Peer is not relevant to this queue anymore */ + http_client_peer_unlink_queue(queue->cur_peer, queue); + } + + queue->cur_peer = NULL; + } + + if (peer == NULL) + peer = http_client_peer_get(queue->client, addr); + + e_debug(queue->event, + "Setting up connection to %s%s (%u requests pending)", + http_client_peer_addr2str(addr), ssl, num_requests); + + /* Create provisional link between queue and peer */ + http_client_peer_link_queue(peer, queue); + + /* Handle requests; creates new connections when needed/possible */ + http_client_peer_trigger_request_handler(peer); + + if (http_client_peer_is_connected(peer)) { + /* Drop any pending peers */ + if (array_count(&queue->pending_peers) > 0) { + struct http_client_peer *pending_peer; + + array_foreach_elem(&queue->pending_peers, pending_peer) { + if (pending_peer == peer) { + /* This can happen with shared clients + */ + continue; + } + i_assert(http_client_peer_addr_cmp( + &pending_peer->shared->addr, addr) != 0); + http_client_peer_unlink_queue(pending_peer, queue); + } + array_clear(&queue->pending_peers); + } + queue->cur_peer = peer; + + http_client_peer_trigger_request_handler(queue->cur_peer); + + } else { + struct http_client_peer *pending_peer; + unsigned int msecs; + bool new_peer = TRUE; + + /* Not already connected, wait for connections */ + + /* We may be waiting for this peer already */ + array_foreach_elem(&queue->pending_peers, pending_peer) { + if (http_client_peer_addr_cmp( + &pending_peer->shared->addr, addr) == 0) { + i_assert(pending_peer == peer); + new_peer = FALSE; + break; + } + } + if (new_peer) { + e_debug(queue->event, "Started new connection to %s%s", + http_client_peer_addr2str(addr), ssl); + + array_push_back(&queue->pending_peers, &peer); + if (queue->connect_attempts++ == 0) + queue->first_connect_time = ioloop_timeval; + } + + /* Start soft connect time-out + (but only if we have another IP left) */ + if (queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) { + msecs = client->set.soft_connect_timeout_msecs; + if (!http_client_queue_is_last_connect_ip(queue) && + msecs > 0 && queue->to_connect == NULL) { + queue->to_connect = timeout_add_to( + client->ioloop, msecs, + http_client_queue_soft_connect_timeout, + queue); + } + } + } + + return peer; +} + +void http_client_queue_connection_setup(struct http_client_queue *queue) +{ + (void)http_client_queue_connection_attempt(queue); +} + +unsigned int +http_client_queue_host_lookup_done(struct http_client_queue *queue) +{ + unsigned int reqs_pending = + http_client_queue_requests_pending(queue, NULL); + + http_client_queue_recover_from_lookup(queue); + if (reqs_pending > 0) + http_client_queue_connection_setup(queue); + return reqs_pending; +} + +void http_client_queue_host_lookup_failure( + struct http_client_queue *queue, const char *error) +{ + http_client_queue_fail( + queue, HTTP_CLIENT_REQUEST_ERROR_HOST_LOOKUP_FAILED, error); +} + +void http_client_queue_connection_success(struct http_client_queue *queue, + struct http_client_peer *peer) +{ + const struct http_client_peer_addr *addr = &peer->shared->addr; + struct http_client_host *host = queue->host; + + if (http_client_host_ready(host) && + queue->addr.type != HTTP_CLIENT_PEER_ADDR_UNIX) { + /* We achieved at least one connection the the addr->ip */ + if (!http_client_host_get_ip_idx( + host, &addr->a.tcp.ip, &queue->ips_connect_start_idx)) { + /* list of IPs changed during connect */ + queue->ips_connect_start_idx = 0; + } + } + + /* Reset attempt counter */ + queue->connect_attempts = 0; + + /* stop soft connect time-out */ + timeout_remove(&queue->to_connect); + + /* Drop all other attempts to the hport. note that we get here whenever + a connection is successfully created, so pending_peers array + may be empty. */ + if (array_count(&queue->pending_peers) > 0) { + struct http_client_peer *pending_peer; + + array_foreach_elem(&queue->pending_peers, pending_peer) { + if (pending_peer == peer) { + /* Don't drop any connections to the + successfully connected peer, even if some of + the connections are pending. they may be + intended for urgent requests. */ + i_assert(queue->cur_peer == NULL); + queue->cur_peer = pending_peer; + continue; + } + /* Unlink this queue from the peer; if this was the + last/only queue, the peer will be freed, closing all + connections. + */ + http_client_peer_unlink_queue(pending_peer, queue); + } + + array_clear(&queue->pending_peers); + i_assert(queue->cur_peer != NULL); + } +} + +void http_client_queue_connection_failure(struct http_client_queue *queue, + struct http_client_peer *peer, + const char *reason) +{ + const struct http_client_settings *set = + &queue->client->set; + const struct http_client_peer_addr *addr = &peer->shared->addr; + const char *https_name = http_client_peer_addr_get_https_name(addr); + struct http_client_host *host = queue->host; + unsigned int ips_count = http_client_host_get_ips_count(host); + struct http_client_peer *const *peer_idx; + unsigned int num_requests = + array_count(&queue->queued_requests) + + array_count(&queue->queued_urgent_requests); + + e_debug(queue->event, + "Failed to set up connection to %s%s: %s " + "(%u peers pending, %u requests pending)", + http_client_peer_addr2str(addr), + (https_name == NULL ? + "" : t_strdup_printf(" (SSL=%s)", https_name)), + reason, array_count(&queue->pending_peers), num_requests); + + http_client_peer_unlink_queue(peer, queue); + + if (array_count(&queue->pending_peers) == 0) { + i_assert(queue->cur_peer == NULL || queue->cur_peer == peer); + queue->cur_peer = NULL; + } else { + bool found = FALSE; + + i_assert(queue->cur_peer == NULL); + + /* We're still doing the initial connections to this hport. if + we're also doing parallel connections with soft timeouts + (pending_peer_count>1), wait for them to finish first. */ + array_foreach(&queue->pending_peers, peer_idx) { + if (*peer_idx == peer) { + array_delete(&queue->pending_peers, + array_foreach_idx( + &queue->pending_peers, + peer_idx), 1); + found = TRUE; + break; + } + } + i_assert(found); + if (array_count(&queue->pending_peers) > 0) { + e_debug(queue->event, + "Waiting for remaining pending peers."); + return; + } + + /* One of the connections failed. if we're not using soft + timeouts, we need to try to connect to the next IP. if we are + using soft timeouts, we've already tried all of the IPs by + now. */ + timeout_remove(&queue->to_connect); + + if (queue->addr.type == HTTP_CLIENT_PEER_ADDR_UNIX) { + http_client_queue_fail( + queue, HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, + reason); + return; + } + } + + if (http_client_queue_is_last_connect_ip(queue)) { + if (array_count(&queue->pending_peers) > 0) { + /* Other connection attempts still pending */ + return; + } + + /* All IPs failed up until here and we allow no more connect + attempts, but try the next ones on the next request. */ + queue->ips_connect_idx = queue->ips_connect_start_idx = + (queue->ips_connect_idx + 1) % ips_count; + + if (set->max_connect_attempts == 0 || + queue->connect_attempts >= set->max_connect_attempts) { + + e_debug(queue->event, + "Failed to set up any connection; " + "failing all queued requests"); + if (queue->connect_attempts > 1) { + unsigned int total_msecs = + timeval_diff_msecs(&ioloop_timeval, + &queue->first_connect_time); + reason = t_strdup_printf( + "%s (%u attempts in %u.%03u secs)", + reason, queue->connect_attempts, + total_msecs/1000, total_msecs%1000); + } + queue->connect_attempts = 0; + http_client_queue_fail( + queue, HTTP_CLIENT_REQUEST_ERROR_CONNECT_FAILED, + reason); + return; + } + } else { + queue->ips_connect_idx = + (queue->ips_connect_idx + 1) % ips_count; + } + + if (http_client_queue_connection_attempt(queue) != peer) + http_client_peer_unlink_queue(peer, queue); + return; +} + +void http_client_queue_peer_disconnected(struct http_client_queue *queue, + struct http_client_peer *peer) +{ + struct http_client_peer *const *peer_idx; + + if (queue->cur_peer == peer) { + queue->cur_peer = NULL; + return; + } + + array_foreach(&queue->pending_peers, peer_idx) { + if (*peer_idx == peer) { + array_delete(&queue->pending_peers, + array_foreach_idx(&queue->pending_peers, + peer_idx), 1); + break; + } + } +} + +/* + * Main request queue + */ + +void http_client_queue_drop_request(struct http_client_queue *queue, + struct http_client_request *req) +{ + struct http_client_request **reqs; + unsigned int count, i; + + e_debug(queue->event, + "Dropping request %s", http_client_request_label(req)); + + /* Drop from queue */ + if (req->urgent) { + reqs = array_get_modifiable(&queue->queued_urgent_requests, + &count); + for (i = 0; i < count; i++) { + if (reqs[i] == req) { + array_delete(&queue->queued_urgent_requests, + i, 1); + break; + } + } + } else { + reqs = array_get_modifiable(&queue->queued_requests, &count); + for (i = 0; i < count; i++) { + if (reqs[i] == req) { + array_delete(&queue->queued_requests, i, 1); + break; + } + } + } + + /* Drop from delay queue */ + if (req->release_time.tv_sec > 0) { + reqs = array_get_modifiable(&queue->delayed_requests, &count); + for (i = 0; i < count; i++) { + if (reqs[i] == req) + break; + } + if (i < count) { + if (i == 0) { + if (queue->to_delayed != NULL) { + timeout_remove(&queue->to_delayed); + if (count > 1) { + i_assert(reqs[1]->release_time.tv_sec > 0); + http_client_queue_set_delay_timer( + queue, reqs[1]->release_time); + } + } + } + array_delete(&queue->delayed_requests, i, 1); + } + } + + /* Drop from main request list */ + reqs = array_get_modifiable(&queue->requests, &count); + for (i = 0; i < count; i++) { + if (reqs[i] == req) + break; + } + i_assert(i < count); + + if (i == 0) { + if (queue->to_request != NULL) { + timeout_remove(&queue->to_request); + if (count > 1 && reqs[1]->timeout_time.tv_sec > 0) { + http_client_queue_set_request_timer(queue, + &reqs[1]->timeout_time); + } + } + } + req->queue = NULL; + array_delete(&queue->requests, i, 1); + + if (array_count(&queue->requests) == 0) + http_client_host_check_idle(queue->host); + return; +} + +static void http_client_queue_request_timeout(struct http_client_queue *queue) +{ + struct http_client_request *const *reqs; + ARRAY_TYPE(http_client_request) failed_requests; + struct timeval new_to = { 0, 0 }; + string_t *str; + size_t prefix_size; + unsigned int count, i; + + e_debug(queue->event, "Timeout (now: %s.%03lu)", + t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_timeval.tv_sec), + ((unsigned long)ioloop_timeval.tv_usec) / 1000); + + timeout_remove(&queue->to_request); + + /* Collect failed requests */ + reqs = array_get(&queue->requests, &count); + i_assert(count > 0); + t_array_init(&failed_requests, count); + for (i = 0; i < count; i++) { + if (reqs[i]->timeout_time.tv_sec > 0 && + timeval_cmp_margin(&reqs[i]->timeout_time, + &ioloop_timeval, + TIMEOUT_CMP_MARGIN_USECS) > 0) { + break; + } + array_push_back(&failed_requests, &reqs[i]); + } + + /* Update timeout */ + if (i < count) + new_to = reqs[i]->timeout_time; + + str = t_str_new(64); + str_append(str, "Request "); + prefix_size = str_len(str); + + /* Abort all failed request */ + reqs = array_get(&failed_requests, &count); + i_assert(count > 0); /* At least one request timed out */ + for (i = 0; i < count; i++) { + struct http_client_request *req = reqs[i]; + + str_truncate(str, prefix_size); + http_client_request_append_stats_text(req, str); + + e_debug(queue->event, + "Absolute timeout expired for request %s (%s)", + http_client_request_label(req), str_c(str)); + http_client_request_error( + &req, HTTP_CLIENT_REQUEST_ERROR_TIMED_OUT, + t_strdup_printf( + "Absolute request timeout expired (%s)", + str_c(str))); + } + + if (new_to.tv_sec > 0) { + e_debug(queue->event, "New timeout"); + http_client_queue_set_request_timer(queue, &new_to); + } +} + +static void +http_client_queue_set_request_timer(struct http_client_queue *queue, + const struct timeval *time) +{ + i_assert(time->tv_sec > 0); + timeout_remove(&queue->to_request); + + e_debug(queue->event, + "Set request timeout to %s.%03lu (now: %s.%03lu)", + t_strflocaltime("%Y-%m-%d %H:%M:%S", time->tv_sec), + ((unsigned long)time->tv_usec) / 1000, + t_strflocaltime("%Y-%m-%d %H:%M:%S", ioloop_timeval.tv_sec), + ((unsigned long)ioloop_timeval.tv_usec) / 1000); + + /* Set timer */ + queue->to_request = timeout_add_absolute_to( + queue->client->ioloop, time, + http_client_queue_request_timeout, queue); +} + +static int +http_client_queue_request_timeout_cmp(struct http_client_request *const *req1, + struct http_client_request *const *req2) +{ + int ret; + + /* 0 means no timeout */ + if ((*req1)->timeout_time.tv_sec == 0) { + if ((*req2)->timeout_time.tv_sec == 0) { + /* sort by age */ + ret = timeval_cmp(&(*req1)->submit_time, + &(*req2)->submit_time); + if (ret != 0) + return ret; + } else { + return 1; + } + } else if ((*req2)->timeout_time.tv_sec == 0) { + return -1; + + /* Sort by timeout */ + } else if ((ret = timeval_cmp(&(*req1)->timeout_time, + &(*req2)->timeout_time)) != 0) { + return ret; + } + + /* Sort by minimum attempts for fairness */ + return ((int)(*req2)->attempts - (int)(*req1)->attempts); +} + +static void +http_client_queue_submit_now(struct http_client_queue *queue, + struct http_client_request *req) +{ + ARRAY_TYPE(http_client_request) *req_queue; + + req->release_time.tv_sec = 0; + req->release_time.tv_usec = 0; + + if (req->urgent) + req_queue = &queue->queued_urgent_requests; + else + req_queue = &queue->queued_requests; + + /* Enqueue */ + if (req->timeout_time.tv_sec == 0) { + /* No timeout; enqueue at end */ + array_push_back(req_queue, &req); + } else if (timeval_diff_msecs(&req->timeout_time, + &ioloop_timeval) <= 1) { + /* Pretty much already timed out; don't bother */ + return; + } else { + unsigned int insert_idx; + + /* Keep transmission queue sorted earliest timeout first */ + (void)array_bsearch_insert_pos( + req_queue, &req, + http_client_queue_request_timeout_cmp, &insert_idx); + array_insert(req_queue, insert_idx, &req, 1); + } + + http_client_queue_connection_setup(queue); +} + +/* + * Delayed request queue + */ + +static void +http_client_queue_delay_timeout(struct http_client_queue *queue) +{ + struct http_client_request *const *reqs; + unsigned int count, i, finished; + + timeout_remove(&queue->to_delayed); + io_loop_time_refresh(); + + finished = 0; + reqs = array_get(&queue->delayed_requests, &count); + for (i = 0; i < count; i++) { + if (timeval_cmp_margin(&reqs[i]->release_time, + &ioloop_timeval, + TIMEOUT_CMP_MARGIN_USECS) > 0) { + break; + } + + e_debug(queue->event, "Activated delayed request %s%s", + http_client_request_label(reqs[i]), + (reqs[i]->urgent ? " (urgent)" : "")); + http_client_queue_submit_now(queue, reqs[i]); + finished++; + } + if (i < count) + http_client_queue_set_delay_timer(queue, reqs[i]->release_time); + array_delete(&queue->delayed_requests, 0, finished); +} + +static void +http_client_queue_set_delay_timer(struct http_client_queue *queue, + struct timeval time) +{ + struct http_client *client = queue->client; + int usecs = timeval_diff_usecs(&time, &ioloop_timeval); + int msecs; + + /* Round up to nearest microsecond */ + msecs = (usecs + 999) / 1000; + + /* Set timer */ + timeout_remove(&queue->to_delayed); + queue->to_delayed = timeout_add_to( + client->ioloop, msecs, + http_client_queue_delay_timeout, queue); +} + +static int +http_client_queue_delayed_cmp(struct http_client_request *const *req1, + struct http_client_request *const *req2) +{ + return timeval_cmp(&(*req1)->release_time, &(*req2)->release_time); +} + +/* + * Request submission + */ + +void http_client_queue_submit_request(struct http_client_queue *queue, + struct http_client_request *req) +{ + unsigned int insert_idx; + + if (req->queue != NULL) + http_client_queue_drop_request(req->queue, req); + req->queue = queue; + + /* Check delay vs timeout */ + if (req->release_time.tv_sec > 0 && req->timeout_time.tv_sec > 0 && + timeval_cmp_margin(&req->release_time, &req->timeout_time, + TIMEOUT_CMP_MARGIN_USECS) >= 0) { + /* Release time is later than absolute timeout */ + req->release_time.tv_sec = 0; + req->release_time.tv_usec = 0; + + /* Timeout rightaway */ + req->timeout_time = ioloop_timeval; + + e_debug(queue->event, + "Delayed request %s%s already timed out", + http_client_request_label(req), + (req->urgent ? " (urgent)" : "")); + } + + /* Add to main request list */ + if (req->timeout_time.tv_sec == 0) { + /* No timeout; just append */ + array_push_back(&queue->requests, &req); + } else { + unsigned int insert_idx; + + /* Keep main request list sorted earliest timeout first */ + (void)array_bsearch_insert_pos( + &queue->requests, &req, + http_client_queue_request_timeout_cmp, &insert_idx); + array_insert(&queue->requests, insert_idx, &req, 1); + + /* Now first in queue; update timer */ + if (insert_idx == 0) { + http_client_queue_set_request_timer(queue, + &req->timeout_time); + } + } + + /* Handle delay */ + if (req->release_time.tv_sec > 0) { + io_loop_time_refresh(); + + if (timeval_cmp_margin(&req->release_time, &ioloop_timeval, + TIMEOUT_CMP_MARGIN_USECS) > 0) { + e_debug(queue->event, + "Delayed request %s%s submitted " + "(time remaining: %d msecs)", + http_client_request_label(req), + (req->urgent ? " (urgent)" : ""), + timeval_diff_msecs(&req->release_time, + &ioloop_timeval)); + + (void)array_bsearch_insert_pos( + &queue->delayed_requests, &req, + http_client_queue_delayed_cmp, &insert_idx); + array_insert(&queue->delayed_requests, insert_idx, + &req, 1); + if (insert_idx == 0) { + http_client_queue_set_delay_timer( + queue, req->release_time); + } + return; + } + } + + http_client_queue_submit_now(queue, req); +} + +/* + * Request retrieval + */ + +struct http_client_request * +http_client_queue_claim_request(struct http_client_queue *queue, + const struct http_client_peer_addr *addr, + bool no_urgent) +{ + struct http_client_request *const *requests; + struct http_client_request *req; + unsigned int i, count; + + count = 0; + if (!no_urgent) + requests = array_get(&queue->queued_urgent_requests, &count); + + if (count == 0) + requests = array_get(&queue->queued_requests, &count); + if (count == 0) + return NULL; + i = 0; + req = requests[i]; + if (req->urgent) + array_delete(&queue->queued_urgent_requests, i, 1); + else + array_delete(&queue->queued_requests, i, 1); + + e_debug(queue->event, + "Connection to peer %s claimed request %s %s", + http_client_peer_addr2str(addr), http_client_request_label(req), + (req->urgent ? "(urgent)" : "")); + + return req; +} + +unsigned int +http_client_queue_requests_pending(struct http_client_queue *queue, + unsigned int *num_urgent_r) +{ + unsigned int urg_count = array_count(&queue->queued_urgent_requests); + + if (num_urgent_r != NULL) + *num_urgent_r = urg_count; + return array_count(&queue->queued_requests) + urg_count; +} + +unsigned int http_client_queue_requests_active(struct http_client_queue *queue) +{ + return array_count(&queue->requests); +} + +/* + * Ioloop + */ + +void http_client_queue_switch_ioloop(struct http_client_queue *queue) +{ + if (queue->to_connect != NULL) + queue->to_connect = io_loop_move_timeout(&queue->to_connect); + if (queue->to_request != NULL) + queue->to_request = io_loop_move_timeout(&queue->to_request); + if (queue->to_delayed != NULL) + queue->to_delayed = io_loop_move_timeout(&queue->to_delayed); +} |