fix(protocol): honor --timeout for protocol I/O; bound idle/session time
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
#include "test_property.h"
|
||||
#include "test_protocol.h"
|
||||
#include "test_queue.h"
|
||||
#include "test_receiver_timeout.h"
|
||||
#include "test_robustness.h"
|
||||
#include "test_scanner.h"
|
||||
#include "test_server.h"
|
||||
@@ -61,6 +62,7 @@ int main() {
|
||||
RUN_TEST(test_delta);
|
||||
RUN_TEST(test_data);
|
||||
RUN_TEST(test_protocol);
|
||||
RUN_TEST(test_receiver_timeout);
|
||||
RUN_TEST(test_metadata);
|
||||
RUN_TEST(test_glob);
|
||||
RUN_TEST(test_iconv);
|
||||
|
||||
@@ -412,6 +412,35 @@ static void test_protocol_accounting_release_does_not_underflow() {
|
||||
protocol_session_unbind();
|
||||
}
|
||||
|
||||
static void test_protocol_session_io_timeout() {
|
||||
/* Default is the built-in 60 s window; the setter stores exactly what it is
|
||||
* given (<= 0 means "fall back to the default") so callers can propagate
|
||||
* --timeout without special-casing 0. */
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, -1, -1);
|
||||
EXPECT_EQ_INT(session.io_timeout_sec, 60);
|
||||
|
||||
protocol_session_set_io_timeout(&session, 120);
|
||||
EXPECT_EQ_INT(session.io_timeout_sec, 120);
|
||||
protocol_session_set_io_timeout(&session, 0);
|
||||
EXPECT_EQ_INT(session.io_timeout_sec, 0);
|
||||
/* A NULL session is a no-op, not a crash. */
|
||||
protocol_session_set_io_timeout(NULL, 5);
|
||||
|
||||
/* A short per-session deadline must actually bound a non-responsive read:
|
||||
* with no writer the poll waits for the configured 1 s and then fails,
|
||||
* rather than the built-in 60 s. */
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
ProtocolSession timed;
|
||||
protocol_session_init(&timed, p[0], p[1]);
|
||||
protocol_session_set_io_timeout(&timed, 1);
|
||||
char buf[4];
|
||||
EXPECT_FALSE(protocol_receive_n_data(&timed, buf, sizeof(buf)));
|
||||
close(p[0]);
|
||||
close(p[1]);
|
||||
}
|
||||
|
||||
static void test_send_receive_status_timed() {
|
||||
int p[2];
|
||||
EXPECT_EQ_INT(pipe(p), 0);
|
||||
@@ -440,6 +469,7 @@ void test_protocol() {
|
||||
test_send_receive_data();
|
||||
test_send_receive_int();
|
||||
test_send_receive_status();
|
||||
test_protocol_session_io_timeout();
|
||||
test_send_receive_status_timed();
|
||||
test_receive_n_data_truncated();
|
||||
test_receive_str_truncated();
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "test_receiver_timeout.h"
|
||||
|
||||
#include "protocol.h"
|
||||
#include "receiver.h"
|
||||
#include "test_utils.h"
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static bool sink_discard(File* file, void* context) {
|
||||
(void)context;
|
||||
file_destroy(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The idle/session bound is a pure function of three monotonic timestamps, so
|
||||
* it can be exercised deterministically without sleeping an hour. A tiny
|
||||
* overridden limit covers the same arithmetic the loop uses. */
|
||||
static void test_receiver_time_limit_predicate() {
|
||||
receiver_set_time_limits(10, 100);
|
||||
struct timespec start = {.tv_sec = 1000, .tv_nsec = 0};
|
||||
struct timespec fresh = {.tv_sec = 1000, .tv_nsec = 0};
|
||||
struct timespec just_idle = {.tv_sec = 1009, .tv_nsec = 0}; /* 9 s no progress */
|
||||
struct timespec idle = {.tv_sec = 1010, .tv_nsec = 0}; /* 10 s no progress */
|
||||
struct timespec just_wall = {.tv_sec = 1099, .tv_nsec = 0};
|
||||
struct timespec wall = {.tv_sec = 1100, .tv_nsec = 0}; /* 100 s session */
|
||||
struct timespec wp_just = {.tv_sec = 1098, .tv_nsec = 0}; /* idle 1 s */
|
||||
struct timespec wp_wall = {.tv_sec = 1099, .tv_nsec = 0}; /* idle 1 s */
|
||||
|
||||
EXPECT_FALSE(receiver_time_limit_exceeded(&start, &fresh, &fresh));
|
||||
EXPECT_FALSE(receiver_time_limit_exceeded(&start, &fresh, &just_idle));
|
||||
EXPECT_TRUE(receiver_time_limit_exceeded(&start, &fresh, &idle));
|
||||
EXPECT_FALSE(receiver_time_limit_exceeded(&start, &wp_just, &just_wall));
|
||||
EXPECT_TRUE(receiver_time_limit_exceeded(&start, &wp_wall, &wall));
|
||||
|
||||
/* Reset restores the generous production defaults (1 h idle / 24 h total). */
|
||||
receiver_reset_time_limits();
|
||||
struct timespec under_hour = {.tv_sec = 1000 + 3599, .tv_nsec = 0};
|
||||
EXPECT_FALSE(receiver_time_limit_exceeded(&start, &start, &under_hour));
|
||||
receiver_reset_time_limits();
|
||||
}
|
||||
|
||||
/* Drive the actual receive loop with a test-only idle limit of 0 so the very
|
||||
* first keepalive is rejected: this exercises the loop's abort path (log +
|
||||
* STATUS_ERROR + return -1) with no timing dependence. */
|
||||
static void test_receiver_aborts_idle_keepalive() {
|
||||
receiver_set_time_limits(0, 3600);
|
||||
int sv[2];
|
||||
EXPECT_EQ_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);
|
||||
Config* config = config_create();
|
||||
EXPECT_NOT_NULL(config);
|
||||
ReceiverSink sink = {.store_file = sink_discard,
|
||||
.context = NULL,
|
||||
.send_error = true,
|
||||
.send_success = false,
|
||||
.send_success_frame = NULL};
|
||||
|
||||
/* Bind an explicit session so the fd-based receive helpers use the
|
||||
* socketpair rather than any transport left over from an earlier test. */
|
||||
ProtocolSession session;
|
||||
protocol_session_init(&session, sv[1], sv[1]);
|
||||
protocol_session_bind(&session);
|
||||
|
||||
Status keepalive = STATUS_KEEPALIVE;
|
||||
EXPECT_EQ_INT((int)write(sv[0], &keepalive, sizeof(keepalive)), (int)sizeof(keepalive));
|
||||
int result = receiver_process_pending(config, sv[1], &sink, NULL);
|
||||
EXPECT_EQ_INT(result, -1);
|
||||
|
||||
Status reply = STATUS_OK;
|
||||
EXPECT_EQ_INT((int)read(sv[0], &reply, sizeof(reply)), (int)sizeof(reply));
|
||||
EXPECT_EQ_INT((int)reply, (int)STATUS_ERROR);
|
||||
|
||||
protocol_session_unbind();
|
||||
config_delete(config);
|
||||
close(sv[0]);
|
||||
close(sv[1]);
|
||||
receiver_reset_time_limits();
|
||||
}
|
||||
|
||||
void test_receiver_timeout(void) {
|
||||
/* EXPECT_* returns from the current function on failure, so reset the
|
||||
* process-global limits around the subtests (and again after) to guarantee a
|
||||
* failed assertion cannot leave the receiver aborted for later tests. */
|
||||
receiver_reset_time_limits();
|
||||
test_receiver_time_limit_predicate();
|
||||
test_receiver_aborts_idle_keepalive();
|
||||
receiver_reset_time_limits();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef TEST_RECEIVER_TIMEOUT_H
|
||||
#define TEST_RECEIVER_TIMEOUT_H
|
||||
|
||||
void test_receiver_timeout(void);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user