feat(raft-drive-async): bound peer connect (O_NONBLOCK+poll), close tasks 1.3/1.4/1.5

socket_connect sets O_NONBLOCK before connect and bounds the attempt with
poll(timeout); new connect_timeout_ms param (0=DEFAULT_CONNECT_TIMEOUT_MS);
all 5 callers updated (ha.c x3, client.c, xcectx.c). Revalidation Trigger:
shared netio helper signature changed. Broad build links all components;
xcelib_test 278/0, controller_unit_test 194/0. 1.4/1.5 unit tests satisfied
by the TDD output of 1.2/1.3 (test_slowlane.c, test_netio_bounded_connect.c).
This commit is contained in:
Rongsong Shen 2026-06-26 07:10:53 +00:00
parent 5650acd375
commit 0781c6c027
9 changed files with 289 additions and 18 deletions

View file

@ -4,6 +4,7 @@
#include <netdb.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@ -13,6 +14,7 @@
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "dataq.h"
#include "netio.h"
#include "request.h"
@ -279,34 +281,115 @@ static int socket_create(int domain, int type, int port, int backlog)
return sock;
}
static int socket_connect(int sock, char* host, int port)
/*
* socket_connect — bounded, non-blocking TCP connect (raft-drive-async R2.3).
*
* Sets O_NONBLOCK *before* connect() and, on EINPROGRESS, bounds the wait with
* poll() up to connect_timeout_ms (0 => DEFAULT_CONNECT_TIMEOUT_MS). This stops
* an unreachable peer from pinning a caller (notably the delaymgr heartbeat
* thread) for the multi-second OS connect timeout. The fd is LEFT non-blocking
* on success — endpoint_connect relies on that for the SSL handshake.
*
* Mirrors the xce_probe_config_tag bounded-connect pattern (config_repl.c).
*
* Returns:
* 0 — connected (fd left non-blocking)
* -ETIMEDOUT — poll bound expired before the connect completed
* -<errno> — name resolution / socket / connect error (e.g. -ECONNREFUSED)
*/
int socket_connect(int sock, char* host, int port, int connect_timeout_ms)
{
struct sockaddr_in server;
struct hostent* hp;
int rc;
int flags;
int timeout_ms;
server.sin_family = AF_INET;
hp = gethostbyname(host);
if (hp == NULL) {
SET_ERROR(errno);
rc = -errno;
return rc;
return -errno;
}
memcpy((char*)&server.sin_addr, (char*)hp->h_addr, (int)hp->h_length);
server.sin_port = htons(port);
rc = connect(sock, (struct sockaddr*)&server, sizeof(server));
if (rc < 0) {
timeout_ms = (connect_timeout_ms > 0) ? connect_timeout_ms
: DEFAULT_CONNECT_TIMEOUT_MS;
/* Set non-blocking BEFORE connect so an unreachable peer returns
* EINPROGRESS immediately instead of blocking. */
flags = fcntl(sock, F_GETFL, 0);
if (flags < 0) {
SET_ERROR(errno);
return -errno;
}
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
SET_ERROR(errno);
return -errno;
}
return rc;
rc = connect(sock, (struct sockaddr*)&server, sizeof(server));
if (rc == 0) {
/* Immediate completion (e.g. loopback). Leave fd non-blocking. */
return 0;
}
if (errno != EINPROGRESS) {
SET_ERROR(errno);
return -errno;
}
/* Connect in progress — bound the wait with poll(). */
{
struct pollfd pfd;
int pr;
pfd.fd = sock;
pfd.events = POLLOUT | POLLERR | POLLHUP;
pfd.revents = 0;
do {
pr = poll(&pfd, 1, timeout_ms);
} while (pr < 0 && errno == EINTR);
if (pr == 0) {
/* Bound expired: report timeout instead of waiting for the OS. */
SET_ERROR(ETIMEDOUT);
return -ETIMEDOUT;
}
if (pr < 0) {
SET_ERROR(errno);
return -errno;
}
/* poll returned ready — confirm the connect actually succeeded. */
{
int so_err = 0;
socklen_t elen = sizeof(so_err);
if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &so_err, &elen) < 0) {
SET_ERROR(errno);
return -errno;
}
if (so_err != 0) {
SET_ERROR(so_err);
return -so_err;
}
}
}
/* Connected; fd left non-blocking for the caller's SSL handshake. */
return 0;
}
static int socket_accept(int sock)
@ -694,12 +777,13 @@ int endpoint_init(endpoint_t* ep, int domain, int type, int port, int backlog)
return fd;
}
int endpoint_connect(endpoint_t* ep, char* host, int port)
int endpoint_connect(endpoint_t* ep, char* host, int port,
int connect_timeout_ms)
{
int rc = -1;
iomgr_t* iomgr = (iomgr_t*)ep->iomgr;
rc = socket_connect(ep->fd, host, port);
rc = socket_connect(ep->fd, host, port, connect_timeout_ms);
if (rc < 0) {
return rc;
}