292 lines
9.4 KiB
C++
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);
|
|
}
|