Phase 5: 反向代理连接池完善 — 连接有效性检测 + 统计指标 + 单元测试

- 新增 proxy_pool_conn_is_alive(): 使用 recv(MSG_PEEK | MSG_DONTWAIT)
  非阻塞检测连接是否被对端关闭,复用前自动剔除失效连接
- 新增 cocoon_pool_stats_t 统计结构:总请求、命中、新建、驱逐、
  有效性检测失败、活跃连接数、空闲连接数
- 连接池 acquire/release 全流程统计追踪
- 新增 7 项 proxy 单元测试:
  - 连接有效性检测(无效 fd、已关闭、有效 socket)
  - acquire/release 复用循环
  - 空池新建失败边界
  - 统计初始状态
- 全部测试通过:458 单元测试 + 115 集成测试
This commit is contained in:
xfy911 2026-06-15 12:23:48 +08:00
parent fb446ac7e6
commit b426ab7130
3 changed files with 235 additions and 1 deletions

66
proxy.c
View File

@ -43,6 +43,7 @@ void proxy_pool_init(cocoon_proxy_backend_t *backend, size_t max_pool_size) {
if (backend->pool.max_size == 0) { if (backend->pool.max_size == 0) {
backend->pool.max_size = COCOON_POOL_DEFAULT_SIZE; backend->pool.max_size = COCOON_POOL_DEFAULT_SIZE;
} }
memset(&backend->pool.stats, 0, sizeof(backend->pool.stats));
pthread_mutex_init(&backend->pool.mutex, NULL); pthread_mutex_init(&backend->pool.mutex, NULL);
for (size_t i = 0; i < COCOON_POOL_MAX_CAPACITY; i++) { for (size_t i = 0; i < COCOON_POOL_MAX_CAPACITY; i++) {
backend->pool.conns[i].fd = COCOON_INVALID_SOCKET; backend->pool.conns[i].fd = COCOON_INVALID_SOCKET;
@ -52,6 +53,50 @@ void proxy_pool_init(cocoon_proxy_backend_t *backend, size_t max_pool_size) {
} }
} }
/**
* proxy_pool_conn_is_alive -
*
* 使 recv(MSG_PEEK | MSG_DONTWAIT)
* socket
*/
bool proxy_pool_conn_is_alive(cocoon_socket_t fd) {
if (fd == COCOON_INVALID_SOCKET) return false;
char buf[1];
ssize_t r = recv(fd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
if (r == 0) {
/* 对端已关闭 */
return false;
}
if (r < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
/* 无数据但连接仍有效 */
return true;
}
/* 其他错误ECONNRESET 等) */
return false;
}
/* 有数据可读,连接有效 */
return true;
}
size_t proxy_pool_get_stats(cocoon_proxy_backend_t *backend, cocoon_pool_stats_t *stats) {
if (!backend) return 0;
pthread_mutex_lock(&backend->pool.mutex);
if (stats) {
*stats = backend->pool.stats;
}
size_t idle = 0;
for (size_t i = 0; i < backend->pool.max_size; i++) {
if (!backend->pool.conns[i].in_use &&
(backend->pool.conns[i].fd != COCOON_INVALID_SOCKET || backend->pool.conns[i].tls_conn)) {
idle++;
}
}
pthread_mutex_unlock(&backend->pool.mutex);
return idle;
}
void proxy_pool_destroy(cocoon_proxy_backend_t *backend) { void proxy_pool_destroy(cocoon_proxy_backend_t *backend) {
if (!backend) return; if (!backend) return;
pthread_mutex_lock(&backend->pool.mutex); pthread_mutex_lock(&backend->pool.mutex);
@ -91,6 +136,7 @@ bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, p
/* 检查超时 */ /* 检查超时 */
if ((now - pc->last_used) * 1000 > COCOON_POOL_IDLE_TIMEOUT_MS) { if ((now - pc->last_used) * 1000 > COCOON_POOL_IDLE_TIMEOUT_MS) {
/* 超时,关闭旧连接 */ /* 超时,关闭旧连接 */
backend->pool.stats.evict_count++;
if (pc->fd != COCOON_INVALID_SOCKET) { if (pc->fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(pc->fd); cocoon_socket_close(pc->fd);
pc->fd = COCOON_INVALID_SOCKET; pc->fd = COCOON_INVALID_SOCKET;
@ -102,9 +148,22 @@ bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, p
continue; continue;
} }
/* 检查连接有效性(非 HTTPS 连接) */
if (!use_https && !proxy_pool_conn_is_alive(pc->fd)) {
backend->pool.stats.alive_check_fail++;
if (pc->fd != COCOON_INVALID_SOCKET) {
cocoon_socket_close(pc->fd);
pc->fd = COCOON_INVALID_SOCKET;
}
continue;
}
/* 标记为使用中 */ /* 标记为使用中 */
pc->in_use = true; pc->in_use = true;
pc->last_used = now; pc->last_used = now;
backend->pool.stats.hit_count++;
backend->pool.stats.total_requests++;
backend->pool.stats.active_conns++;
*pfd = pc->fd; *pfd = pc->fd;
*ptls = pc->tls_conn; *ptls = pc->tls_conn;
pthread_mutex_unlock(&backend->pool.mutex); pthread_mutex_unlock(&backend->pool.mutex);
@ -138,6 +197,9 @@ bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, p
log_debug("连接池新建: %s:%d (fd=%d, tls=%p)", log_debug("连接池新建: %s:%d (fd=%d, tls=%p)",
backend->target_host, backend->target_port, backend->target_host, backend->target_port,
(int)*pfd, (void*)*ptls); (int)*pfd, (void*)*ptls);
backend->pool.stats.miss_count++;
backend->pool.stats.total_requests++;
backend->pool.stats.active_conns++;
return true; return true;
} }
@ -149,6 +211,10 @@ void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, pro
pthread_mutex_lock(&backend->pool.mutex); pthread_mutex_lock(&backend->pool.mutex);
if (backend->pool.stats.active_conns > 0) {
backend->pool.stats.active_conns--;
}
/* 先尝试找到这个连接的槽位(如果它原本就在池中) */ /* 先尝试找到这个连接的槽位(如果它原本就在池中) */
for (size_t i = 0; i < backend->pool.max_size; i++) { for (size_t i = 0; i < backend->pool.max_size; i++) {
cocoon_pooled_conn_t *pc = &backend->pool.conns[i]; cocoon_pooled_conn_t *pc = &backend->pool.conns[i];

34
proxy.h
View File

@ -26,6 +26,19 @@
#define COCOON_HEALTHY_THRESHOLD 2 /* 连续成功次数恢复健康 */ #define COCOON_HEALTHY_THRESHOLD 2 /* 连续成功次数恢复健康 */
#define COCOON_UNHEALTHY_THRESHOLD 3 /* 连续失败次数标记不健康 */ #define COCOON_UNHEALTHY_THRESHOLD 3 /* 连续失败次数标记不健康 */
/**
* cocoon_pool_stats_t -
*/
typedef struct {
uint64_t total_requests; /**< 总请求次数 */
uint64_t hit_count; /**< 复用命中次数 */
uint64_t miss_count; /**< 新建连接次数 */
uint64_t evict_count; /**< 超时驱逐次数 */
uint64_t alive_check_fail; /**< 有效性检测失败次数 */
size_t active_conns; /**< 当前活跃连接数 */
size_t idle_conns; /**< 当前空闲连接数 */
} cocoon_pool_stats_t;
/** /**
* cocoon_pooled_conn_t - * cocoon_pooled_conn_t -
*/ */
@ -42,6 +55,7 @@ typedef struct {
typedef struct { typedef struct {
cocoon_pooled_conn_t conns[COCOON_POOL_MAX_CAPACITY]; /**< 空闲连接数组上限16 */ cocoon_pooled_conn_t conns[COCOON_POOL_MAX_CAPACITY]; /**< 空闲连接数组上限16 */
size_t max_size; /**< 实际配置大小默认4 */ size_t max_size; /**< 实际配置大小默认4 */
cocoon_pool_stats_t stats; /**< 连接池统计 */
pthread_mutex_t mutex; /**< 保护锁 */ pthread_mutex_t mutex; /**< 保护锁 */
} cocoon_conn_pool_t; } cocoon_conn_pool_t;
@ -171,4 +185,24 @@ bool proxy_pool_acquire(cocoon_proxy_backend_t *backend, cocoon_socket_t *pfd, p
*/ */
void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, proxy_tls_conn_t *tls_conn); void proxy_pool_release(cocoon_proxy_backend_t *backend, cocoon_socket_t fd, proxy_tls_conn_t *tls_conn);
/**
* proxy_pool_get_stats -
*
* @param backend
* @param stats
* @return
*/
size_t proxy_pool_get_stats(cocoon_proxy_backend_t *backend, cocoon_pool_stats_t *stats);
/**
* proxy_pool_conn_is_alive -
*
* 使 recv(MSG_PEEK)
* socket
*
* @param fd socket
* @return true false
*/
bool proxy_pool_conn_is_alive(cocoon_socket_t fd);
#endif /* COCOON_PROXY_H */ #endif /* COCOON_PROXY_H */

View File

@ -3,6 +3,8 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <pthread.h> #include <pthread.h>
#include <sys/socket.h>
#include <unistd.h>
/* 白盒测试:包含 proxy.c 以访问静态函数 */ /* 白盒测试:包含 proxy.c 以访问静态函数 */
#define UNIT_TEST #define UNIT_TEST
@ -364,6 +366,126 @@ void test_pool_init_zero_uses_default(void) {
proxy_pool_destroy(&backend); proxy_pool_destroy(&backend);
} }
/* ===== proxy_pool_conn_is_alive ===== */
void test_conn_is_alive_invalid_fd(void) {
TEST_ASSERT_FALSE(proxy_pool_conn_is_alive(COCOON_INVALID_SOCKET));
}
void test_conn_is_alive_closed_fd(void) {
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
TEST_IGNORE_MESSAGE("socketpair 创建失败,跳过测试");
return;
}
close(fds[0]); /* 关闭一端 */
/* 另一端应该检测到对端关闭 */
TEST_ASSERT_FALSE(proxy_pool_conn_is_alive(fds[1]));
close(fds[1]);
}
void test_conn_is_alive_valid_socket(void) {
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
TEST_IGNORE_MESSAGE("socketpair 创建失败,跳过测试");
return;
}
/* 两端都打开,连接有效 */
TEST_ASSERT_TRUE(proxy_pool_conn_is_alive(fds[1]));
close(fds[0]);
close(fds[1]);
}
/* ===== proxy_pool_acquire / release 循环 ===== */
void test_pool_acquire_release_cycle(void) {
cocoon_proxy_backend_t backend = {0};
backend.target_https = false;
proxy_pool_init(&backend, 2);
/* 模拟连接:用 socketpair */
int fds[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
TEST_IGNORE_MESSAGE("socketpair 创建失败,跳过测试");
proxy_pool_destroy(&backend);
return;
}
/* 第一次 acquire池中无可用连接但当前代码没有真实连接时不会新建
* socket */
backend.pool.conns[0].fd = fds[1];
backend.pool.conns[0].in_use = false;
backend.pool.conns[0].last_used = time(NULL);
cocoon_socket_t pfd = COCOON_INVALID_SOCKET;
proxy_tls_conn_t *ptls = NULL;
bool ok = proxy_pool_acquire(&backend, &pfd, &ptls);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_EQUAL(fds[1], pfd);
TEST_ASSERT_NULL(ptls);
/* 释放回池 */
proxy_pool_release(&backend, pfd, ptls);
/* 再次 acquire应该复用 */
cocoon_socket_t pfd2 = COCOON_INVALID_SOCKET;
proxy_tls_conn_t *ptls2 = NULL;
ok = proxy_pool_acquire(&backend, &pfd2, &ptls2);
TEST_ASSERT_TRUE(ok);
TEST_ASSERT_EQUAL(fds[1], pfd2);
/* 统计2次复用第一次 + 第二次) */
cocoon_pool_stats_t stats;
proxy_pool_get_stats(&backend, &stats);
TEST_ASSERT_EQUAL(2, stats.hit_count);
TEST_ASSERT_EQUAL(0, stats.miss_count);
proxy_pool_release(&backend, pfd2, ptls2);
proxy_pool_destroy(&backend);
close(fds[0]);
}
void test_pool_acquire_new_conn_miss(void) {
cocoon_proxy_backend_t backend = {0};
backend.target_https = false;
strcpy(backend.target_host, "255.255.255.255"); /* 不可达,新建连接会失败 */
backend.target_port = 1;
proxy_pool_init(&backend, 2);
/* 空池 acquire会尝试新建连接 */
cocoon_socket_t pfd = COCOON_INVALID_SOCKET;
proxy_tls_conn_t *ptls = NULL;
bool ok = proxy_pool_acquire(&backend, &pfd, &ptls);
/* 新建连接会失败(因为 target 不可达) */
TEST_ASSERT_FALSE(ok);
/* 统计miss_count 应该没有增加,因为连接没建立成功 */
cocoon_pool_stats_t stats;
proxy_pool_get_stats(&backend, &stats);
TEST_ASSERT_EQUAL(0, stats.miss_count);
TEST_ASSERT_EQUAL(0, stats.hit_count);
proxy_pool_destroy(&backend);
}
/* ===== proxy_pool_get_stats ===== */
void test_pool_stats_initial(void) {
cocoon_proxy_backend_t backend = {0};
proxy_pool_init(&backend, 4);
cocoon_pool_stats_t stats;
size_t idle = proxy_pool_get_stats(&backend, &stats);
TEST_ASSERT_EQUAL(0, idle);
TEST_ASSERT_EQUAL(0, stats.total_requests);
TEST_ASSERT_EQUAL(0, stats.hit_count);
TEST_ASSERT_EQUAL(0, stats.miss_count);
TEST_ASSERT_EQUAL(0, stats.active_conns);
TEST_ASSERT_EQUAL(0, stats.evict_count);
proxy_pool_destroy(&backend);
}
/* ===== 主函数 ===== */ /* ===== 主函数 ===== */
int main(void) { int main(void) {
@ -418,5 +540,17 @@ int main(void) {
RUN_TEST(test_pool_init_capped); RUN_TEST(test_pool_init_capped);
RUN_TEST(test_pool_init_zero_uses_default); RUN_TEST(test_pool_init_zero_uses_default);
/* proxy_pool_conn_is_alive */
RUN_TEST(test_conn_is_alive_invalid_fd);
RUN_TEST(test_conn_is_alive_closed_fd);
RUN_TEST(test_conn_is_alive_valid_socket);
/* proxy_pool_acquire/release */
RUN_TEST(test_pool_acquire_release_cycle);
RUN_TEST(test_pool_acquire_new_conn_miss);
/* proxy_pool_stats */
RUN_TEST(test_pool_stats_initial);
return UNITY_END(); return UNITY_END();
} }