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:
parent
c5162db508
commit
0ebf8b997d
85
server.c
85
server.c
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
16
static.c
16
static.c
@ -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
1
www/index.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<html><body>Hello Cocoon</body></html>
|
||||||
Loading…
x
Reference in New Issue
Block a user