diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 8ef084e5e..4d1c17373 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -2133,7 +2133,7 @@ void listen_start_accept(struct listen_dnsport* listen) } struct tcp_req_info* -tcp_req_info_create(struct sldns_buffer* spoolbuf) +tcp_req_info_create(struct comm_base* base, struct sldns_buffer* spoolbuf) { struct tcp_req_info* req = (struct tcp_req_info*)malloc(sizeof(*req)); if(!req) { @@ -2141,6 +2141,12 @@ tcp_req_info_create(struct sldns_buffer* spoolbuf) return NULL; } memset(req, 0, sizeof(*req)); + req->read_again_timer = comm_timer_create(base, tcp_read_again_cb, req); + if(!req->read_again_timer) { + log_err("malloc failure"); + free(req); + return NULL; + } req->spool_buffer = spoolbuf; return req; } @@ -2150,6 +2156,7 @@ tcp_req_info_delete(struct tcp_req_info* req) { if(!req) return; tcp_req_info_clear(req); + comm_timer_delete(req->read_again_timer); /* cp is pointer back to commpoint that owns this struct and * called delete on us */ /* spool_buffer is shared udp buffer, not deleted here */ @@ -2189,6 +2196,9 @@ void tcp_req_info_clear(struct tcp_req_info* req) req->done_req_list = NULL; req->num_done_req = 0; req->read_is_closed = 0; + + if(comm_timer_is_set(req->read_again_timer)) + comm_timer_disable(req->read_again_timer); } void diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index ae0463468..4aeb6aa70 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -347,6 +347,10 @@ struct tcp_req_info { int num_done_req; /** list of pending writable result packets, malloced one at a time */ struct tcp_req_done_item* done_req_list; + /** the read again timer, when the number of pipelined TCP queries + * is large, it waits, zero time, for a new event loop to service + * the remainder of the TCP traffic on the fd. */ + struct comm_timer* read_again_timer; }; /** @@ -377,10 +381,12 @@ struct tcp_req_done_item { * Create tcp request info structure that keeps track of open * requests on the TCP channel that are resolved at the same time, * and the pending results that have to get written back to that client. + * @param base: comm base for read again timer. * @param spoolbuf: shared buffer * @return new structure or NULL on alloc failure. */ -struct tcp_req_info* tcp_req_info_create(struct sldns_buffer* spoolbuf); +struct tcp_req_info* tcp_req_info_create(struct comm_base* base, + struct sldns_buffer* spoolbuf); /** * Delete tcp request structure. Called by owning commpoint. diff --git a/testcode/testbound.c b/testcode/testbound.c index 063037df4..3a3bcaef6 100644 --- a/testcode/testbound.c +++ b/testcode/testbound.c @@ -786,3 +786,13 @@ size_t doq_table_quic_size_get(struct doq_table* ATTR_UNUSED(table)) return 0; } #endif + +void tcp_read_again_cb(void* ATTR_UNUSED(arg)) +{ + /* nothing */ +} + +void tcp_more_read_again_cb(void* ATTR_UNUSED(arg)) +{ + /* nothing */ +} diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 0ff6840ad..1be742bd5 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -141,6 +141,8 @@ fptr_whitelist_comm_timer(void (*fptr)(void*)) #ifdef UB_ON_WINDOWS else if(fptr == &wsvc_cron_cb) return 1; #endif + else if(fptr == &tcp_read_again_cb) return 1; + else if(fptr == &tcp_more_read_again_cb) return 1; else if(fptr == &auth_xfer_timer) return 1; else if(fptr == &auth_xfer_probe_timer_callback) return 1; else if(fptr == &auth_xfer_transfer_timer_callback) return 1; diff --git a/util/netevent.c b/util/netevent.c index 432fead1b..6b1957dc3 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -122,6 +122,10 @@ #define NUM_UDP_PER_SELECT 1 #endif +/** The number of TCP queries over a TCP connection, per read indication + * from select. */ +#define NUM_TCP_PER_SELECT 100 + /** timeout in millisec to wait for write to unblock, packets dropped after.*/ #define SEND_BLOCKED_WAIT_TIMEOUT 200 /** max number of times to wait for write to unblock, packets dropped after.*/ @@ -4621,6 +4625,10 @@ comm_point_tcp_handle_write(int fd, struct comm_point* c) static int tcp_req_info_read_again(int fd, struct comm_point* c) { + /* One event-loop visit drains at most this many pipelined queries; + * the rest is re-queued, so that other file descriptors get + * serviced in between. */ + int budget = NUM_TCP_PER_SELECT; while(c->tcp_req_info->read_again) { int r; c->tcp_req_info->read_again = 0; @@ -4637,6 +4645,16 @@ tcp_req_info_read_again(int fd, struct comm_point* c) } return 0; } + if(--budget <= 0 && c->tcp_req_info->read_again) { + /* Defer the rest of the drain to the next loop turn. + * This uses a zero delay timer. For TLS the undrained + * remainder sits in OpenSSL's user-space buffer. */ + struct timeval tv; + memset(&tv, 0, sizeof(tv)); + verbose(VERB_ALGO, "Defer tcp_req_info read again"); + comm_timer_set(c->tcp_req_info->read_again_timer, &tv); + return 1; + } } return 1; } @@ -4650,6 +4668,7 @@ tcp_more_read_again(int fd, struct comm_point* c) /* this continues until the read routines get EAGAIN or so, * and thus does not call the callback, and the bool is 0 */ int* moreread = c->tcp_more_read_again; + int budget = NUM_TCP_PER_SELECT; while(moreread && *moreread) { *moreread = 0; if(!comm_point_tcp_handle_read(fd, c, 0)) { @@ -4662,6 +4681,30 @@ tcp_more_read_again(int fd, struct comm_point* c) } return; } + if(--budget <= 0 && *moreread) { + /* Defer the rest of the drain to the next loop turn. + * This uses a zero delay timer. For TLS the undrained + * remainder sits in OpenSSL's user-space buffer. */ + struct timeval tv; + memset(&tv, 0, sizeof(tv)); + if(!c->tcp_more_read_again_timer) { + c->tcp_more_read_again_timer = comm_timer_create(c->ev->base, tcp_more_read_again_cb, c); + if(!c->tcp_more_read_again_timer) { + log_err("out of memory for tcp more read again timer"); + reclaim_tcp_handler(c); + if(!c->tcp_do_close) { + fptr_ok(fptr_whitelist_comm_point( + c->callback)); + (void)(*c->callback)(c, c->cb_arg, + NETEVENT_CLOSED, NULL); + } + return; + } + } + verbose(VERB_ALGO, "Defer more read again"); + comm_timer_set(c->tcp_more_read_again_timer, &tv); + return; + } } } @@ -4689,6 +4732,23 @@ tcp_more_write_again(int fd, struct comm_point* c) } } +void +tcp_read_again_cb(void* arg) +{ + struct tcp_req_info* req = (struct tcp_req_info*)arg; + verbose(VERB_ALGO, "tcp_read_again_cb"); + if(!tcp_req_info_read_again(req->cp->fd, req->cp)) + return; +} + +void +tcp_more_read_again_cb(void* arg) +{ + struct comm_point* c = (struct comm_point*)arg; + verbose(VERB_ALGO, "tcp_more_read_again_cb"); + tcp_more_read_again(c->fd, c); +} + void comm_point_tcp_handle_callback(int fd, short event, void* arg) { @@ -6131,7 +6191,7 @@ comm_point_create_tcp_handler(struct comm_base *base, c->pp2_enabled = parent->pp2_enabled; c->pp2_header_state = pp2_header_none; if(spoolbuf) { - c->tcp_req_info = tcp_req_info_create(spoolbuf); + c->tcp_req_info = tcp_req_info_create(base, spoolbuf); if(!c->tcp_req_info) { log_err("could not create tcp commpoint"); sldns_buffer_free(c->buffer); @@ -6693,6 +6753,9 @@ comm_point_close(struct comm_point* c) *c->tcp_more_read_again = 0; if(c->tcp_more_write_again && *c->tcp_more_write_again) *c->tcp_more_write_again = 0; + if(c->tcp_more_read_again_timer && + comm_timer_is_set(c->tcp_more_read_again_timer)) + comm_timer_disable(c->tcp_more_read_again_timer); /* close fd after removing from event lists, or epoll.. is messed up */ if(c->fd != -1 && !c->do_not_close) { @@ -6732,6 +6795,7 @@ comm_point_delete(struct comm_point* c) free(c->tcp_handlers); } free(c->timeout); + comm_timer_delete(c->tcp_more_read_again_timer); if(c->type == comm_tcp || c->type == comm_local || c->type == comm_http) { sldns_buffer_free(c->buffer); #ifdef USE_DNSCRYPT diff --git a/util/netevent.h b/util/netevent.h index 7d64fac5b..7f53464b1 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -384,6 +384,9 @@ struct comm_point { * Or leave NULL if it is not used at all. */ int* tcp_more_write_again; + /** resume timer for tcp_more_read_again */ + struct comm_timer* tcp_more_read_again_timer; + /** if set, read/write completes: read/write state of tcp is toggled. buffer reset/bytecount reset. @@ -1133,6 +1136,12 @@ void doq_send_pkt(struct comm_point* c, struct doq_pkt_addr* paddr, /** doq timer callback function. */ void doq_timer_cb(void* arg); +/** tcp read again callback function. For tcp req info listen. */ +void tcp_read_again_cb(void* arg); + +/** tcp more read again callback function. For outside network. */ +void tcp_more_read_again_cb(void* arg); + /** * This routine is published for checks and tests, and is only used internally. * handle libevent callback for timer comm.