fix(server): 修复多线程模式下服务器无法处理请求的问题

问题根因:
1. coco_accept/coco_sleep/coco_timer 在多线程全局调度器下不工作,
   因为 main_sched 的 poll_fd 和 timer_wheel 无人轮询
2. client_handler 协程栈上分配了约 40KB+ 的 http_request_t,
   超出默认 2KB 协程栈,导致栈溢出 SIGSEGV

修复方案:
- 主线程直接 accept_loop(poll 阻塞),不使用协程
- accept 成功后通过 coco_go_with_opts 创建 client_handler 协程,
  显式指定 1MB 栈大小
- 单线程模式保持原有行为不变

测试:
- 59/59 集成测试通过
- 127/127 单元测试通过
- curl 多线程模式 HTTP 请求正常返回
This commit is contained in:
xfy911 2026-06-05 12:37:40 +08:00
parent c5162db508
commit 0ebf8b997d
3 changed files with 86 additions and 16 deletions

View File

@ -36,6 +36,7 @@
#include <netinet/tcp.h> #include <netinet/tcp.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <poll.h>
#include "../coco/include/coco.h" #include "../coco/include/coco.h"
@ -652,32 +653,41 @@ static void client_handler(void *arg) {
if (!conn) return; if (!conn) return;
conn->coro = coco_self(); conn->coro = coco_self();
log_debug("client_handler 启动 fd=%d", conn->fd);
log_debug("fd=%d 检查 HTTP/2...", conn->fd);
/* 检查是否是 HTTP/2 连接TLS ALPN 协商) */ /* 检查是否是 HTTP/2 连接TLS ALPN 协商) */
if (http2_session_is_http2(conn->fd)) { if (http2_session_is_http2(conn->fd)) {
log_debug("fd=%d 是 HTTP/2 连接", conn->fd);
handle_http2(conn); handle_http2(conn);
conn_cancel_timer(conn); conn_cancel_timer(conn);
close_connection(conn); close_connection(conn);
free(conn); free(conn);
return; return;
} }
log_debug("fd=%d 不是 HTTP/2", conn->fd);
/* 启动空闲定时器 */ /* 启动空闲定时器 */
log_debug("fd=%d 创建定时器...", conn->fd);
uint32_t timeout_ms = conn->timeout_ms > 0 ? conn->timeout_ms : CONN_TIMEOUT_MS; uint32_t timeout_ms = conn->timeout_ms > 0 ? conn->timeout_ms : CONN_TIMEOUT_MS;
conn->timer = coco_timer(timeout_ms, conn_timeout_handler, conn); conn->timer = coco_timer(timeout_ms, conn_timeout_handler, conn);
log_debug("fd=%d 定时器已创建 timer=%p", conn->fd, (void*)conn->timer);
while (!conn->closed) { while (!conn->closed) {
/* 读取数据 */ /* 读取数据 */
ssize_t n = conn_read(conn); ssize_t n = conn_read(conn);
log_debug("fd=%d conn_read 返回 %zd", conn->fd, n);
if (n < 0) { if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* 数据不足,继续等待 */ log_debug("fd=%d EAGAIN继续等待", conn->fd);
continue; continue;
} }
/* coco_read 返回负值错误码(如 COCO_ERROR_CANCELLED或系统错误 */ /* coco_read 返回负值错误码(如 COCO_ERROR_CANCELLED或系统错误 */
log_debug("fd=%d conn_read 错误: %d %s", conn->fd, (int)n, strerror(errno));
break; break;
} }
if (n == 0) { if (n == 0) {
log_debug("fd=%d 对端关闭", conn->fd);
break; /* 对端关闭 */ break; /* 对端关闭 */
} }
@ -744,6 +754,7 @@ static void client_handler(void *arg) {
/* 尝试处理请求 */ /* 尝试处理请求 */
bool keep = handle_request(conn, conn->root_dir); bool keep = handle_request(conn, conn->root_dir);
log_debug("fd=%d handle_request 返回 %d", conn->fd, keep);
if (!keep) { if (!keep) {
break; break;
} }
@ -751,6 +762,7 @@ static void client_handler(void *arg) {
conn_cancel_timer(conn); conn_cancel_timer(conn);
close_connection(conn); close_connection(conn);
log_debug("client_handler 结束 fd=%d", conn->fd);
free(conn); free(conn);
} }
@ -786,11 +798,36 @@ static void accept_loop(void *arg) {
log_info("连接空闲超时: %u ms", ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS); log_info("连接空闲超时: %u ms", ctx->config.timeout_ms > 0 ? ctx->config.timeout_ms : CONN_TIMEOUT_MS);
while (ctx->running) { while (ctx->running) {
if (ctx->listen_fd < 0) break;
struct sockaddr_storage client_addr; struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(client_addr); socklen_t addr_len = sizeof(client_addr);
int client_fd;
int client_fd = accept(ctx->listen_fd, if (ctx->config.threaded) {
/* 多线程模式:非阻塞 accept + poll 阻塞等待 */
client_fd = accept(ctx->listen_fd,
(struct sockaddr *)&client_addr, &addr_len); (struct sockaddr *)&client_addr, &addr_len);
if (client_fd < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
struct pollfd pfd = {
.fd = ctx->listen_fd,
.events = POLLIN
};
int ret = poll(&pfd, 1, 100);
if (ret < 0 && errno != EINTR) {
log_error("poll 失败: %s", strerror(errno));
break;
}
if (ret > 0 && (pfd.revents & POLLNVAL)) {
break; /* listen_fd 被关闭 */
}
continue;
}
} else {
/* 单线程模式:标准阻塞 accept */
client_fd = accept(ctx->listen_fd,
(struct sockaddr *)&client_addr, &addr_len);
}
if (client_fd < 0) { if (client_fd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
continue; continue;
@ -828,6 +865,11 @@ static void accept_loop(void *arg) {
int opt = 1; int opt = 1;
setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
/* 多线程模式下设置 client_fd 非阻塞(用于 coco_read/coco_write */
if (ctx->config.threaded) {
set_nonblocking(client_fd);
}
/* TLS 握手(如果启用) */ /* TLS 握手(如果启用) */
if (tls_has_context()) { if (tls_has_context()) {
if (tls_accept(client_fd) != 0) { if (tls_accept(client_fd) != 0) {
@ -875,16 +917,25 @@ static void accept_loop(void *arg) {
log_debug("新连接 fd=%d当前活跃连接: %d", client_fd, log_debug("新连接 fd=%d当前活跃连接: %d", client_fd,
atomic_load(&g_active_connections)); atomic_load(&g_active_connections));
if (ctx->config.threaded && coco_sched_get_current()) { if (ctx->config.threaded) {
/* 多线程协程模式:创建协程处理连接 */ /* 多线程模式:使用 coco_go_with_opts 创建工作协程128KB 栈 */
coco_coro_t *coro = coco_create(coco_sched_get_current(), coco_go_opts_t opts = {
client_handler, conn, 0); .stack_size = 1024 * 1024,
.context = NULL,
.priority = -1,
.p_id = -1
};
coco_coro_t *coro = coco_go_with_opts(client_handler, conn, &opts);
if (!coro) { if (!coro) {
log_error("创建协程失败,关闭连接 fd=%d", client_fd); log_error("coco_go_with_opts(client_handler) 返回 NULL无法创建协程 fd=%d", client_fd);
atomic_fetch_sub(&g_active_connections, 1); http2_session_t *h2 = http2_session_get(client_fd);
close_connection(conn); if (h2) http2_session_destroy(h2);
close(client_fd);
free(conn); free(conn);
atomic_fetch_sub(&g_active_connections, 1);
continue;
} }
log_debug("client_handler 协程已创建 coro=%p fd=%d", (void*)coro, client_fd);
} else { } else {
/* 单线程模式:直接调用处理函数(阻塞) */ /* 单线程模式:直接调用处理函数(阻塞) */
client_handler(conn); client_handler(conn);
@ -952,9 +1003,6 @@ server_context_t *server_create(const cocoon_config_t *config) {
} }
} }
/* 非阻塞模式 */
set_nonblocking(ctx->listen_fd);
return ctx; return ctx;
} }
@ -984,14 +1032,17 @@ int server_start(server_context_t *ctx) {
return COCOON_ERROR; return COCOON_ERROR;
} }
/* 在调度器上运行 accept 循环 */ /* 多线程模式下 listen_fd 需要非阻塞(用于 accept + poll 等待) */
/* 注意:当前实现使用主线程直接 accept多线程仅用于工作协程 */ set_nonblocking(ctx->listen_fd);
/* 在主线程中运行 accept_loop */
accept_loop(ctx); accept_loop(ctx);
/* 等待所有协程完成 */
coco_global_sched_wait(); coco_global_sched_wait();
coco_global_sched_stop(); coco_global_sched_stop();
} else { } else {
/* 单线程模式 */ /* 单线程模式listen_fd 保持阻塞 */
accept_loop(ctx); accept_loop(ctx);
} }
@ -1008,6 +1059,10 @@ int server_start(server_context_t *ctx) {
void server_stop(server_context_t *ctx) { void server_stop(server_context_t *ctx) {
if (ctx) { if (ctx) {
ctx->running = 0; ctx->running = 0;
if (ctx->listen_fd >= 0) {
close(ctx->listen_fd);
ctx->listen_fd = -1;
}
} }
} }

View File

@ -10,6 +10,7 @@
#include "static.h" #include "static.h"
#include "cocoon.h" #include "cocoon.h"
#include "../coco/include/coco.h"
#include "tls.h" #include "tls.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -246,7 +247,20 @@ int send_all(int fd, const char *buf, size_t len) {
size_t sent = 0; size_t sent = 0;
while (sent < len) { while (sent < len) {
ssize_t n = write(fd, buf + sent, len - sent); ssize_t n;
if (coco_sched_get_current() != NULL) {
/* 多线程协程模式:使用 coco_write自动 yield 等待) */
int ret = coco_write(fd, buf + sent, len - sent);
if (ret < 0) {
if (ret == COCO_ERROR_WOULD_BLOCK) {
continue;
}
return -1;
}
n = ret;
} else {
n = write(fd, buf + sent, len - sent);
}
if (n < 0) { if (n < 0) {
if (errno == EAGAIN || errno == EINTR) continue; if (errno == EAGAIN || errno == EINTR) continue;
return -1; return -1;

1
www/index.html Normal file
View File

@ -0,0 +1 @@
<html><body>Hello Cocoon</body></html>