From 9c2a7dd58b6016ad82231ec42b0004b0cc0e1f65 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Fri, 6 Oct 2017 16:07:57 +0800 Subject: [PATCH] ah pool: change to dynamic linked list For some targets like ESP32, the ah pool is mainly sitting idle wasting memory. For HTTP/2, if the client sends a series of pipelined headers on different SIDs that exist simultaneously, there is no way to stall the headers to wait for an ah, because we must read the stream for stuff like WINDOW_UPDATE on the other streams. In both these cases having the ability to free unused ah completely and allocate more dynamically if there is memory is useful, so this patch makes the ah pool an initially-empty linked list that allocates on demand up to the "max pool size" limit from the context info. When nobody wants an ah, it is freed (if someone was waiting for it, it is directly reused). For ESP32 it means no large, permanent alloc when lws starts and dynamic alloc according to how many streams the client opens, which can be controlled by SETTINGS. --- lib/context.c | 27 ++----- lib/hpack.c | 4 +- lib/libwebsockets.c | 14 ++-- lib/parsers.c | 60 +++++++++++++-- lib/private-libwebsockets.h | 44 +++++++---- lib/service.c | 146 +++++++++++++++++++----------------- lib/ssl-http2.c | 6 +- lib/ssl-server.c | 2 +- lwsws/main.c | 2 +- 9 files changed, 183 insertions(+), 122 deletions(-) diff --git a/lib/context.c b/lib/context.c index f577e26c..39ecdeca 100644 --- a/lib/context.c +++ b/lib/context.c @@ -757,7 +757,7 @@ lws_create_context(struct lws_context_creation_info *info) #ifndef LWS_NO_DAEMONIZE int pid_daemon = get_daemonize_pid(); #endif - int n, m; + int n; #if defined(__ANDROID__) struct rlimit rt; #endif @@ -908,7 +908,8 @@ lws_create_context(struct lws_context_creation_info *info) * and header data pool */ for (n = 0; n < context->count_threads; n++) { - context->pt[n].serv_buf = lws_zalloc(context->pt_serv_buf_size, "pt_serv_buf"); + context->pt[n].serv_buf = lws_malloc(context->pt_serv_buf_size, + "pt_serv_buf"); if (!context->pt[n].serv_buf) { lwsl_err("OOM\n"); return NULL; @@ -918,19 +919,8 @@ lws_create_context(struct lws_context_creation_info *info) context->pt[n].context = context; #endif context->pt[n].tid = n; - context->pt[n].http_header_data = lws_malloc(context->max_http_header_data * - context->max_http_header_pool, "context ah hdr data"); - if (!context->pt[n].http_header_data) - goto bail; - - context->pt[n].ah_pool = lws_zalloc(sizeof(struct allocated_headers) * - context->max_http_header_pool, "context ah hdr pool"); - for (m = 0; m < context->max_http_header_pool; m++) - context->pt[n].ah_pool[m].data = - (char *)context->pt[n].http_header_data + - (m * context->max_http_header_data); - if (!context->pt[n].ah_pool) - goto bail; + context->pt[n].ah_list = NULL; + context->pt[n].ah_pool_length = 0; lws_pt_mutex_init(&context->pt[n]); } @@ -1457,10 +1447,9 @@ lws_context_destroy(struct lws_context *context) lws_libevent_destroyloop(context, n); lws_free_set_NULL(context->pt[n].serv_buf); - if (pt->ah_pool) - lws_free(pt->ah_pool); - if (pt->http_header_data) - lws_free(pt->http_header_data); + + while (pt->ah_list) + _lws_destroy_ah(pt, pt->ah_list); } lws_plat_context_early_destroy(context); diff --git a/lib/hpack.c b/lib/hpack.c index cdc089ed..a07d13e4 100644 --- a/lib/hpack.c +++ b/lib/hpack.c @@ -289,11 +289,11 @@ lws_hpack_add_dynamic_header(struct lws *wsi, int token, char *arg, int len) return 1; wsi->u.http2.hpack_dyn_table = dyn; - dyn->args = lws_malloc(1024); + dyn->args = lws_malloc(1024, "hpack"); if (!dyn->args) goto bail1; dyn->args_length = 1024; - dyn->entries = lws_malloc(sizeof(dyn->entries[0]) * 20); + dyn->entries = lws_malloc(sizeof(dyn->entries[0]) * 20, "hpack dyn entries"); if (!dyn->entries) goto bail2; dyn->num_entries = 20; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index d8a739c1..79ed3b3e 100755 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -66,7 +66,7 @@ void lws_free_wsi(struct lws *wsi) { struct lws_context_per_thread *pt; - int n; + struct allocated_headers *ah; if (!wsi) return; @@ -94,14 +94,16 @@ lws_free_wsi(struct lws *wsi) wsi->vhost->lserv_wsi = NULL; lws_pt_lock(pt); - for (n = 0; n < wsi->context->max_http_header_pool; n++) { - if (pt->ah_pool[n].in_use && - pt->ah_pool[n].wsi == wsi) { + ah = pt->ah_list; + while (ah) { + if (ah->in_use && ah->wsi == wsi) { lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi); - pt->ah_pool[n].in_use = 0; - pt->ah_pool[n].wsi = NULL; + ah->in_use = 0; + ah->wsi = NULL; pt->ah_count_in_use--; + break; } + ah = ah->next; } #if defined(LWS_WITH_PEER_LIMITS) diff --git a/lib/parsers.c b/lib/parsers.c index b99fb26b..99119e13 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -60,6 +60,51 @@ lextable_decode(int pos, char c) } } +static struct allocated_headers * +_lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) +{ + struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct"); + + if (!ah) + return NULL; + + ah->data = lws_malloc(data_size, "ah data"); + if (!ah->data) { + lws_free(ah); + + return NULL; + } + ah->next = pt->ah_list; + pt->ah_list = ah; + ah->data_length = data_size; + pt->ah_pool_length++; + + lwsl_info("%s: created ah %p (size %d): pool length %d\n", __func__, + ah, (int)data_size, pt->ah_pool_length); + + return ah; +} + +int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) +{ + lws_start_foreach_llp(struct allocated_headers **, a, pt->ah_list) { + if ((*a) == ah) { + *a = ah->next; + pt->ah_pool_length--; + lwsl_info("%s: freed ah %p : pool length %d\n", + __func__, ah, pt->ah_pool_length); + if (ah->data) + lws_free(ah->data); + lws_free(ah); + + return 0; + } + } lws_end_foreach_llp(a, next); + + return 1; +} + void _lws_header_table_reset(struct allocated_headers *ah) { @@ -214,16 +259,15 @@ lws_header_table_attach(struct lws *wsi, int autoservice) __lws_remove_from_ah_waiting_list(wsi); - for (n = 0; n < context->max_http_header_pool; n++) - if (!pt->ah_pool[n].in_use) - break; + wsi->u.hdr.ah = _lws_create_ah(pt, context->max_http_header_data); + if (!wsi->u.hdr.ah) { /* we could not create an ah */ + _lws_header_ensure_we_are_on_waiting_list(wsi); - /* if the count of in use said something free... */ - assert(n != context->max_http_header_pool); + goto bail; + } - wsi->u.hdr.ah = &pt->ah_pool[n]; wsi->u.hdr.ah->in_use = 1; - pt->ah_pool[n].wsi = wsi; /* mark our owner */ + wsi->u.hdr.ah->wsi = wsi; /* mark our owner */ pt->ah_count_in_use++; #if defined(LWS_WITH_PEER_LIMITS) @@ -431,7 +475,7 @@ bail: nobody_usable_waiting: lwsl_info("%s: nobody usable waiting\n", __func__); - ah->in_use = 0; + _lws_destroy_ah(pt, ah); pt->ah_count_in_use--; goto bail; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index e03a7d45..7d30078f 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -731,10 +731,17 @@ struct lws_fd_hashtable { * other APIs to get information out of it. */ +#if defined(LWS_WITH_ESP32) +typedef uint16_t ah_data_idx_t; +#else +typedef uint32_t ah_data_idx_t; +#endif + struct lws_fragments { - unsigned int offset; - unsigned short len; - unsigned char nfrag; /* which ah->frag[] continues this content, or 0 */ + ah_data_idx_t offset; + uint16_t len; + uint8_t nfrag; /* which ah->frag[] continues this content, or 0 */ + uint8_t flags; /* only http2 cares */ }; /* @@ -743,34 +750,39 @@ struct lws_fragments { */ struct allocated_headers { + struct allocated_headers *next; /* linked list */ struct lws *wsi; /* owner */ char *data; /* prepared by context init to point to dedicated storage */ + ah_data_idx_t data_length; /* * the randomly ordered fragments, indexed by frag_index and * lws_fragments->nfrag for continuation. */ - struct lws_fragments frags[WSI_TOKEN_COUNT * 2]; + struct lws_fragments frags[WSI_TOKEN_COUNT]; time_t assigned; /* * for each recognized token, frag_index says which frag[] his data * starts in (0 means the token did not appear) * the actual header data gets dumped as it comes in, into data[] */ - unsigned char frag_index[WSI_TOKEN_COUNT]; - unsigned char rx[2048]; + uint8_t frag_index[WSI_TOKEN_COUNT]; +#if defined(LWS_WITH_ESP32) + uint8_t rx[256]; +#else + uint8_t rx[2048]; +#endif - unsigned int rxpos; - unsigned int rxlen; - unsigned int pos; - - unsigned int http_response; + int16_t rxpos; + int16_t rxlen; + uint32_t pos; + uint32_t http_response; #ifndef LWS_NO_CLIENT char initial_handshake_hash_base64[30]; #endif - unsigned char in_use; - unsigned char nfrag; + uint8_t in_use; + uint8_t nfrag; }; /* @@ -796,7 +808,7 @@ struct lws_context_per_thread { struct lws_cgi *cgi_list; #endif void *http_header_data; - struct allocated_headers *ah_pool; + struct allocated_headers *ah_list; struct lws *ah_wait_list; int ah_wait_list_length; #ifdef LWS_OPENSSL_SUPPORT @@ -835,6 +847,7 @@ struct lws_context_per_thread { lws_sockfd_type dummy_pipe_fds[2]; #endif unsigned int fds_count; + uint32_t ah_pool_length; short ah_count_in_use; unsigned char tid; @@ -1901,6 +1914,9 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); LWS_EXTERN struct lws * lws_client_connect_via_info2(struct lws *wsi); +LWS_EXTERN int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah); + /* * EXTENSIONS */ diff --git a/lib/service.c b/lib/service.c index cf514603..23d51bbf 100644 --- a/lib/service.c +++ b/lib/service.c @@ -73,7 +73,7 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) int write_type = LWS_WRITE_PONG; struct lws_tokens eff_buf; #ifdef LWS_WITH_HTTP2 - struct lws *wsi2, *wsi2a; + struct lws *wsi2; #endif int ret, m, n; @@ -530,7 +530,7 @@ LWS_VISIBLE LWS_EXTERN int lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; - int n; + struct allocated_headers *ah; /* Figure out if we really want to wait in poll() * We only need to wait if really nothing already to do and we have @@ -550,15 +550,16 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) #endif /* 3) if any ah has pending rx, do not wait in poll */ - for (n = 0; n < context->max_http_header_pool; n++) - if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen) { - /* any ah with pending rx must be attached to someone */ - if (!pt->ah_pool[n].wsi) { - lwsl_err("%s: ah with no wsi\n", __func__); + ah = pt->ah_list; + while (ah) { + if (ah->rxpos != ah->rxlen) { + if (!ah->wsi) { assert(0); } return 0; } + ah = ah->next; + } return timeout_ms; } @@ -573,12 +574,12 @@ int lws_service_flag_pending(struct lws_context *context, int tsi) { struct lws_context_per_thread *pt = &context->pt[tsi]; + struct allocated_headers *ah; #ifdef LWS_OPENSSL_SUPPORT struct lws *wsi_next; #endif struct lws *wsi; int forced = 0; - int n; /* POLLIN faking */ @@ -629,16 +630,20 @@ lws_service_flag_pending(struct lws_context *context, int tsi) * fake their POLLIN status so they will be able to drain the * rx buffered in the ah */ - for (n = 0; n < context->max_http_header_pool; n++) - if (pt->ah_pool[n].rxpos != pt->ah_pool[n].rxlen && - !pt->ah_pool[n].wsi->hdr_parsing_completed) { - pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].revents |= - pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].events & + ah = pt->ah_list; + while (ah) { + if (ah->rxpos != ah->rxlen && !ah->wsi->hdr_parsing_completed) { + pt->fds[ah->wsi->position_in_fds_table].revents |= + pt->fds[ah->wsi->position_in_fds_table].events & LWS_POLLIN; - if (pt->fds[pt->ah_pool[n].wsi->position_in_fds_table].revents & - LWS_POLLIN) + if (pt->fds[ah->wsi->position_in_fds_table].revents & + LWS_POLLIN) { forced = 1; + break; + } } + ah = ah->next; + } return forced; } @@ -809,6 +814,7 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t { struct lws_context_per_thread *pt = &context->pt[tsi]; lws_sockfd_type our_fd = 0, tmp_fd; + struct allocated_headers *ah; struct lws_tokens eff_buf; unsigned int pending = 0; struct lws *wsi, *wsi1; @@ -888,62 +894,66 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t * timeout status */ - for (n = 0; n < context->max_http_header_pool; n++) - if (pt->ah_pool[n].in_use && pt->ah_pool[n].wsi && - pt->ah_pool[n].assigned && - now - pt->ah_pool[n].assigned > 60) { - int len; - char buf[256]; - const unsigned char *c; + ah = pt->ah_list; + while (ah) { + int len; + char buf[256]; + const unsigned char *c; - /* - * a single ah session somehow got held for - * an unreasonable amount of time. - * - * Dump info on the connection... - */ - - wsi = pt->ah_pool[n].wsi; - buf[0] = '\0'; - lws_get_peer_simple(wsi, buf, sizeof(buf)); - lwsl_notice("ah excessive hold: wsi %p\n" - " peer address: %s\n" - " ah rxpos %u, rxlen %u, pos %u\n", - wsi, buf, pt->ah_pool[n].rxpos, - pt->ah_pool[n].rxlen, - pt->ah_pool[n].pos); - buf[0] = '\0'; - m = 0; - do { - c = lws_token_to_string(m); - if (!c) - break; - - len = lws_hdr_total_length(wsi, m); - if (!len || len > sizeof(buf) - 1) { - m++; - continue; - } - - if (lws_hdr_copy(wsi, buf, - sizeof buf, m) > 0) { - buf[sizeof(buf) - 1] = '\0'; - - lwsl_notice(" %s = %s\n", - (const char *)c, buf); - } - m++; - } while (1); - - /* ... and then drop the connection */ - - if (wsi->desc.sockfd == our_fd) - /* it was the guy we came to service! */ - timed_out = 1; - - lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + if (!ah->in_use || !ah->wsi || !ah->assigned || + now - ah->assigned < 60) { + ah = ah->next; + continue; } + /* + * a single ah session somehow got held for + * an unreasonable amount of time. + * + * Dump info on the connection... + */ + wsi = ah->wsi; + buf[0] = '\0'; + lws_get_peer_simple(wsi, buf, sizeof(buf)); + lwsl_notice("ah excessive hold: wsi %p\n" + " peer address: %s\n" + " ah rxpos %u, rxlen %u, pos %u\n", + wsi, buf, ah->rxpos, ah->rxlen, + ah->pos); + buf[0] = '\0'; + m = 0; + do { + c = lws_token_to_string(m); + if (!c) + break; + + len = lws_hdr_total_length(wsi, m); + if (!len || len > sizeof(buf) - 1) { + m++; + continue; + } + + if (lws_hdr_copy(wsi, buf, + sizeof buf, m) > 0) { + buf[sizeof(buf) - 1] = '\0'; + + lwsl_notice(" %s = %s\n", + (const char *)c, buf); + } + m++; + } while (1); + + /* ... and then drop the connection */ + + if (wsi->desc.sockfd == our_fd) + /* it was the guy we came to service! */ + timed_out = 1; + + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + ah = ah->next; + } + #ifdef LWS_WITH_CGI /* * Phase 3: handle cgi timeouts diff --git a/lib/ssl-http2.c b/lib/ssl-http2.c index 428e7887..9c25646f 100644 --- a/lib/ssl-http2.c +++ b/lib/ssl-http2.c @@ -52,7 +52,7 @@ #ifndef LWS_NO_SERVER #ifdef LWS_OPENSSL_SUPPORT -#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) struct alpn_ctx { unsigned char *data; @@ -89,7 +89,7 @@ alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, LWS_VISIBLE void lws_context_init_http2_ssl(struct lws_vhost *vhost) { -#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) static struct alpn_ctx protos = { (unsigned char *)"\x02h2" "\x08http/1.1", 6 + 9 }; @@ -107,7 +107,7 @@ lws_context_init_http2_ssl(struct lws_vhost *vhost) void lws_http2_configure_if_upgraded(struct lws *wsi) { -#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10002000L) struct allocated_headers *ah; const char *method = "alpn"; const unsigned char *name; diff --git a/lib/ssl-server.c b/lib/ssl-server.c index e352f84c..a7590bb0 100644 --- a/lib/ssl-server.c +++ b/lib/ssl-server.c @@ -137,7 +137,7 @@ lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info, return 0; } -#if defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT) +#if !defined(LWS_WITH_MBEDTLS) && defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT) static int lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) { diff --git a/lwsws/main.c b/lwsws/main.c index a68ea1af..de800970 100644 --- a/lwsws/main.c +++ b/lwsws/main.c @@ -119,7 +119,7 @@ context_creation(void) memset(&info, 0, sizeof(info)); info.external_baggage_free_on_destroy = config_strings; - info.max_http_header_pool = 16; + info.max_http_header_pool = 256; info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS | LWS_SERVER_OPTION_LIBUV;