1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

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.
This commit is contained in:
Andy Green 2017-10-06 16:07:57 +08:00
parent 4f99ccd6a8
commit 9c2a7dd58b
9 changed files with 183 additions and 122 deletions

View file

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

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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
*/

View file

@ -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

View file

@ -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;

View file

@ -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)
{

View file

@ -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;