Release 2.19.9

- [FEATURE] Add lsquic_stream_pwritev().  This function allows one to
  reduce the number of system calls required to read a file from disk
  by using lsquic_stream_pwritev() together with preadv(2).
- [BUGFIX] When stream is reset, it is writeable -- let user collect
  the error.
- [BUGFIX] Calculate correct conn flow control if reading ends early.
- [BUGFIX] Remove stream from read and write queues on internal
  shutdown.  This is a regression introduced in 2.19.7.
- [BUGFIX] Swapped arguments in IETF RESET_FRAME generation.
- Turn off mini conn history when compiling with Visual Studio; this
  allows the project to compile on Windows again.
- http_client: Add -3 flag to stop reading from streams early; code
  cleanup.
- Don't use -Werror.
This commit is contained in:
Dmitri Tikhonov 2020-09-08 11:43:03 -04:00
parent 49f1f4f620
commit 2f2f436324
25 changed files with 1545 additions and 85 deletions

View file

@ -76,6 +76,11 @@ static int g_header_bypass;
static int s_discard_response;
/* If set to a non-zero value, abandon reading from stream early: read at
* most `s_abandon_early' bytes and then close the stream.
*/
static long s_abandon_early;
struct sample_stats
{
unsigned n;
@ -164,8 +169,6 @@ struct path_elem {
};
struct http_client_ctx {
TAILQ_HEAD(, lsquic_conn_ctx)
conn_ctxs;
const char *hostname;
const char *method;
const char *payload;
@ -294,7 +297,6 @@ http_client_on_new_conn (void *stream_if_ctx, lsquic_conn_t *conn)
conn_h->ch_n_reqs = MIN(client_ctx->hcc_total_n_reqs,
client_ctx->hcc_reqs_per_conn);
client_ctx->hcc_total_n_reqs -= conn_h->ch_n_reqs;
TAILQ_INSERT_TAIL(&client_ctx->conn_ctxs, conn_h, next_ch);
++conn_h->client_ctx->hcc_n_open_conns;
if (!TAILQ_EMPTY(&client_ctx->hcc_path_elems))
create_streams(client_ctx, conn_h);
@ -346,7 +348,6 @@ http_client_on_conn_closed (lsquic_conn_t *conn)
if (!(conn_h->client_ctx->hcc_flags & HCC_SEEN_FIN))
abort();
}
TAILQ_REMOVE(&conn_h->client_ctx->conn_ctxs, conn_h, next_ch);
--conn_h->client_ctx->hcc_n_open_conns;
cacos = calloc(1, sizeof(*cacos));
@ -474,9 +475,16 @@ struct lsquic_stream_ctx {
enum {
HEADERS_SENT = (1 << 0),
PROCESSED_HEADERS = 1 << 1,
ABANDON = 1 << 2, /* Abandon reading from stream after sh_stop bytes
* have been read.
*/
} sh_flags;
lsquic_time_t sh_created;
lsquic_time_t sh_ttfb;
size_t sh_stop; /* Stop after reading this many bytes if ABANDON is set */
size_t sh_nread; /* Number of bytes read from stream using one of
* lsquic_stream_read* functions.
*/
unsigned count;
struct lsquic_reader reader;
};
@ -524,6 +532,11 @@ http_client_on_new_stream (void *stream_if_ctx, lsquic_stream_t *stream)
lsquic_stream_wantwrite(stream, 1);
if (randomly_reprioritize_streams)
lsquic_stream_set_priority(stream, 1 + (random() & 0xFF));
if (s_abandon_early)
{
st_h->sh_stop = random() % (s_abandon_early + 1);
st_h->sh_flags |= ABANDON;
}
return st_h;
}
@ -634,6 +647,14 @@ http_client_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
static size_t
discard (void *ctx, const unsigned char *buf, size_t sz, int fin)
{
lsquic_stream_ctx_t *st_h = ctx;
if (st_h->sh_flags & ABANDON)
{
if (sz > st_h->sh_stop - st_h->sh_nread)
sz = st_h->sh_stop - st_h->sh_nread;
}
return sz;
}
@ -671,10 +692,14 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
st_h->sh_flags |= PROCESSED_HEADERS;
}
else if (nread = (s_discard_response
? lsquic_stream_readf(stream, discard, NULL)
: lsquic_stream_read(stream, buf, sizeof(buf))),
? lsquic_stream_readf(stream, discard, st_h)
: lsquic_stream_read(stream, buf,
st_h->sh_flags & ABANDON
? MIN(sizeof(buf), st_h->sh_nread - st_h->sh_stop)
: sizeof(buf))),
nread > 0)
{
st_h->sh_nread += (size_t) nread;
s_stat_downloaded_bytes += nread;
/* test stream_reset after some number of read bytes */
if (client_ctx->hcc_reset_after_nbytes &&
@ -713,6 +738,13 @@ http_client_on_read (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
LSQ_DEBUG("changed stream %"PRIu64" priority from %u to %u",
lsquic_stream_id(stream), old_prio, new_prio);
}
if ((st_h->sh_flags & ABANDON) && st_h->sh_nread >= st_h->sh_stop)
{
LSQ_DEBUG("closing stream early having read %zd bytes",
st_h->sh_nread);
lsquic_stream_close(stream);
break;
}
}
else if (0 == nread)
{
@ -756,11 +788,7 @@ http_client_on_close (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
LSQ_INFO("%s called", __func__);
struct http_client_ctx *const client_ctx = st_h->client_ctx;
lsquic_conn_t *conn = lsquic_stream_conn(stream);
lsquic_conn_ctx_t *conn_h;
TAILQ_FOREACH(conn_h, &client_ctx->conn_ctxs, next_ch)
if (conn_h->conn == conn)
break;
assert(conn_h);
lsquic_conn_ctx_t *const conn_h = lsquic_conn_get_ctx(conn);
--conn_h->ch_n_reqs;
--conn_h->ch_n_cc_streams;
if (0 == conn_h->ch_n_reqs)
@ -834,6 +862,8 @@ usage (const char *prog)
" -q FILE QIF mode: issue requests from the QIF file and validate\n"
" server responses.\n"
" -e TOKEN Hexadecimal string representing resume token.\n"
" -3 MAX Close stream after reading at most MAX bytes. The actual\n"
" number of bytes read is randominzed.\n"
, prog);
}
@ -1406,7 +1436,6 @@ main (int argc, char **argv)
TAILQ_INIT(&sports);
memset(&client_ctx, 0, sizeof(client_ctx));
TAILQ_INIT(&client_ctx.hcc_path_elems);
TAILQ_INIT(&client_ctx.conn_ctxs);
client_ctx.method = "GET";
client_ctx.hcc_concurrency = 1;
client_ctx.hcc_cc_reqs_per_conn = 1;
@ -1420,6 +1449,7 @@ main (int argc, char **argv)
while (-1 != (opt = getopt(argc, argv, PROG_OPTS
"46Br:R:IKu:EP:M:n:w:H:p:0:q:e:hatT:b:d:"
"3:" /* 3 is 133+ for "e" ("e" for "early") */
#ifndef WIN32
"C:"
#endif
@ -1536,6 +1566,9 @@ main (int argc, char **argv)
http_client_if.on_sess_resume_info = http_client_on_sess_resume_info;
client_ctx.hcc_sess_resume_file_name = optarg;
break;
case '3':
s_abandon_early = strtol(optarg, NULL, 10);
break;
default:
if (0 != prog_set_opt(&prog, opt, optarg))
exit(1);

View file

@ -278,6 +278,13 @@ static const size_t IDLE_SIZE = sizeof(on_being_idle) - 1;
*/
static int s_immediate_write;
/* Use preadv(2) in conjuction with lsquic_stream_pwritev() to reduce
* number of system calls required to read from disk. The actual value
* specifies maximum write size. A negative value indicates always to use
* the remaining file size.
*/
static ssize_t s_pwritev;
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define V(v) (v), strlen(v)
@ -445,6 +452,7 @@ struct lsquic_stream_ctx {
SH_HEADERS_READ = (1 << 2),
} flags;
struct lsquic_reader reader;
int file_fd; /* Used by pwritev */
/* Fields below are used by interop callbacks: */
enum interop_handler {
@ -469,6 +477,7 @@ struct lsquic_stream_ctx {
} interop_u;
struct event *resume_resp;
size_t written;
size_t file_size; /* Used by pwritev */
};
@ -556,18 +565,35 @@ resume_response (evutil_socket_t fd, short what, void *arg)
}
static size_t
bytes_left (lsquic_stream_ctx_t *st_h)
{
if (s_pwritev)
return st_h->file_size - st_h->written;
else
return test_reader_size(st_h->reader.lsqr_ctx);
}
static ssize_t
my_preadv (void *user_data, const struct iovec *iov, int iovcnt)
{
lsquic_stream_ctx_t *const st_h = user_data;
return preadv(st_h->file_fd, iov, iovcnt, st_h->written);
}
static void
http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
if (st_h->flags & SH_HEADERS_SENT)
{
ssize_t nw;
if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
if (bytes_left(st_h) > 0)
{
if (st_h->server_ctx->delay_resp_sec
&& !(st_h->flags & SH_DELAYED)
&& st_h->written > 10000000
&& test_reader_size(st_h->reader.lsqr_ctx) > 0)
&& st_h->written > 10000000)
{
struct timeval delay = {
.tv_sec = st_h->server_ctx->delay_resp_sec, };
@ -585,7 +611,20 @@ http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
else
LSQ_ERROR("cannot allocate event");
}
nw = lsquic_stream_writef(stream, &st_h->reader);
if (s_pwritev)
{
size_t to_write = bytes_left(st_h);
if (s_pwritev > 0 && (size_t) s_pwritev < to_write)
to_write = s_pwritev;
nw = lsquic_stream_pwritev(stream, my_preadv, st_h, to_write);
if (nw == 0)
goto use_reader;
}
else
{
use_reader:
nw = lsquic_stream_writef(stream, &st_h->reader);
}
if (nw < 0)
{
struct lsquic_conn *conn = lsquic_stream_conn(stream);
@ -601,7 +640,7 @@ http_server_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
exit(1);
}
}
if (test_reader_size(st_h->reader.lsqr_ctx) > 0)
if (bytes_left(st_h) > 0)
{
st_h->written += (size_t) nw;
lsquic_stream_wantwrite(stream, 1);
@ -702,11 +741,32 @@ parse_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
static void
process_request (struct lsquic_stream *stream, lsquic_stream_ctx_t *st_h)
{
st_h->reader.lsqr_read = test_reader_read;
st_h->reader.lsqr_size = test_reader_size;
st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path);
if (!st_h->reader.lsqr_ctx)
exit(1);
struct stat st;
if (s_pwritev)
{
st_h->file_fd = open(st_h->req_path, O_RDONLY);
if (st_h->file_fd < 0)
{
LSQ_ERROR("cannot open %s for reading: %s", st_h->req_path,
strerror(errno));
exit(1);
}
if (fstat(st_h->file_fd, &st) < 0)
{
LSQ_ERROR("fstat: %s", strerror(errno));
exit(1);
}
st_h->file_size = st.st_size;
}
else
{
st_h->reader.lsqr_read = test_reader_read;
st_h->reader.lsqr_size = test_reader_size;
st_h->reader.lsqr_ctx = create_lsquic_reader_ctx(st_h->req_path);
if (!st_h->reader.lsqr_ctx)
exit(1);
}
if (s_immediate_write)
{
@ -1369,6 +1429,24 @@ new_req (enum method method, const char *path, const char *authority)
}
static ssize_t
my_interop_preadv (void *user_data, const struct iovec *iov, int iovcnt)
{
struct gen_file_ctx *const gfc = user_data;
size_t nread, nr;
int i;
nread = 0;
for (i = 0; i < iovcnt; ++i)
{
nr = idle_read(gfc, iov[i].iov_base, iov[i].iov_len);
nread += nr;
}
return (ssize_t) nread;
}
static void
idle_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
{
@ -1379,16 +1457,25 @@ idle_on_write (lsquic_stream_t *stream, lsquic_stream_ctx_t *st_h)
struct req *req;
ssize_t nw;
struct header_buf hbuf;
struct lsquic_reader reader;
if (st_h->flags & SH_HEADERS_SENT)
{
struct lsquic_reader reader =
if (s_pwritev)
{
.lsqr_read = idle_read,
.lsqr_size = idle_size,
.lsqr_ctx = gfc,
};
nw = lsquic_stream_writef(stream, &reader);
nw = lsquic_stream_pwritev(stream, my_interop_preadv, gfc,
gfc->remain);
if (nw == 0)
goto with_reader;
}
else
{
with_reader:
reader.lsqr_read = idle_read,
reader.lsqr_size = idle_size,
reader.lsqr_ctx = gfc,
nw = lsquic_stream_writef(stream, &reader);
}
if (nw < 0)
{
LSQ_ERROR("error writing idle thoughts: %s", strerror(errno));
@ -1507,6 +1594,10 @@ usage (const char *prog)
" -p FILE Push request with this path\n"
" -w SIZE Write immediately (LSWS mode). Argument specifies maximum\n"
" size of the immediate write.\n"
" -P SIZE Use preadv(2) to read from disk and lsquic_stream_pwritev() to\n"
" write to stream. Positive SIZE indicate maximum value per\n"
" write; negative means always use remaining file size.\n"
" Incompatible with -w.\n"
" -y DELAY Delay response for this many seconds -- use for debugging\n"
, prog);
}
@ -1680,7 +1771,7 @@ main (int argc, char **argv)
prog_init(&prog, LSENG_SERVER|LSENG_HTTP, &server_ctx.sports,
&http_server_if, &server_ctx);
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:h")))
while (-1 != (opt = getopt(argc, argv, PROG_OPTS "y:Y:n:p:r:w:P:h")))
{
switch (opt) {
case 'n':
@ -1707,6 +1798,9 @@ main (int argc, char **argv)
case 'w':
s_immediate_write = atoi(optarg);
break;
case 'P':
s_pwritev = strtoull(optarg, NULL, 10);
break;
case 'y':
server_ctx.delay_resp_sec = atoi(optarg);
break;
@ -1734,6 +1828,12 @@ main (int argc, char **argv)
#endif
}
if (s_immediate_write && s_pwritev)
{
LSQ_ERROR("-w and -P are incompatible options");
exit(EXIT_FAILURE);
}
if (0 != prog_prep(&prog))
{
LSQ_ERROR("could not prep");