Skip to content

Commit

Permalink
Reduce number of syscalls when sending FastCGI records
Browse files Browse the repository at this point in the history
Before, for a GET request (no STDIN records):

writev(24, [{iov_base="\1\1\0\1\0\10\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\265\0\0",
       iov_len=24}, {iov_base="\21\7GATEWAY_INTERFACECGI/1.1\v\tREMO"...,
       iov_len=437}, {iov_base="\1\4\0\1\0\0\0\0", iov_len=8}], 3) = 469
sendto(24, "\1\5\0\1\0\0\0\0", 8, MSG_DONTWAIT|MSG_NOSIGNAL, NULL, 0) = 8

After, for the same request:

writev(24, [{iov_base="\1\1\0\1\0\10\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\265\0\0",
       iov_len=24}, {iov_base="\21\7GATEWAY_INTERFACECGI/1.1\v\tREMO"...,
       iov_len=437}, {iov_base="\1\4\0\1\0\0\0\0", iov_len=8},
       {iov_base="\1\5\0\1\0\0\0\0", iov_len=8}], 4) = 477

Gains are a bit better when sending STDIN records, as most of them will be
sent with a single writev() syscall.  We flush every 16 iovs to avoid
inlinefirst array reallocation and keep allocations in the stack as much
as we can.  (There is a possibility that the last STDIN record after multiple
STDIN records might trigger reallocation/copy, but the memory will be freed
anyway.  This can happen if build_stdin_records() managed to put 16 elements
in the iovec_array.)
  • Loading branch information
lpereira committed May 20, 2022
1 parent b53cd32 commit 98f102e
Showing 1 changed file with 99 additions and 49 deletions.
148 changes: 99 additions & 49 deletions src/lib/lwan-mod-fastcgi.c
Original file line number Diff line number Diff line change
Expand Up @@ -551,35 +551,59 @@ try_initiating_chunked_response(struct lwan_request *request)
return HTTP_OK;
}

static void send_stdin_records(struct lwan_request *request,
int fcgi_fd,
const struct lwan_value *body_data,
const struct record *end_stdin_block)
DEFINE_ARRAY_TYPE_INLINEFIRST(iovec_array, struct iovec)
DEFINE_ARRAY_TYPE_INLINEFIRST(record_array, struct record)

static bool build_stdin_records(struct lwan_request *request,
struct iovec_array *iovec_array,
struct record_array *record_array,
int fcgi_fd,
const struct lwan_value *body_data)
{
if (!body_data)
return true;

size_t to_send = body_data->len;
char *buffer = body_data->value;

while (to_send) {
struct record *record;
struct iovec *iovec;
size_t block_size = LWAN_MIN(0xffffull, to_send);
struct record stdin_header = {

record = record_array_append(record_array);
if (UNLIKELY(!record))
return false;
*record = (struct record){
.version = 1,
.type = FASTCGI_TYPE_STDIN,
.id = htons(1),
.len_content = htons((uint16_t)block_size),
};
struct iovec vec[] = {
{.iov_base = &stdin_header, .iov_len = sizeof(stdin_header)},
{.iov_base = buffer, .iov_len = block_size},
};

lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec));
iovec = iovec_array_append(iovec_array);
if (UNLIKELY(!iovec))
return false;
*iovec = (struct iovec){.iov_base = record, .iov_len = sizeof(*record)};

iovec = iovec_array_append(iovec_array);
if (UNLIKELY(!iovec))
return false;
*iovec = (struct iovec){.iov_base = buffer, .iov_len = block_size};

if (iovec_array_len(iovec_array) == LWAN_ARRAY_INCREMENT) {
lwan_request_async_writev(request, fcgi_fd,
iovec_array_get_array(iovec_array),
(int)iovec_array_len(iovec_array));
iovec_array_reset(iovec_array);
record_array_reset(record_array);
}

to_send -= block_size;
buffer += block_size;
}

lwan_request_async_write(request, fcgi_fd, end_stdin_block,
sizeof(*end_stdin_block));
return true;
}

static enum lwan_http_status send_request(struct private_data *pd,
Expand Down Expand Up @@ -607,48 +631,74 @@ static enum lwan_http_status send_request(struct private_data *pd,
return HTTP_TOO_LARGE;
}

struct request_header request_header = {
.begin_request = {.version = 1,
.type = FASTCGI_TYPE_BEGIN_REQUEST,
.id = htons(1),
.len_content = htons(
(uint16_t)sizeof(struct begin_request_body))},
.begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)},
.begin_params = {.version = 1,
.type = FASTCGI_TYPE_PARAMS,
.id = htons(1),
.len_content = htons((uint16_t)lwan_strbuf_get_length(
response->buffer))},
};
struct record end_params_block = {
.version = 1,
.type = FASTCGI_TYPE_PARAMS,
.id = htons(1),
};
struct record end_stdin_block = {
.version = 1,
.type = FASTCGI_TYPE_STDIN,
.id = htons(1),
struct iovec_array iovec_array;
struct record_array record_array;
struct iovec *iovec;

/* These arrays should never go beyond the inlinefirst threshold, so they
* shouldn't leak -- thus requiring no defer to reset them. */
record_array_init(&record_array);
iovec_array_init(&iovec_array);

iovec = iovec_array_append(&iovec_array);
if (UNLIKELY(!iovec))
return HTTP_INTERNAL_ERROR;
*iovec = (struct iovec){
.iov_base =
&(struct request_header){
.begin_request = {.version = 1,
.type = FASTCGI_TYPE_BEGIN_REQUEST,
.id = htons(1),
.len_content = htons(
(uint16_t)sizeof(struct begin_request_body))},
.begin_request_body = {.role = htons(FASTCGI_ROLE_RESPONDER)},
.begin_params = {.version = 1,
.type = FASTCGI_TYPE_PARAMS,
.id = htons(1),
.len_content =
htons((uint16_t)lwan_strbuf_get_length(
response->buffer))}},
.iov_len = sizeof(struct request_header),
};
struct iovec vec[] = {
{.iov_base = &request_header, .iov_len = sizeof(request_header)},
{.iov_base = lwan_strbuf_get_buffer(response->buffer),
.iov_len = lwan_strbuf_get_length(response->buffer)},
{.iov_base = &end_params_block, .iov_len = sizeof(end_params_block)},
{.iov_base = &end_stdin_block, .iov_len = sizeof(end_stdin_block)},

iovec = iovec_array_append(&iovec_array);
if (UNLIKELY(!iovec))
return HTTP_INTERNAL_ERROR;
*iovec =
(struct iovec){.iov_base = lwan_strbuf_get_buffer(response->buffer),
.iov_len = lwan_strbuf_get_length(response->buffer)};

iovec = iovec_array_append(&iovec_array);
if (UNLIKELY(!iovec))
return HTTP_INTERNAL_ERROR;
*iovec = (struct iovec){
.iov_base = &(struct record){.version = 1,
.type = FASTCGI_TYPE_PARAMS,
.id = htons(1)},
.iov_len = sizeof(struct record),
};

const struct lwan_value *body_data = lwan_request_get_request_body(request);
if (!body_data) {
lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec));
} else {
/* If we have body data, don't send the last element just yet: we need
* to send each stdin segment until we don't have anything else there
* anymore before sending one with len_content = 0. */
lwan_request_async_writev(request, fcgi_fd, vec, N_ELEMENTS(vec) - 1);
send_stdin_records(request, fcgi_fd, body_data, &end_stdin_block);
if (!build_stdin_records(request, &iovec_array, &record_array, fcgi_fd,
lwan_request_get_request_body(request))) {
return HTTP_INTERNAL_ERROR;
}

iovec = iovec_array_append(&iovec_array);
if (UNLIKELY(!iovec))
return HTTP_INTERNAL_ERROR;
*iovec = (struct iovec){
.iov_base = &(struct record){.version = 1,
.type = FASTCGI_TYPE_STDIN,
.id = htons(1)},
.iov_len = sizeof(struct record),
};

lwan_request_async_writev(request, fcgi_fd,
iovec_array_get_array(&iovec_array),
(int)iovec_array_len(&iovec_array));
iovec_array_reset(&iovec_array);
record_array_reset(&record_array);

lwan_strbuf_reset(response->buffer);

return HTTP_OK;
Expand Down

0 comments on commit 98f102e

Please sign in to comment.