Files
hio/t/t-008-wq.inc

292 lines
9.4 KiB
C++

/* write-queue byte accounting and the soft cap.
* included from t-008.c - see the note at the include site. */
static void test_wq_size_accounting (void)
{
tdev_t* dev;
int peerfd;
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 0; /* refuse everything, so it all queues */
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 6); return; }
OK (hio_dev_getwqsize((hio_dev_t*)dev) == 0, "a fresh device reports an empty write queue");
hio_dev_write ((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x81, HIO_NULL);
OK (hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)PATLEN,
"a queued request counts its bytes, not itself");
hio_dev_write ((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x82, HIO_NULL);
OK (hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)(PATLEN * 2),
"a second request adds to the byte count");
/* let exactly one slice through so a partially drained request is visible */
g_wr_chunk = 30;
g_wr_calls_left = 1;
pump ();
OK (hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)(PATLEN * 2 - 30),
"bytes handed to the transport stop counting immediately, mid-request");
g_wr_chunk = 0;
g_wr_calls_left = 100;
pump ();
OK (hio_dev_getwqsize((hio_dev_t*)dev) == 0, "a fully drained queue is back to zero");
OK (g_cw_n == 2, "both requests completed");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
static void test_wq_size_released_on_kill (void)
{
/* the count lives on the device, so a kill only has to not corrupt it on
* the way out - but a request dropped with bytes still queued is the one
* path where the decrement is not paired with a drain. */
tdev_t* dev;
int peerfd;
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 0;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 2); return; }
hio_dev_write ((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x83, HIO_NULL);
OK (hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)PATLEN, "the request is queued and counted");
hio_dev_kill ((hio_dev_t*)dev);
OK (g_cw_n == 0, "the dropped request fires no completion");
close (peerfd);
}
static void test_wq_limit (void)
{
tdev_t* dev;
int peerfd;
int rc;
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 0; /* refuse everything, so it all queues */
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 8); return; }
OK (hio_dev_getwqlimit((hio_dev_t*)dev) == 0, "a device is uncapped by default");
hio_dev_setwqlimit ((hio_dev_t*)dev, PATLEN + PATLEN / 2);
OK (hio_dev_getwqlimit((hio_dev_t*)dev) == (hio_oow_t)(PATLEN + PATLEN / 2), "the cap reads back");
rc = hio_dev_write((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x84, HIO_NULL);
OK (rc == 0, "a write below the cap is accepted");
/* now at PATLEN of a PATLEN*1.5 cap - still under, so this one goes on
* whole even though it takes the queue past the cap. that is what makes
* the cap soft, and it is what keeps a stream from being cut in half. */
rc = hio_dev_write((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x85, HIO_NULL);
OK (rc == 0, "a request that starts under the cap is accepted whole");
OK (hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)(PATLEN * 2),
"which can leave the queue above the cap");
rc = hio_dev_write((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x86, HIO_NULL);
OK (rc <= -1 && hio_geterrnum(g_hio) == HIO_EBUFFULL,
"a write attempted while at or above the cap fails with HIO_EBUFFULL");
OK (g_cw_n == 0, "the refused request fires no completion");
/* drain and confirm the device accepts writes again */
g_wr_calls_left = 100;
pump ();
rc = hio_dev_write((hio_dev_t*)dev, g_pattern, PATLEN, (void*)0x87, HIO_NULL);
OK (rc == 0, "the device accepts writes again once the queue drains");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
static void test_wq_limit_zero_is_unlimited (void)
{
tdev_t* dev;
int peerfd;
int i, all_ok = 1;
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 0;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 1); return; }
/* the default. every release before the cap existed behaved this way and
* still has to. */
for (i = 0; i < 64; i++)
{
if (hio_dev_write((hio_dev_t*)dev, g_pattern, PATLEN, HIO_NULL, HIO_NULL) <= -1) all_ok = 0;
}
OK (all_ok && hio_dev_getwqsize((hio_dev_t*)dev) == (hio_oow_t)(PATLEN * 64),
"an uncapped device queues without limit");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
/* ------------------------------------------------------------------ */
/* completion firing inside the greedy read loop.
*
* when on_read asks for another read, the loop fires the completions queued
* so far before calling on_read again - otherwise a write started from inside
* on_read would be reported after a later on_read, which is the reordering
* the comment in handle_event() warns about. the firing is per device, so an
* unrelated connection's callbacks are not dragged into this device's pass. */
static void test_completion_fires_within_read_loop (void)
{
tdev_t* dev;
int peerfd;
hio_uint8_t chunk[30];
obs_reset ();
g_wr_chunk = 0;
g_wr_calls_left = 100; /* every write completes at once */
g_rd_chunk = 10; /* so 30 octets take three read iterations */
g_write_on_read = 1;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 3); return; }
HIO_MEMSET (chunk, 'x', HIO_SIZEOF(chunk));
if (write(peerfd, chunk, HIO_SIZEOF(chunk)) != (ssize_t)HIO_SIZEOF(chunk))
{
skip ("peer write failed", 3);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
return;
}
pump ();
/* iteration 1 reads and writes; iteration 2 fires that write's completion
* before reading again. hence R, then W and R alternating. */
OK (g_seq_n >= 3, "the greedy read loop ran more than once");
OK (g_seq[0] == 'R', "no completion is fired before the first read");
OK (strncmp(g_seq, "RWRWR", 5) == 0,
"each iteration's write completion is reported before the next on_read");
g_write_on_read = 0;
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}
/* ------------------------------------------------------------------ */
/* the shared read buffer's configurable size.
*
* every device reads through one buffer owned by the loop. its size decides
* how much a single read can take, so it is the ceiling on read size for a
* bulk transfer - and it is memory held per loop, which matters when a
* process runs one loop per thread. */
static void test_read_buffer_size_option (void)
{
hio_oow_t capa = 0, want;
int rc;
obs_reset ();
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == HIO_DFL_READ_BUFFER_SIZE,
"the read buffer starts at HIO_DFL_READ_BUFFER_SIZE");
want = HIO_MIN_READ_BUFFER_SIZE - 1;
rc = hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want);
OK (rc <= -1 && hio_geterrnum(g_hio) == HIO_EINVAL,
"a size below HIO_MIN_READ_BUFFER_SIZE is refused with HIO_EINVAL");
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == HIO_DFL_READ_BUFFER_SIZE,
"a refused set leaves the existing buffer alone");
want = HIO_DFL_READ_BUFFER_SIZE * 4;
OK (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) == 0 &&
hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &capa) == 0 && capa == want,
"a larger size is accepted and reads back");
want = HIO_DFL_READ_BUFFER_SIZE;
OK (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) == 0, "and it can be set back");
}
static void test_read_buffer_size_is_honoured (void)
{
/* the size has to reach the read path, not merely be stored. the stub
* device records the length the core offers it. */
tdev_t* dev;
int peerfd;
hio_oow_t want, restore = HIO_DFL_READ_BUFFER_SIZE;
hio_uint8_t buf[4096];
obs_reset ();
g_wr_calls_left = 100;
want = HIO_MIN_READ_BUFFER_SIZE; /* the smallest the core allows */
if (hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &want) <= -1) { skip ("cannot set the buffer size", 2); return; }
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 2); hio_setoption(g_hio, HIO_READ_BUFFER_SIZE, &restore); return; }
HIO_MEMSET (buf, 'z', HIO_SIZEOF(buf));
if (write(peerfd, buf, HIO_SIZEOF(buf)) != (ssize_t)HIO_SIZEOF(buf))
{
skip ("peer write failed", 2);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &restore);
return;
}
pump ();
OK (g_rd_calls > 0, "the device was read from");
OK (g_rd_offered == (hio_iolen_t)HIO_MIN_READ_BUFFER_SIZE,
"the core offers the read method exactly the configured buffer size");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
hio_setoption (g_hio, HIO_READ_BUFFER_SIZE, &restore);
}
static void test_read_buffer_resize_refused_in_callback (void)
{
/* on_read receives a pointer into the buffer. resizing it there would
* leave that pointer dangling, so the attempt has to fail. */
tdev_t* dev;
int peerfd;
hio_uint8_t buf[64];
obs_reset ();
g_wr_calls_left = 100;
g_setopt_from_on_read = 1;
dev = make_tdev(&peerfd);
if (!dev) { skip ("device creation failed", 2); return; }
HIO_MEMSET (buf, 'q', HIO_SIZEOF(buf));
if (write(peerfd, buf, HIO_SIZEOF(buf)) != (ssize_t)HIO_SIZEOF(buf))
{
skip ("peer write failed", 2);
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
return;
}
pump ();
OK (g_setopt_rc <= -1 && g_setopt_errnum == HIO_EBUSY,
"resizing the read buffer from inside on_read fails with HIO_EBUSY");
OK (hio_getoption(g_hio, HIO_READ_BUFFER_SIZE, &(hio_oow_t){0}) == 0,
"and the option is still readable afterwards");
hio_dev_kill ((hio_dev_t*)dev);
close (peerfd);
}