litespeed-quic/src/liblsquic/lsquic_di_hash.c

689 lines
18 KiB
C
Raw Normal View History

2022-05-06 16:49:46 +00:00
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
2017-09-22 21:00:03 +00:00
/*
* lsquic_di_hash.c -- Copy incoming data into a hash
*
* While this implementation copies the data, its memory use is limited,
* which makes it a good choice when we have a lot of stream frames
* coming in.
*
* Another difference is that incoming STREAM frames are allowed to overlap.
2017-09-22 21:00:03 +00:00
*/
#include <assert.h>
#include <inttypes.h>
2017-09-22 21:00:03 +00:00
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include "lsquic.h"
#include "lsquic_int_types.h"
#include "lsquic_types.h"
#include "lsquic_conn_flow.h"
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
#include "lsquic_packet_common.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_packet_in.h"
#include "lsquic_rtt.h"
#include "lsquic_sfcw.h"
#include "lsquic_varint.h"
#include "lsquic_hq.h"
#include "lsquic_hash.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_stream.h"
#include "lsquic_mm.h"
#include "lsquic_malo.h"
#include "lsquic_conn.h"
#include "lsquic_conn_public.h"
#include "lsquic_data_in_if.h"
#define LSQUIC_LOGGER_MODULE LSQLM_DI
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(hdi->hdi_conn_pub->lconn)
2017-09-22 21:00:03 +00:00
#define LSQUIC_LOG_STREAM_ID hdi->hdi_stream_id
#include "lsquic_logger.h"
#define N_DB_SETS 57
#define DB_DATA_SIZE (0x1000 - sizeof(TAILQ_ENTRY(data_block)) - \
sizeof(uint64_t) - N_DB_SETS * sizeof(uint64_t))
struct data_block
{
TAILQ_ENTRY(data_block) db_next;
uint64_t db_off;
uint64_t db_set[N_DB_SETS]; /* bit for each valid byte */
unsigned char db_data[DB_DATA_SIZE];
};
typedef char db_set_covers_all_db_data[(N_DB_SETS * 64 >= DB_DATA_SIZE) ?1: - 1];
typedef char db_set_no_waste[(N_DB_SETS * 64 - 64 <= DB_DATA_SIZE)?1: - 1];
typedef char db_block_is_4K[(sizeof(struct data_block) == 0x1000) ?1:- 1];
2017-09-22 21:00:03 +00:00
TAILQ_HEAD(dblock_head, data_block);
2018-03-30 14:57:17 +00:00
static const struct data_in_iface *di_if_hash_ptr;
2017-09-22 21:00:03 +00:00
struct hash_data_in
{
struct data_in hdi_data_in;
struct lsquic_conn_public *hdi_conn_pub;
uint64_t hdi_fin_off;
struct dblock_head *hdi_buckets;
struct data_block *hdi_last_block;
struct data_frame hdi_data_frame;
lsquic_stream_id_t hdi_stream_id;
2017-09-22 21:00:03 +00:00
unsigned hdi_count;
unsigned hdi_nbits;
enum {
HDI_FIN = (1 << 0),
} hdi_flags;
};
#define HDI_PTR(data_in) (struct hash_data_in *) \
((unsigned char *) (data_in) - offsetof(struct hash_data_in, hdi_data_in))
#define N_BUCKETS(n_bits) (1U << (n_bits))
#define BUCKNO(n_bits, off) ((off / DB_DATA_SIZE) & (N_BUCKETS(n_bits) - 1))
static unsigned
my_log2 /* silly name to suppress compiler warning */ (unsigned sz)
{
#if __GNUC__
unsigned clz = __builtin_clz(sz);
return 32 - clz;
#else
unsigned clz;
size_t y;
clz = 32;
y = sz >> 16; if (y) { clz -= 16; sz = y; }
y = sz >> 8; if (y) { clz -= 8; sz = y; }
y = sz >> 4; if (y) { clz -= 4; sz = y; }
y = sz >> 2; if (y) { clz -= 2; sz = y; }
y = sz >> 1; if (y) return 32 - clz + 1;
return 32 - clz + sz;
#endif
}
2018-03-30 14:57:17 +00:00
struct data_in *
lsquic_data_in_hash_new (struct lsquic_conn_public *conn_pub,
lsquic_stream_id_t stream_id, uint64_t byteage)
2018-03-30 14:57:17 +00:00
{
struct hash_data_in *hdi;
unsigned n;
hdi = malloc(sizeof(*hdi));
if (!hdi)
return NULL;
hdi->hdi_data_in.di_if = di_if_hash_ptr;
hdi->hdi_data_in.di_flags = 0;
hdi->hdi_conn_pub = conn_pub;
hdi->hdi_stream_id = stream_id;
hdi->hdi_fin_off = 0;
hdi->hdi_flags = 0;
hdi->hdi_last_block = NULL;
if (byteage >= DB_DATA_SIZE /* __builtin_clz is undefined if
argument is 0 */)
hdi->hdi_nbits = my_log2(byteage / DB_DATA_SIZE) + 2;
else
hdi->hdi_nbits = 3;
hdi->hdi_count = 0;
hdi->hdi_buckets = malloc(sizeof(hdi->hdi_buckets[0]) *
N_BUCKETS(hdi->hdi_nbits));
if (!hdi->hdi_buckets)
{
free(hdi);
return NULL;
}
for (n = 0; n < N_BUCKETS(hdi->hdi_nbits); ++n)
TAILQ_INIT(&hdi->hdi_buckets[n]);
return &hdi->hdi_data_in;
}
2017-09-22 21:00:03 +00:00
static void
hash_di_destroy (struct data_in *data_in)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
struct data_block *block;
unsigned n;
for (n = 0; n < N_BUCKETS(hdi->hdi_nbits); ++n)
{
while ((block = TAILQ_FIRST(&hdi->hdi_buckets[n])))
{
TAILQ_REMOVE(&hdi->hdi_buckets[n], block, db_next);
free(block);
}
}
free(hdi->hdi_buckets);
free(hdi);
}
static int
hash_grow (struct hash_data_in *hdi)
{
struct dblock_head *new_buckets, *new[2];
struct data_block *block;
unsigned n, old_nbits;
int idx;
old_nbits = hdi->hdi_nbits;
LSQ_DEBUG("doubling number of buckets to %u", N_BUCKETS(old_nbits + 1));
new_buckets = malloc(sizeof(hdi->hdi_buckets[0])
* N_BUCKETS(old_nbits + 1));
if (!new_buckets)
{
LSQ_WARN("malloc failed: potential trouble ahead");
return -1;
}
for (n = 0; n < N_BUCKETS(old_nbits); ++n)
{
new[0] = &new_buckets[n];
new[1] = &new_buckets[n + N_BUCKETS(old_nbits)];
TAILQ_INIT(new[0]);
TAILQ_INIT(new[1]);
while ((block = TAILQ_FIRST(&hdi->hdi_buckets[n])))
{
TAILQ_REMOVE(&hdi->hdi_buckets[n], block, db_next);
idx = (BUCKNO(old_nbits + 1, block->db_off) >> old_nbits) & 1;
TAILQ_INSERT_TAIL(new[idx], block, db_next);
}
}
free(hdi->hdi_buckets);
hdi->hdi_nbits = old_nbits + 1;
hdi->hdi_buckets = new_buckets;
return 0;
}
static int
hash_insert (struct hash_data_in *hdi, struct data_block *block)
{
unsigned buckno;
if (hdi->hdi_count >= N_BUCKETS(hdi->hdi_nbits) / 2 && 0 != hash_grow(hdi))
return -1;
buckno = BUCKNO(hdi->hdi_nbits, block->db_off);
TAILQ_INSERT_TAIL(&hdi->hdi_buckets[buckno], block, db_next);
++hdi->hdi_count;
return 0;
}
static struct data_block *
hash_find (const struct hash_data_in *hdi, uint64_t off)
{
struct data_block *block;
unsigned buckno;
buckno = BUCKNO(hdi->hdi_nbits, off);
TAILQ_FOREACH(block, &hdi->hdi_buckets[buckno], db_next)
if (off == block->db_off)
return block;
return NULL;
}
static void
hash_remove (struct hash_data_in *hdi, struct data_block *block)
{
unsigned buckno;
buckno = BUCKNO(hdi->hdi_nbits, block->db_off);
TAILQ_REMOVE(&hdi->hdi_buckets[buckno], block, db_next);
--hdi->hdi_count;
}
static struct data_block *
new_block (struct hash_data_in *hdi, uint64_t off)
{
struct data_block *block;
assert(0 == off % DB_DATA_SIZE);
block = malloc(sizeof(*block));
if (!block)
return NULL;
block->db_off = off;
if (0 != hash_insert(hdi, block))
{
free(block);
return NULL;
}
memset(block->db_set, 0, sizeof(block->db_set));
return block;
}
static unsigned
block_write (struct data_block *block, unsigned block_off,
const unsigned char *data, unsigned data_sz)
{
const unsigned char *begin, *end;
unsigned set, bit, n_full_sets, n;
uint64_t mask;
2019-02-01 20:16:24 +00:00
assert(block_off < DB_DATA_SIZE);
2017-09-22 21:00:03 +00:00
if (data_sz > DB_DATA_SIZE - block_off)
data_sz = DB_DATA_SIZE - block_off;
begin = data;
end = begin + data_sz;
set = block_off >> 6;
bit = block_off & 0x3F;
2019-01-30 20:28:35 +00:00
assert(set < N_DB_SETS);
2017-09-22 21:00:03 +00:00
if (bit)
{
n = 64 - bit;
if (n > data_sz)
n = data_sz;
mask = ~((1ULL << bit ) - 1)
& ((1ULL << (bit + n - 1)) | ((1ULL << (bit + n - 1)) - 1));
block->db_set[ set ] |= mask;
memcpy(block->db_data + block_off, data, n);
data += n;
block_off += n;
++set;
}
n_full_sets = (end - data) >> 6;
if (n_full_sets)
{
memcpy(block->db_data + block_off, data, n_full_sets * 64);
data += n_full_sets * 64;
block_off += n_full_sets * 64;
memset(&block->db_set[ set ], 0xFF, n_full_sets * 8);
set += n_full_sets;
}
if (data < end)
{
assert(end - data < 64);
block->db_set[ set ] |= ((1ULL << (end - data)) - 1);
memcpy(block->db_data + block_off, data, end - data);
data = end;
}
assert(set <= N_DB_SETS);
return data - begin;
}
static int
has_bytes_after (const struct data_block *block, unsigned off)
{
unsigned bit, set;
int has;
set = off >> 6;
bit = off & 0x3F;
has = 0 != (block->db_set[ set ] >> bit);
++set;
for ( ; set < N_DB_SETS; ++set)
has += 0 != block->db_set[ set ];
return has > 0;
}
enum ins_frame
lsquic_data_in_hash_insert_data_frame (struct data_in *data_in,
2017-09-22 21:00:03 +00:00
const struct data_frame *data_frame, uint64_t read_offset)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
struct data_block *block;
uint64_t key, off, diff, fin_off;
const unsigned char *data;
unsigned size, nw;
if (data_frame->df_offset + data_frame->df_size < read_offset)
2018-05-04 18:00:34 +00:00
{
if (data_frame->df_fin)
return INS_FRAME_ERR;
else
return INS_FRAME_DUP;
}
2017-09-22 21:00:03 +00:00
if ((hdi->hdi_flags & HDI_FIN) &&
(
(data_frame->df_fin &&
data_frame->df_offset + data_frame->df_size != hdi->hdi_fin_off)
||
data_frame->df_offset + data_frame->df_size > hdi->hdi_fin_off
)
)
{
return INS_FRAME_ERR;
}
if (data_frame->df_offset < read_offset)
{
diff = read_offset - data_frame->df_offset;
assert(diff <= data_frame->df_size);
size = data_frame->df_size - diff;
off = data_frame->df_offset + diff;
data = data_frame->df_data + diff;
}
else
{
size = data_frame->df_size;
off = data_frame->df_offset;
data = data_frame->df_data;
}
key = off - (off % DB_DATA_SIZE);
do
{
block = hash_find(hdi, key);
if (!block)
{
block = new_block(hdi, key);
if (!block)
return INS_FRAME_ERR;
}
nw = block_write(block, off % DB_DATA_SIZE, data, size);
size -= nw;
off += nw;
data += nw;
key += DB_DATA_SIZE;
}
while (size > 0);
if (data_frame->df_fin)
{
fin_off = data_frame->df_offset + data_frame->df_size;
if (has_bytes_after(block, fin_off - block->db_off) ||
hash_find(hdi, key))
{
return INS_FRAME_ERR;
}
hdi->hdi_flags |= HDI_FIN;
hdi->hdi_fin_off = fin_off;
}
return INS_FRAME_OK;
}
static enum ins_frame
hash_di_insert_frame (struct data_in *data_in,
struct stream_frame *new_frame, uint64_t read_offset)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
const struct data_frame *const data_frame = &new_frame->data_frame;
enum ins_frame ins;
ins = lsquic_data_in_hash_insert_data_frame(data_in, data_frame,
read_offset);
2018-05-04 18:00:34 +00:00
assert(ins != INS_FRAME_OVERLAP);
2017-09-22 21:00:03 +00:00
lsquic_packet_in_put(hdi->hdi_conn_pub->mm, new_frame->packet_in);
if (ins != INS_FRAME_OK)
lsquic_malo_put(new_frame);
2017-09-22 21:00:03 +00:00
return ins;
}
#if __GNUC__
# define ctz __builtin_ctzll
#else
static unsigned
ctz (unsigned long long x)
{
unsigned n = 0;
if (0 == (x & ((1ULL << 32) - 1))) { n += 32; x >>= 32; }
if (0 == (x & ((1ULL << 16) - 1))) { n += 16; x >>= 16; }
if (0 == (x & ((1ULL << 8) - 1))) { n += 8; x >>= 8; }
if (0 == (x & ((1ULL << 4) - 1))) { n += 4; x >>= 4; }
if (0 == (x & ((1ULL << 2) - 1))) { n += 2; x >>= 2; }
if (0 == (x & ((1ULL << 1) - 1))) { n += 1; x >>= 1; }
return n;
}
#endif
static unsigned
n_avail_bytes (const struct data_block *block, unsigned set, unsigned bit)
{
unsigned count;
uint64_t part;
part = ~(block->db_set[ set ] >> bit);
if (part)
{
count = ctz(part);
if (count < 64 - bit)
return count;
}
else
count = 64;
++set;
for ( ; set < N_DB_SETS && ~0ULL == block->db_set[ set ]; ++set)
count += 64;
if (set < N_DB_SETS)
{
part = ~block->db_set[ set ];
if (part)
count += ctz(part);
else
count += 64;
}
return count;
}
/* Data block is readable if there is at least one readable byte at
* `read_offset' or there is FIN at that offset.
*/
static int
setup_data_frame (struct hash_data_in *hdi, const uint64_t read_offset,
struct data_block *block)
{
unsigned set, bit;
uint64_t offset;
offset = read_offset % DB_DATA_SIZE;
set = offset >> 6;
bit = offset & 0x3F;
if (block->db_set[ set ] & (1ULL << bit))
{
hdi->hdi_last_block = block;
hdi->hdi_data_frame.df_data = block->db_data;
hdi->hdi_data_frame.df_offset = block->db_off;
hdi->hdi_data_frame.df_read_off = offset;
hdi->hdi_data_frame.df_size = offset +
n_avail_bytes(block, set, bit);
hdi->hdi_data_frame.df_fin =
(hdi->hdi_flags & HDI_FIN) &&
hdi->hdi_data_frame.df_read_off +
hdi->hdi_data_frame.df_size == hdi->hdi_fin_off;
return 1;
}
else if ((hdi->hdi_flags & HDI_FIN) && read_offset == hdi->hdi_fin_off)
{
hdi->hdi_last_block = block;
hdi->hdi_data_frame.df_data = NULL;
hdi->hdi_data_frame.df_offset = block->db_off;
hdi->hdi_data_frame.df_read_off = offset;
hdi->hdi_data_frame.df_size = offset;
hdi->hdi_data_frame.df_fin = 1;
return 1;
}
else
return 0;
}
static struct data_frame *
hash_di_get_frame (struct data_in *data_in, uint64_t read_offset)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
struct data_block *block;
uint64_t key;
key = read_offset - (read_offset % DB_DATA_SIZE);
block = hash_find(hdi, key);
if (!block)
{
if ((hdi->hdi_flags & HDI_FIN) && read_offset == hdi->hdi_fin_off)
{
hdi->hdi_last_block = NULL;
hdi->hdi_data_frame.df_data = NULL;
hdi->hdi_data_frame.df_offset = read_offset -
read_offset % DB_DATA_SIZE;
hdi->hdi_data_frame.df_read_off = 0;
hdi->hdi_data_frame.df_size = 0;
hdi->hdi_data_frame.df_fin = 1;
return &hdi->hdi_data_frame;
}
else
return NULL;
}
if (setup_data_frame(hdi, read_offset, block))
return &hdi->hdi_data_frame;
else
return NULL;
}
static void
hash_di_frame_done (struct data_in *data_in, struct data_frame *data_frame)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
struct data_block *const block = hdi->hdi_last_block;
if (block)
{
if (data_frame->df_read_off == DB_DATA_SIZE ||
!has_bytes_after(block, data_frame->df_read_off))
{
hash_remove(hdi, block);
free(block);
if (0 == hdi->hdi_count && 0 == (hdi->hdi_flags & HDI_FIN))
{
LSQ_DEBUG("hash empty, want to switch");
hdi->hdi_data_in.di_flags |= DI_SWITCH_IMPL;
}
}
}
else
assert(data_frame->df_fin && data_frame->df_size == 0);
}
static int
hash_di_empty (struct data_in *data_in)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
return hdi->hdi_count == 0;
}
static struct data_in *
2017-09-22 21:00:03 +00:00
hash_di_switch_impl (struct data_in *data_in, uint64_t read_offset)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
struct data_in *new_data_in;
assert(hdi->hdi_count == 0);
new_data_in = lsquic_data_in_nocopy_new(hdi->hdi_conn_pub,
hdi->hdi_stream_id);
2017-09-22 21:00:03 +00:00
data_in->di_if->di_destroy(data_in);
return new_data_in;
}
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
static size_t
hash_di_mem_used (struct data_in *data_in)
{
struct hash_data_in *const hdi = HDI_PTR(data_in);
const struct data_block *block;
size_t size;
unsigned n;
size = sizeof(*data_in);
for (n = 0; n < N_BUCKETS(hdi->hdi_nbits); ++n)
TAILQ_FOREACH(block, &hdi->hdi_buckets[n], db_next)
size += sizeof(*block);
size += N_BUCKETS(hdi->hdi_nbits) * sizeof(hdi->hdi_buckets[0]);
return size;
}
static void
hash_di_dump_state (struct data_in *data_in)
{
const struct hash_data_in *const hdi = HDI_PTR(data_in);
const struct data_block *block;
unsigned n;
LSQ_DEBUG("hash state: flags: %X; fin off: %"PRIu64"; count: %u",
hdi->hdi_flags, hdi->hdi_fin_off, hdi->hdi_count);
for (n = 0; n < N_BUCKETS(hdi->hdi_nbits); ++n)
TAILQ_FOREACH(block, &hdi->hdi_buckets[n], db_next)
LSQ_DEBUG("block: off: %"PRIu64, block->db_off);
}
static uint64_t
hash_di_readable_bytes (struct data_in *data_in, uint64_t read_offset)
{
const struct data_frame *data_frame;
uint64_t starting_offset;
starting_offset = read_offset;
while (data_frame = hash_di_get_frame(data_in, read_offset),
data_frame && data_frame->df_size - data_frame->df_read_off)
read_offset += data_frame->df_size - data_frame->df_read_off;
return read_offset - starting_offset;
}
2017-09-22 21:00:03 +00:00
static const struct data_in_iface di_if_hash = {
.di_destroy = hash_di_destroy,
.di_dump_state = hash_di_dump_state,
2017-09-22 21:00:03 +00:00
.di_empty = hash_di_empty,
.di_frame_done = hash_di_frame_done,
.di_get_frame = hash_di_get_frame,
.di_insert_frame = hash_di_insert_frame,
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
.di_mem_used = hash_di_mem_used,
.di_own_on_ok = 0,
.di_readable_bytes
= hash_di_readable_bytes,
2017-09-22 21:00:03 +00:00
.di_switch_impl = hash_di_switch_impl,
};
2018-03-30 14:57:17 +00:00
static const struct data_in_iface *di_if_hash_ptr = &di_if_hash;