forked from lei.chen/myxce
Add io_classify_io_error and propagate endpoint/epoll errors through endpoint_update_epoll and cbuf_get instead of swallowing them, so client failover surfaces the real error rather than hanging. Adds netio arm-failure, close-propagation, and request send-propagation tests. Pre-existing uncommitted work, committed to establish a clean baseline before ha-commit-durability implementation.
95 lines
3.7 KiB
C
95 lines
3.7 KiB
C
/*
|
|
* test_netio_arm_failure.c — libxce-failover: a poll-arm failure must fail LOUD.
|
|
*
|
|
* Root cause (bug-646): endpoint_update_epoll() swallowed iomgr_update_poll()
|
|
* (EPOLL_CTL_MOD) failures — it logged "rc1=-1 < 0" but returned 0 and left the
|
|
* poll interest unarmed. On the synchronous client send path (endpoint_enq),
|
|
* a failed arm meant the request bytes were never flushed, no reply ever
|
|
* arrived, and the caller blocked forever in cbuf_get() while a perfectly
|
|
* healthy io_worker slept in epoll_wait(). (EPOLL_CTL_MOD fails with ENOENT
|
|
* when the fd is no longer registered — e.g. after a failed reconnect.)
|
|
*
|
|
* Contract under test: when the poll interest cannot be armed, endpoint_enq()
|
|
* must (1) return a negative, RETRIABLE error so the caller propagates it
|
|
* (request.c senders return it; the client failover loop rotates — bug-650),
|
|
* and (2) must NOT synchronously invoke the endpoint's err_handler. endpoint_enq
|
|
* runs on the caller's thread, which may already hold a lock the err_handler
|
|
* needs (the controller's primary_heartbeat_fn holds ha->lock and
|
|
* vote_ep_err_handler re-acquires it → self-deadlock, bug-652). The real
|
|
* teardown stays on the io_worker thread, off the next epoll error event.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#include <unity/unity.h>
|
|
#include "netio.h"
|
|
|
|
/* ── err_handler invocation tracker ──────────────────────────────── */
|
|
|
|
static int g_err_count = 0;
|
|
|
|
static int err_handler_stub(void *ep)
|
|
{
|
|
(void)ep;
|
|
g_err_count++;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Arming EPOLL_CTL_MOD on an fd that was never EPOLL_CTL_ADD'd fails with
|
|
* ENOENT — the exact post-failed-reconnect condition. endpoint_enq() must
|
|
* surface it (negative rc) WITHOUT calling the err_handler (no re-entrancy).
|
|
*/
|
|
static void test_enq_arm_failure_is_loud_no_errhandler(void)
|
|
{
|
|
iomgr_t iomgr;
|
|
memset(&iomgr, 0, sizeof(iomgr));
|
|
TEST_ASSERT_EQUAL_INT(0, iomgr_init(&iomgr));
|
|
|
|
endpoint_t *ep = endpoint_get();
|
|
TEST_ASSERT_NOT_NULL(ep);
|
|
|
|
/* A real socket fd that is NOT registered in iomgr's epoll: EPOLL_CTL_MOD
|
|
* on it fails with ENOENT, mirroring a send after a failed reconnect. */
|
|
ep->fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
TEST_ASSERT_TRUE(ep->fd >= 0);
|
|
ep->iomgr = &iomgr;
|
|
ep->ssl_handshake_done = 1; /* past handshake: calc_flags arms EPOLLIN */
|
|
ep->flags = 0; /* force flags != calc_flags → iomgr_update_poll */
|
|
endpoint_set_err_handler(ep, (err_handler_t)err_handler_stub);
|
|
|
|
g_err_count = 0;
|
|
|
|
/* One packet on the outq so there is real outbound work to strand. The
|
|
* endpoint owns it after enq; endpoint_fini drains it via outq_item_killer. */
|
|
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
|
|
TEST_ASSERT_NOT_NULL(pkt);
|
|
|
|
int rc = endpoint_enq(ep, &pkt->link);
|
|
|
|
/* Loud failure (negative, retriable) so the caller propagates and rotates —
|
|
* but the err_handler must NOT be invoked from this synchronous send path
|
|
* (re-entrancy would self-deadlock the controller, bug-652). */
|
|
TEST_ASSERT_TRUE(rc < 0);
|
|
TEST_ASSERT_EQUAL_INT(0, g_err_count);
|
|
|
|
endpoint_fini(ep);
|
|
free(ep);
|
|
close(iomgr.epoll_fd);
|
|
printf("test_enq_arm_failure_is_loud_no_errhandler: PASS\n");
|
|
}
|
|
|
|
/* ── suite entry point ───────────────────────────────────────────── */
|
|
|
|
void test_netio_arm_failure(void)
|
|
{
|
|
printf("\n>> netio poll-arm failure tests\n\n");
|
|
|
|
RUN_TEST(test_enq_arm_failure_is_loud_no_errhandler);
|
|
}
|