From 199d0810935524608204c408e00a75b205f2a69a Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 15:38:30 +0800 Subject: [PATCH 1/9] refactor(event): centralize nio handshake dispatch Co-authored-by: TRAE CLI --- Makefile | 1 + docs/cn/hloop.md | 13 +++++ event/hevent.c | 2 + event/hevent.h | 16 +++++- event/hloop.c | 8 ++- event/hloop.h | 18 ++++++- event/nio.c | 108 +++++++++++++++++++++++--------------- event/proxy.c | 25 ++++++++- event/proxy.h | 1 + event/socks5.c | 6 +-- event/socks5.h | 1 + scripts/unittest.sh | 3 ++ unittest/CMakeLists.txt | 5 ++ unittest/hio_phase_test.c | 102 +++++++++++++++++++++++++++++++++++ 14 files changed, 259 insertions(+), 50 deletions(-) create mode 100644 unittest/hio_phase_test.c diff --git a/Makefile b/Makefile index 2b7f50464..15623635d 100644 --- a/Makefile +++ b/Makefile @@ -411,6 +411,7 @@ unittest: prepare libhv $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Iprotocol -Iutil -o bin/sendmail unittest/sendmail_test.c protocol/smtp.c base/hsocket.c base/htime.c util/base64.c $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hdns_test unittest/hdns_test.c -Llib -lhv -pthread $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hdns_benchmark unittest/hdns_benchmark.c -Llib -lhv -pthread + $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hio_phase_test unittest/hio_phase_test.c -Llib -lhv -pthread ifeq ($(WITH_EVPP), yes) $(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -o bin/tcpclient_dns_test unittest/tcpclient_dns_test.cpp -Llib -lhv -pthread endif diff --git a/docs/cn/hloop.md b/docs/cn/hloop.md index 065be1f38..f799f3e9e 100644 --- a/docs/cn/hloop.md +++ b/docs/cn/hloop.md @@ -271,6 +271,8 @@ hwrite_cb hio_getcb_write(hio_t* io); // 获取关闭回调 hclose_cb hio_getcb_close(hio_t* io); +// 回调语义见下文“IO回调语义与缓冲区生命周期”。 + // 开启SSL/TLS加密通信 int hio_enable_ssl(hio_t* io); // 是否SSL/TLS加密通信 @@ -311,6 +313,7 @@ int hio_accept (hio_t* io); // 连接 // connect => hio_add(io, HV_WRITE) => hconnect_cb +// 在非IOCP的NIO后端,connect_cb表示应用层连接可用;配置代理或TLS时会在相应握手全部成功后才调用。 int hio_connect(hio_t* io); // 读 @@ -347,10 +350,12 @@ int hio_read_until_delim (hio_t* io, unsigned char delim); // 写 // hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb +// write_cb报告实际写出的字节数;使用hio_write_is_complete判断写队列是否排空。 int hio_write (hio_t* io, const void* buf, size_t len); // 关闭 // hio_del(io, HV_RDWR) => close => hclose_cb +// close_cb在一次hio生命周期中最多调用一次,适合释放连接关联的上下文。 int hio_close (hio_t* io); // 异步关闭 (投递一个close事件) @@ -644,6 +649,14 @@ int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL)); ``` +### IO回调语义与缓冲区生命周期 + +- 在非IOCP的NIO后端,`connect_cb` 表示应用层连接已可用:若设置了代理或TLS,代理握手和TLS握手均已成功;它并不只是TCP三次握手完成。 +- `write_cb` 的 `writebytes` 表示本次实际写出的字节数,不表示一条业务消息已经全部发送;需要通过 `hio_write_is_complete(io)` 判断写队列是否排空。 +- `read_cb` 和 `write_cb` 都可能同步触发:例如 `hio_read` 处理已有读缓存,或 `hio_write` 立即写入成功时。因此回调代码必须能处理可重入调用。 +- `close_cb` 在一次 `hio_t` 生命周期内最多触发一次,是释放连接关联上下文的合适位置。 +- `read_cb` / `write_cb` 收到的 `buf` 由libhv借用提供,只保证在本次回调返回前有效;需要保存或跨异步边界使用时必须自行复制。 + 示例代码: - 事件循环: [examples/hloop_test.c](../../examples/hloop_test.c) diff --git a/event/hevent.c b/event/hevent.c index 44f12d485..c500cfb2d 100644 --- a/event/hevent.c +++ b/event/hevent.c @@ -94,6 +94,7 @@ void hio_ready(hio_t* io) { // public: io->id = hio_next_id(); io->io_type = HIO_TYPE_UNKNOWN; + io->phase = HIO_PHASE_READY; io->error = 0; io->events = io->revents = 0; io->last_read_hrtime = io->last_write_hrtime = io->loop->cur_hrtime; @@ -426,6 +427,7 @@ void hio_write_cb(hio_t* io, const void* buf, int len) { void hio_close_cb(hio_t* io) { io->connected = 0; io->closed = 1; + io->phase = HIO_PHASE_CLOSED; hclose_cb close_cb = io->close_cb; if (close_cb) { // printd("close_cb------\n"); diff --git a/event/hevent.h b/event/hevent.h index a844879ae..94829d5b5 100644 --- a/event/hevent.h +++ b/event/hevent.h @@ -24,6 +24,19 @@ #define HIO_READ_UNTIL_LENGTH 0x2 #define HIO_READ_UNTIL_DELIM 0x4 +// Internal NIO transport lifecycle. hio_handle_events is the single readiness +// dispatcher; phase selects its current transport or handshake action. +typedef enum { + HIO_PHASE_READY = 0, + HIO_PHASE_ACCEPTING, + HIO_PHASE_CONNECTING, + HIO_PHASE_PROXY_HANDSHAKING, + HIO_PHASE_TLS_SERVER_HANDSHAKING, + HIO_PHASE_TLS_CLIENT_HANDSHAKING, + HIO_PHASE_ESTABLISHED, + HIO_PHASE_CLOSED, +} hio_phase_e; + ARRAY_DECL(hio_t*, io_array); ARRAY_DECL(hsignal_t*, signal_array); QUEUE_DECL(hevent_t, event_queue); @@ -115,7 +128,7 @@ struct hperiod_s { }; QUEUE_DECL(offset_buf_t, write_queue); -// sizeof(struct hio_s)=416 on linux-x64 +// sizeof(struct hio_s)=424 on linux-x64 struct hio_s { HEVENT_FIELDS // flags @@ -134,6 +147,7 @@ struct hio_s { unsigned alloced_ssl_ctx :1; // for hio_new_ssl_ctx // public: hio_type_e io_type; + hio_phase_e phase; uint32_t id; // fd cannot be used as unique identifier, so we provide an id int fd; int error; diff --git a/event/hloop.c b/event/hloop.c index 133df4fd2..6137592db 100644 --- a/event/hloop.c +++ b/event/hloop.c @@ -898,7 +898,13 @@ int hio_add(hio_t* io, hio_cb cb, int events) { io->events |= add_events; if (!io->active) { - EVENT_ADD(loop, io, cb); + // A NULL callback only changes the watched mask. Keep the transport + // dispatcher installed by the initial hio_add call. + hio_cb handler = cb ? cb : (hio_cb)io->cb; + io->loop = loop; + io->event_id = hloop_next_event_id(); + io->cb = (hevent_cb)handler; + EVENT_ACTIVE(io); loop->nios++; } return 0; diff --git a/event/hloop.h b/event/hloop.h index 4716fee17..ca080062f 100644 --- a/event/hloop.h +++ b/event/hloop.h @@ -30,6 +30,14 @@ typedef void (*hread_cb) (hio_t* io, void* buf, int readbytes); typedef void (*hwrite_cb) (hio_t* io, const void* buf, int writebytes); typedef void (*hclose_cb) (hio_t* io); +/* + * Callback buffer lifetime: buf passed to hread_cb/hwrite_cb is borrowed from + * libhv and is valid only until the callback returns. Copy it before retaining + * it or using it asynchronously. hio_read/hio_write can deliver a callback + * synchronously (for buffered data or an immediately writable socket), so + * callback code must tolerate re-entry. + */ + typedef enum { HLOOP_STATUS_STOP, HLOOP_STATUS_RUNNING, @@ -423,6 +431,9 @@ HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_ HV_EXPORT int hio_accept (hio_t* io); // connect => hio_add(io, HV_WRITE) => hconnect_cb +// On non-IOCP NIO backends, hconnect_cb means the application connection is +// ready: any configured proxy and TLS handshakes have completed. It is not +// merely TCP connect completion. HV_EXPORT int hio_connect(hio_t* io); // hio_add(io, HV_READ) => read => hread_cb @@ -444,12 +455,15 @@ HV_EXPORT int hio_read_remain(hio_t* io); #define hio_read_until(io, len) hio_read_until_length(io, len) // NOTE: hio_write is thread-safe, locked by recursive_mutex, allow to be called by other threads. -// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb +// hio_try_write => hio_add(io, HV_WRITE) => write => hwrite_cb. hwrite_cb +// reports bytes written, not completion of a logical application message; use +// hio_write_is_complete(io) to determine whether the write queue is empty. HV_EXPORT int hio_write (hio_t* io, const void* buf, size_t len); HV_EXPORT int hio_sendto (hio_t* io, const void* buf, size_t len, struct sockaddr* addr); // NOTE: hio_close is thread-safe, hio_close_async will be called actually in other thread. -// hio_del(io, HV_RDWR) => close => hclose_cb +// hio_del(io, HV_RDWR) => close => hclose_cb. hclose_cb is invoked at most once +// per hio lifecycle and is the place to release connection-associated state. HV_EXPORT int hio_close (hio_t* io); // NOTE: hloop_post_event(hio_close_event) HV_EXPORT int hio_close_async(hio_t* io); diff --git a/event/nio.c b/event/nio.c index 7805cbac8..1a5cb50da 100644 --- a/event/nio.c +++ b/event/nio.c @@ -66,53 +66,51 @@ static void __close_cb(hio_t* io) { hio_close_cb(io); } -static void ssl_server_handshake(hio_t* io) { - printd("ssl server handshake...\n"); - int ret = hssl_accept(io->ssl); - if (ret == 0) { - // handshake finish - hio_del(io, HV_RDWR); - printd("ssl handshake finished.\n"); - __accept_cb(io); - } - else if (ret == HSSL_WANT_READ) { - if (io->events & HV_WRITE) { - hio_del(io, HV_WRITE); - } - if ((io->events & HV_READ) == 0) { - hio_add(io, ssl_server_handshake, HV_READ); - } - } - else if (ret == HSSL_WANT_WRITE) { - if (io->events & HV_READ) { - hio_del(io, HV_READ); - } - if ((io->events & HV_WRITE) == 0) { - hio_add(io, ssl_server_handshake, HV_WRITE); - } - } - else { - hloge("ssl server handshake failed: %d", ret); - io->error = ERR_SSL_HANDSHAKE; - hio_close(io); +static void hio_handle_events(hio_t* io); + +static bool nio_write_deferred(hio_t* io) { + return io->phase == HIO_PHASE_CONNECTING || + io->phase == HIO_PHASE_PROXY_HANDSHAKING || + io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING || + io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; +} + +static void nio_flush_deferred_writes(hio_t* io) { + hrecursive_mutex_lock(&io->write_mutex); + bool pending = !write_queue_empty(&io->write_queue); + hrecursive_mutex_unlock(&io->write_mutex); + if (!io->closed && pending) { + hio_add(io, hio_handle_events, HV_WRITE); } } -static void ssl_client_handshake(hio_t* io) { - printd("ssl client handshake...\n"); - int ret = hssl_connect(io->ssl); +static void nio_connect_ready(hio_t* io) { + io->phase = HIO_PHASE_ESTABLISHED; + __connect_cb(io); + nio_flush_deferred_writes(io); +} + +static void nio_tls_handshake(hio_t* io) { + bool server = io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; + printd("ssl %s handshake...\n", server ? "server" : "client"); + int ret = server ? hssl_accept(io->ssl) : hssl_connect(io->ssl); if (ret == 0) { // handshake finish hio_del(io, HV_RDWR); printd("ssl handshake finished.\n"); - __connect_cb(io); + io->phase = HIO_PHASE_ESTABLISHED; + if (server) { + __accept_cb(io); + } else { + nio_connect_ready(io); + } } else if (ret == HSSL_WANT_READ) { if (io->events & HV_WRITE) { hio_del(io, HV_WRITE); } if ((io->events & HV_READ) == 0) { - hio_add(io, ssl_client_handshake, HV_READ); + hio_add(io, hio_handle_events, HV_READ); } } else if (ret == HSSL_WANT_WRITE) { @@ -120,11 +118,11 @@ static void ssl_client_handshake(hio_t* io) { hio_del(io, HV_READ); } if ((io->events & HV_WRITE) == 0) { - hio_add(io, ssl_client_handshake, HV_WRITE); + hio_add(io, hio_handle_events, HV_WRITE); } } else { - hloge("ssl client handshake failed: %d", ret); + hloge("ssl %s handshake failed: %d", server ? "server" : "client", ret); io->error = ERR_SSL_HANDSHAKE; hio_close(io); } @@ -183,10 +181,12 @@ static void nio_accept(hio_t* io) { connio->ssl = ssl; } hio_enable_ssl(connio); - ssl_server_handshake(connio); + connio->phase = HIO_PHASE_TLS_SERVER_HANDSHAKING; + nio_tls_handshake(connio); } else { // NOTE: SSL call accept_cb after handshake finished + connio->phase = HIO_PHASE_ESTABLISHED; __accept_cb(connio); } } @@ -239,11 +239,12 @@ static void nio_connect_established(hio_t* io) { if (sni) { hssl_set_sni_hostname(io->ssl, sni); } - ssl_client_handshake(io); + io->phase = HIO_PHASE_TLS_CLIENT_HANDSHAKING; + nio_tls_handshake(io); } else { // NOTE: SSL call connect_cb after handshake finished - __connect_cb(io); + nio_connect_ready(io); } } @@ -465,7 +466,14 @@ static void nio_write(hio_t* io) { static void hio_handle_events(hio_t* io) { if ((io->events & HV_READ) && (io->revents & HV_READ)) { - if (io->accept) { + if (io->phase == HIO_PHASE_PROXY_HANDSHAKING) { + proxy_handshake_read(io); + } + else if (io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || + io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { + nio_tls_handshake(io); + } + else if (io->accept) { nio_accept(io); } else { @@ -480,7 +488,15 @@ static void hio_handle_events(hio_t* io) { hio_del(io, HV_WRITE); } hrecursive_mutex_unlock(&io->write_mutex); - if (io->connect) { + if (io->phase == HIO_PHASE_PROXY_HANDSHAKING) { + // Proxy negotiation only consumes readable events. Application + // writes remain queued until the connection becomes established. + } + else if (io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || + io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { + nio_tls_handshake(io); + } + else if (io->connect) { // NOTE: connect just do once // ONESHOT io->connect = 0; @@ -497,10 +513,12 @@ static void hio_handle_events(hio_t* io) { int hio_accept(hio_t* io) { io->accept = 1; + io->phase = HIO_PHASE_ACCEPTING; return hio_add(io, hio_handle_events, HV_READ); } int hio_connect(hio_t* io) { + io->phase = HIO_PHASE_CONNECTING; int ret = connect(io->fd, io->peeraddr, SOCKADDR_LEN(io->peeraddr)); #ifdef OS_WIN if (ret < 0 && socket_errno() != WSAEWOULDBLOCK) { @@ -553,6 +571,10 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* } #endif if (write_queue_empty(&io->write_queue)) { + if (nio_write_deferred(io)) { + nwrite = 0; + goto enqueue; + } try_write: nwrite = __nio_write(io, buf, len, addr); // printd("write retval=%d\n", nwrite); @@ -575,7 +597,9 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* goto disconnect; } enqueue: - hio_add(io, hio_handle_events, HV_WRITE); + if (!nio_write_deferred(io)) { + hio_add(io, hio_handle_events, HV_WRITE); + } } if (nwrite < len) { size_t unwritten_len = len - nwrite; diff --git a/event/proxy.c b/event/proxy.c index 410db6c92..19421dce9 100644 --- a/event/proxy.c +++ b/event/proxy.c @@ -130,6 +130,7 @@ void proxy_handshake_established(hio_t* io) { proxy_handshake_fail(io); return; } + io->phase = HIO_PHASE_CONNECTING; proxy->on_established(io); } @@ -189,7 +190,25 @@ static void http_connect_client_start(hio_t* io) { return; } io->proxy->rlen = 0; - hio_add(io, http_connect_client_handshake, HV_READ); + hio_add(io, NULL, HV_READ); +} + +void proxy_handshake_read(hio_t* io) { + if (io->proxy == NULL) { + proxy_handshake_fail(io); + return; + } + switch (io->proxy->setting.protocol) { + case PROXY_PROTOCOL_SOCKS5: + socks5_client_handshake_read(io); + return; + case PROXY_PROTOCOL_HTTP_CONNECT: + http_connect_client_handshake(io); + return; + default: + proxy_handshake_fail(io); + return; + } } void proxy_handshake_start(hio_t* io, proxy_established_cb on_established) { @@ -198,6 +217,10 @@ void proxy_handshake_start(hio_t* io, proxy_established_cb on_established) { proxy_handshake_fail(io); return; } + io->phase = HIO_PHASE_PROXY_HANDSHAKING; + if (io->events & HV_WRITE) { + hio_del(io, HV_WRITE); + } proxy->on_established = on_established; switch (proxy->setting.protocol) { case PROXY_PROTOCOL_SOCKS5: diff --git a/event/proxy.h b/event/proxy.h index c5f17166e..f734b74f4 100644 --- a/event/proxy.h +++ b/event/proxy.h @@ -28,6 +28,7 @@ bool proxy_setting_valid(const proxy_setting_t* setting, bool need_target); int http_connect_build_request(const proxy_ctx_t* proxy, char* buf, int bufsize); void proxy_handshake_start(hio_t* io, proxy_established_cb on_established); +void proxy_handshake_read(hio_t* io); void proxy_handshake_fail(hio_t* io); int proxy_handshake_send(hio_t* io, const void* buf, int len); void proxy_handshake_established(hio_t* io); diff --git a/event/socks5.c b/event/socks5.c index 47c70d949..960e74932 100644 --- a/event/socks5.c +++ b/event/socks5.c @@ -210,7 +210,7 @@ typedef enum { S5C_RECV_REPLY_ADDR, S5C_RECV_REPLY_DADDR, } socks5_client_state_e; -static void socks5_client_handshake(hio_t* io); +void socks5_client_handshake_read(hio_t* io); static void socks5_client_expect(hio_t* io, int state, int want) { proxy_ctx_t* proxy = io->proxy; @@ -287,7 +287,7 @@ static void socks5_client_dispatch(hio_t* io) { } } -static void socks5_client_handshake(hio_t* io) { +void socks5_client_handshake_read(hio_t* io) { proxy_ctx_t* proxy = io->proxy; while (proxy->rlen < proxy->want) { int need = proxy->want - proxy->rlen; @@ -311,7 +311,7 @@ void socks5_client_handshake_start(hio_t* io) { int n = socks5_build_method_request(io->proxy, buf); if (proxy_handshake_send(io, buf, n) != 0) { proxy_handshake_fail(io); return; } socks5_client_expect(io, S5C_RECV_METHOD, 2); - hio_add(io, socks5_client_handshake, HV_READ); + hio_add(io, NULL, HV_READ); } // Build the SOCKS5 method-selection request. diff --git a/event/socks5.h b/event/socks5.h index 641410402..f00d29396 100644 --- a/event/socks5.h +++ b/event/socks5.h @@ -36,6 +36,7 @@ int socks5_build_auth_request (const proxy_ctx_t* s5, unsigned char* buf); int socks5_build_connect_request(const proxy_ctx_t* s5, unsigned char* buf); void socks5_client_handshake_start(hio_t* io); +void socks5_client_handshake_read(hio_t* io); END_EXTERN_C diff --git a/scripts/unittest.sh b/scripts/unittest.sh index 5a4d05668..8c8aaef0c 100755 --- a/scripts/unittest.sh +++ b/scripts/unittest.sh @@ -71,6 +71,9 @@ fi if [ -x bin/hdns_test ]; then bin/hdns_test fi +if [ -x bin/hio_phase_test ]; then + bin/hio_phase_test +fi if [ -x bin/tcpclient_dns_test ]; then bin/tcpclient_dns_test fi diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index d0a1327fc..7f75f6cfb 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -157,6 +157,10 @@ endif() endif() # ------event: async dns------ +add_executable(hio_phase_test hio_phase_test.c) +target_include_directories(hio_phase_test PRIVATE .. ../base ../ssl ../event) +target_link_libraries(hio_phase_test ${HV_LIBRARIES}) + add_executable(hdns_test hdns_test.c) target_include_directories(hdns_test PRIVATE .. ../base ../ssl ../event) target_link_libraries(hdns_test ${HV_LIBRARIES}) @@ -223,6 +227,7 @@ add_custom_target(unittest DEPENDS http_router_test ${HTTP_LUA_UNITTEST_TARGETS} ${HTTP_JS_UNITTEST_TARGETS} + hio_phase_test hdns_test hdns_benchmark ${REDIS_UNITTEST_TARGETS} diff --git a/unittest/hio_phase_test.c b/unittest/hio_phase_test.c new file mode 100644 index 000000000..5f9742838 --- /dev/null +++ b/unittest/hio_phase_test.c @@ -0,0 +1,102 @@ +#include +#include +#include + +#include "hevent.h" +#include "hsocket.h" + +#ifdef EVENT_IOCP +int main() { + printf("hio_phase_test skipped: NIO phase dispatch is not used by IOCP\n"); + return 0; +} +#else + +static int s_accept_count = 0; +static int s_connect_count = 0; +static int s_read_count = 0; +static hloop_t* s_loop = NULL; +static hio_t* s_listener = NULL; +static hio_t* s_client = NULL; +static const char s_preconnect_message[] = "queued before connect"; + +static void finish_if_ready() { + if (s_accept_count != 1 || s_connect_count != 1 || s_read_count != 1) return; + hloop_stop(s_loop); +} + +static void on_timeout(htimer_t* timer) { + (void)timer; + assert(s_accept_count == 1); + assert(s_connect_count == 1); + assert(s_read_count == 1); + hloop_stop(s_loop); +} + +static void on_read(hio_t* io, void* buf, int readbytes) { + assert(readbytes == (int)sizeof(s_preconnect_message) - 1); + assert(memcmp(buf, s_preconnect_message, readbytes) == 0); + ++s_read_count; + hio_close(io); + finish_if_ready(); +} + +static void on_accept(hio_t* io) { + assert(io->phase == HIO_PHASE_ESTABLISHED); + ++s_accept_count; + hio_setcb_read(io, on_read); + hio_read(io); + finish_if_ready(); +} + +static void on_connect(hio_t* io) { + assert(io->phase == HIO_PHASE_ESTABLISHED); + ++s_connect_count; + finish_if_ready(); +} + +int main() { + s_loop = hloop_new(0); + assert(s_loop != NULL); + + s_listener = hio_create_socket(s_loop, "127.0.0.1", 0, HIO_TYPE_TCP, HIO_SERVER_SIDE); + assert(s_listener != NULL); + assert(s_listener->phase == HIO_PHASE_READY); + hio_setcb_accept(s_listener, on_accept); + assert(hio_accept(s_listener) == 0); + assert(s_listener->phase == HIO_PHASE_ACCEPTING); + hio_cb dispatcher = (hio_cb)s_listener->cb; + assert(dispatcher != NULL); + assert(hio_del(s_listener, HV_READ) == 0); + assert(hio_add(s_listener, NULL, HV_READ) == 0); + assert((hio_cb)s_listener->cb == dispatcher); + + sockaddr_u* addr = (sockaddr_u*)hio_localaddr(s_listener); + int port = sockaddr_port(addr); + assert(port > 0); + + s_client = hio_create_socket(s_loop, "127.0.0.1", port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + assert(s_client != NULL); + assert(s_client->phase == HIO_PHASE_READY); + hio_setcb_connect(s_client, on_connect); + assert(hio_connect(s_client) == 0); + assert(s_client->phase == HIO_PHASE_CONNECTING); + assert(hio_write(s_client, s_preconnect_message, sizeof(s_preconnect_message) - 1) == 0); + assert(hio_write_bufsize(s_client) == sizeof(s_preconnect_message) - 1); + + htimer_add(s_loop, on_timeout, 1000, 1); + assert(hloop_run(s_loop) == 0); + assert(s_accept_count == 1); + assert(s_connect_count == 1); + assert(s_read_count == 1); + assert(hio_write_is_complete(s_client)); + hio_close(s_client); + hio_close(s_listener); + assert(s_client->phase == HIO_PHASE_CLOSED); + assert(s_listener->phase == HIO_PHASE_CLOSED); + hloop_free(&s_loop); + printf("hio_phase_test passed\n"); + return 0; +} + +#endif From a679ee9ca58b0eea4f1eb9fee68f6cab287eceb7 Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 15:58:53 +0800 Subject: [PATCH 2/9] refactor(event): drive proxy completion by phase Co-authored-by: TRAE CLI --- event/hevent.h | 1 + event/nio.c | 8 +++++++- event/proxy.c | 10 ++++------ event/proxy.h | 5 +---- unittest/hio_phase_test.c | 23 +++++++++++++++++++++++ 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/event/hevent.h b/event/hevent.h index 94829d5b5..7e60d5c2b 100644 --- a/event/hevent.h +++ b/event/hevent.h @@ -31,6 +31,7 @@ typedef enum { HIO_PHASE_ACCEPTING, HIO_PHASE_CONNECTING, HIO_PHASE_PROXY_HANDSHAKING, + HIO_PHASE_PROXY_ESTABLISHED, HIO_PHASE_TLS_SERVER_HANDSHAKING, HIO_PHASE_TLS_CLIENT_HANDSHAKING, HIO_PHASE_ESTABLISHED, diff --git a/event/nio.c b/event/nio.c index 1a5cb50da..5a7759e1f 100644 --- a/event/nio.c +++ b/event/nio.c @@ -71,6 +71,7 @@ static void hio_handle_events(hio_t* io); static bool nio_write_deferred(hio_t* io) { return io->phase == HIO_PHASE_CONNECTING || io->phase == HIO_PHASE_PROXY_HANDSHAKING || + io->phase == HIO_PHASE_PROXY_ESTABLISHED || io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING || io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; } @@ -263,7 +264,7 @@ static void nio_connect(hio_t* io) { // Proxy: the TCP connection is to the proxy; run its protocol handshake // before TLS or the user connect callback. if (io->proxy) { - proxy_handshake_start(io, nio_connect_established); + proxy_handshake_start(io); return; } @@ -468,6 +469,11 @@ static void hio_handle_events(hio_t* io) { if ((io->events & HV_READ) && (io->revents & HV_READ)) { if (io->phase == HIO_PHASE_PROXY_HANDSHAKING) { proxy_handshake_read(io); + if (io->phase == HIO_PHASE_PROXY_ESTABLISHED) { + io->revents = 0; + nio_connect_established(io); + return; + } } else if (io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { diff --git a/event/proxy.c b/event/proxy.c index 19421dce9..ee42a39f8 100644 --- a/event/proxy.c +++ b/event/proxy.c @@ -126,12 +126,11 @@ int proxy_handshake_send(hio_t* io, const void* buf, int len) { void proxy_handshake_established(hio_t* io) { proxy_ctx_t* proxy = io->proxy; hio_del(io, HV_READ); - if (proxy == NULL || proxy->on_established == NULL) { + if (proxy == NULL) { proxy_handshake_fail(io); return; } - io->phase = HIO_PHASE_CONNECTING; - proxy->on_established(io); + io->phase = HIO_PHASE_PROXY_ESTABLISHED; } static void http_connect_client_handshake(hio_t* io) { @@ -211,9 +210,9 @@ void proxy_handshake_read(hio_t* io) { } } -void proxy_handshake_start(hio_t* io, proxy_established_cb on_established) { +void proxy_handshake_start(hio_t* io) { proxy_ctx_t* proxy = io->proxy; - if (proxy == NULL || on_established == NULL) { + if (proxy == NULL) { proxy_handshake_fail(io); return; } @@ -221,7 +220,6 @@ void proxy_handshake_start(hio_t* io, proxy_established_cb on_established) { if (io->events & HV_WRITE) { hio_del(io, HV_WRITE); } - proxy->on_established = on_established; switch (proxy->setting.protocol) { case PROXY_PROTOCOL_SOCKS5: socks5_client_handshake_start(io); diff --git a/event/proxy.h b/event/proxy.h index f734b74f4..f22437820 100644 --- a/event/proxy.h +++ b/event/proxy.h @@ -3,8 +3,6 @@ #include "hloop.h" -typedef void (*proxy_established_cb)(hio_t* io); - // Internal context held by hio_t::proxy. ctx is available to either client or // server proxy implementations; ctx_free, when set, owns its cleanup. typedef struct proxy_ctx_s { @@ -15,7 +13,6 @@ typedef struct proxy_ctx_s { unsigned char rbuf[1024]; int rlen; int want; - proxy_established_cb on_established; } proxy_ctx_t; proxy_ctx_t* proxy_ctx_new(const proxy_setting_t* setting); @@ -27,7 +24,7 @@ bool proxy_setting_valid(const proxy_setting_t* setting, bool need_target); // written, or a negative value when the buffer is insufficient. int http_connect_build_request(const proxy_ctx_t* proxy, char* buf, int bufsize); -void proxy_handshake_start(hio_t* io, proxy_established_cb on_established); +void proxy_handshake_start(hio_t* io); void proxy_handshake_read(hio_t* io); void proxy_handshake_fail(hio_t* io); int proxy_handshake_send(hio_t* io, const void* buf, int len); diff --git a/unittest/hio_phase_test.c b/unittest/hio_phase_test.c index 5f9742838..779c7eb14 100644 --- a/unittest/hio_phase_test.c +++ b/unittest/hio_phase_test.c @@ -4,6 +4,7 @@ #include "hevent.h" #include "hsocket.h" +#include "proxy.h" #ifdef EVENT_IOCP int main() { @@ -20,6 +21,26 @@ static hio_t* s_listener = NULL; static hio_t* s_client = NULL; static const char s_preconnect_message[] = "queued before connect"; +static void test_proxy_established_transition() { + hloop_t* loop = hloop_new(0); + assert(loop != NULL); + hio_t* io = hio_create_socket(loop, "127.0.0.1", 1, HIO_TYPE_TCP, HIO_CLIENT_SIDE); + assert(io != NULL); + + proxy_setting_t setting; + memset(&setting, 0, sizeof(setting)); + setting.protocol = PROXY_PROTOCOL_SOCKS5; + assert(hio_set_proxy(io, &setting) == 0); + io->phase = HIO_PHASE_PROXY_HANDSHAKING; + + proxy_handshake_established(io); + assert(io->phase == HIO_PHASE_PROXY_ESTABLISHED); + assert(!io->closed); + + hio_close(io); + hloop_free(&loop); +} + static void finish_if_ready() { if (s_accept_count != 1 || s_connect_count != 1 || s_read_count != 1) return; hloop_stop(s_loop); @@ -56,6 +77,8 @@ static void on_connect(hio_t* io) { } int main() { + test_proxy_established_transition(); + s_loop = hloop_new(0); assert(s_loop != NULL); From 4aaadd58dab76a84cd8a27857ee0b11e65e57206 Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 16:27:29 +0800 Subject: [PATCH 3/9] refactor(event): extract TLS handshake state Co-authored-by: TRAE CLI --- event/README.md | 1 + event/hevent.h | 1 + event/nio.c | 162 ++++++++++---------------------------- event/tls.c | 104 ++++++++++++++++++++++++ event/tls.h | 14 ++++ unittest/hio_phase_test.c | 74 +++++++++++++++++ 6 files changed, 235 insertions(+), 121 deletions(-) create mode 100644 event/tls.c create mode 100644 event/tls.h diff --git a/event/README.md b/event/README.md index 87782e897..59a504295 100644 --- a/event/README.md +++ b/event/README.md @@ -9,6 +9,7 @@ ├── unpack.h 拆包 ├── rudp.h 可靠UDP ├── proxy.c 代理 +├── tls.c TLS握手状态机 ├── socks5.c SOCKS5代理 ├── iowatcher.h IO多路复用统一抽象接口 ├── select.c EVENT_SELECT实现 diff --git a/event/hevent.h b/event/hevent.h index 7e60d5c2b..1792e74b5 100644 --- a/event/hevent.h +++ b/event/hevent.h @@ -34,6 +34,7 @@ typedef enum { HIO_PHASE_PROXY_ESTABLISHED, HIO_PHASE_TLS_SERVER_HANDSHAKING, HIO_PHASE_TLS_CLIENT_HANDSHAKING, + HIO_PHASE_TLS_ESTABLISHED, HIO_PHASE_ESTABLISHED, HIO_PHASE_CLOSED, } hio_phase_e; diff --git a/event/nio.c b/event/nio.c index 5a7759e1f..730492d79 100644 --- a/event/nio.c +++ b/event/nio.c @@ -7,6 +7,7 @@ #include "herr.h" #include "hthread.h" #include "proxy.h" +#include "tls.h" static void __connect_timeout_cb(htimer_t* timer) { hio_t* io = (hio_t*)timer->privdata; @@ -73,7 +74,8 @@ static bool nio_write_deferred(hio_t* io) { io->phase == HIO_PHASE_PROXY_HANDSHAKING || io->phase == HIO_PHASE_PROXY_ESTABLISHED || io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING || - io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; + io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || + io->phase == HIO_PHASE_TLS_ESTABLISHED; } static void nio_flush_deferred_writes(hio_t* io) { @@ -91,42 +93,9 @@ static void nio_connect_ready(hio_t* io) { nio_flush_deferred_writes(io); } -static void nio_tls_handshake(hio_t* io) { - bool server = io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; - printd("ssl %s handshake...\n", server ? "server" : "client"); - int ret = server ? hssl_accept(io->ssl) : hssl_connect(io->ssl); - if (ret == 0) { - // handshake finish - hio_del(io, HV_RDWR); - printd("ssl handshake finished.\n"); - io->phase = HIO_PHASE_ESTABLISHED; - if (server) { - __accept_cb(io); - } else { - nio_connect_ready(io); - } - } - else if (ret == HSSL_WANT_READ) { - if (io->events & HV_WRITE) { - hio_del(io, HV_WRITE); - } - if ((io->events & HV_READ) == 0) { - hio_add(io, hio_handle_events, HV_READ); - } - } - else if (ret == HSSL_WANT_WRITE) { - if (io->events & HV_READ) { - hio_del(io, HV_READ); - } - if ((io->events & HV_WRITE) == 0) { - hio_add(io, hio_handle_events, HV_WRITE); - } - } - else { - hloge("ssl %s handshake failed: %d", server ? "server" : "client", ret); - io->error = ERR_SSL_HANDSHAKE; - hio_close(io); - } +static void nio_accept_ready(hio_t* io) { + io->phase = HIO_PHASE_ESTABLISHED; + __accept_cb(io); } static void nio_accept(hio_t* io) { @@ -159,36 +128,15 @@ static void nio_accept(hio_t* io) { } if (io->io_type == HIO_TYPE_SSL) { - if (connio->ssl == NULL) { - // io->ssl_ctx > g_ssl_ctx > hssl_ctx_new - hssl_ctx_t ssl_ctx = NULL; - if (io->ssl_ctx) { - ssl_ctx = io->ssl_ctx; - } else if (g_ssl_ctx) { - ssl_ctx = g_ssl_ctx; - } else { - io->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); - io->alloced_ssl_ctx = 1; - } - if (ssl_ctx == NULL) { - io->error = ERR_NEW_SSL_CTX; - goto accept_error; - } - hssl_t ssl = hssl_new(ssl_ctx, connfd); - if (ssl == NULL) { - io->error = ERR_NEW_SSL; - goto accept_error; - } - connio->ssl = ssl; + hio_add(connio, hio_handle_events, HV_READ); + if (tls_server_handshake_start(io, connio) == 0 && + connio->phase == HIO_PHASE_TLS_ESTABLISHED) { + nio_accept_ready(connio); } - hio_enable_ssl(connio); - connio->phase = HIO_PHASE_TLS_SERVER_HANDSHAKING; - nio_tls_handshake(connio); } else { // NOTE: SSL call accept_cb after handshake finished - connio->phase = HIO_PHASE_ESTABLISHED; - __accept_cb(connio); + nio_accept_ready(connio); } } return; @@ -203,45 +151,10 @@ static void nio_accept(hio_t* io) { // handshake completed), start the SSL handshake or deliver connect_cb. static void nio_connect_established(hio_t* io) { if (io->io_type == HIO_TYPE_SSL) { - if (io->ssl == NULL) { - // io->ssl_ctx > g_ssl_ctx > hssl_ctx_new - hssl_ctx_t ssl_ctx = NULL; - if (io->ssl_ctx) { - ssl_ctx = io->ssl_ctx; - } else if (g_ssl_ctx) { - ssl_ctx = g_ssl_ctx; - } else { - io->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); - io->alloced_ssl_ctx = 1; - } - if (ssl_ctx == NULL) { - io->error = ERR_NEW_SSL_CTX; - hio_close(io); - return; - } - hssl_t ssl = hssl_new(ssl_ctx, io->fd); - if (ssl == NULL) { - io->error = ERR_NEW_SSL; - hio_close(io); - return; - } - io->ssl = ssl; - } - // SNI: through a proxy the TLS peer is the target, so the proxy's - // target_host is authoritative; otherwise use the explicitly-set - // io->hostname. SNI must be a hostname, not an IP literal (RFC 6066), - // so a numeric candidate is skipped and the next one is considered. - const char* sni = NULL; - if (io->proxy && io->proxy->setting.target_host[0] && !is_ipaddr(io->proxy->setting.target_host)) { - sni = io->proxy->setting.target_host; - } else if (io->hostname && !is_ipaddr(io->hostname)) { - sni = io->hostname; - } - if (sni) { - hssl_set_sni_hostname(io->ssl, sni); + if (tls_client_handshake_start(io) == 0 && + io->phase == HIO_PHASE_TLS_ESTABLISHED) { + nio_connect_ready(io); } - io->phase = HIO_PHASE_TLS_CLIENT_HANDSHAKING; - nio_tls_handshake(io); } else { // NOTE: SSL call connect_cb after handshake finished @@ -466,20 +379,34 @@ static void nio_write(hio_t* io) { } static void hio_handle_events(hio_t* io) { - if ((io->events & HV_READ) && (io->revents & HV_READ)) { - if (io->phase == HIO_PHASE_PROXY_HANDSHAKING) { + hio_phase_e phase = io->phase; + if (phase == HIO_PHASE_PROXY_HANDSHAKING) { + int revents = io->revents; + io->revents = 0; + if (revents & HV_READ) { proxy_handshake_read(io); - if (io->phase == HIO_PHASE_PROXY_ESTABLISHED) { - io->revents = 0; - nio_connect_established(io); - return; - } } - else if (io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || - io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { - nio_tls_handshake(io); + if (io->phase == HIO_PHASE_PROXY_ESTABLISHED) { + nio_connect_established(io); + } + return; + } + if (phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || + phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { + io->revents = 0; + tls_handshake_step(io); + if (io->phase == HIO_PHASE_TLS_ESTABLISHED) { + if (phase == HIO_PHASE_TLS_SERVER_HANDSHAKING) { + nio_accept_ready(io); + } else { + nio_connect_ready(io); + } } - else if (io->accept) { + return; + } + + if ((io->events & HV_READ) && (io->revents & HV_READ)) { + if (io->accept) { nio_accept(io); } else { @@ -494,15 +421,7 @@ static void hio_handle_events(hio_t* io) { hio_del(io, HV_WRITE); } hrecursive_mutex_unlock(&io->write_mutex); - if (io->phase == HIO_PHASE_PROXY_HANDSHAKING) { - // Proxy negotiation only consumes readable events. Application - // writes remain queued until the connection becomes established. - } - else if (io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || - io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING) { - nio_tls_handshake(io); - } - else if (io->connect) { + if (io->connect) { // NOTE: connect just do once // ONESHOT io->connect = 0; @@ -525,6 +444,7 @@ int hio_accept(hio_t* io) { int hio_connect(hio_t* io) { io->phase = HIO_PHASE_CONNECTING; + io->cb = (hevent_cb)hio_handle_events; int ret = connect(io->fd, io->peeraddr, SOCKADDR_LEN(io->peeraddr)); #ifdef OS_WIN if (ret < 0 && socket_errno() != WSAEWOULDBLOCK) { diff --git a/event/tls.c b/event/tls.c new file mode 100644 index 000000000..23fba18f8 --- /dev/null +++ b/event/tls.c @@ -0,0 +1,104 @@ +#include "tls.h" + +#include "hevent.h" +#include "herr.h" +#include "hlog.h" +#include "hsocket.h" +#include "proxy.h" + +static int tls_prepare(hio_t* io, hssl_ctx_t ssl_ctx) { + if (io->ssl == NULL) { + if (ssl_ctx == NULL) { + io->error = ERR_NEW_SSL_CTX; + hio_close(io); + return -1; + } + io->ssl = hssl_new(ssl_ctx, io->fd); + if (io->ssl == NULL) { + io->error = ERR_NEW_SSL; + hio_close(io); + return -1; + } + } + hio_enable_ssl(io); + return 0; +} + +static hssl_ctx_t tls_client_ctx(hio_t* io) { + if (io->ssl_ctx) return io->ssl_ctx; + if (g_ssl_ctx) return g_ssl_ctx; + io->ssl_ctx = hssl_ctx_new(NULL); + if (io->ssl_ctx) io->alloced_ssl_ctx = 1; + return io->ssl_ctx; +} + +int tls_server_handshake_start(hio_t* listenio, hio_t* connio) { + hssl_ctx_t ssl_ctx = NULL; + if (connio->ssl == NULL) { + ssl_ctx = listenio->ssl_ctx; + if (ssl_ctx == NULL) { + ssl_ctx = g_ssl_ctx; + } + if (ssl_ctx == NULL) { + listenio->ssl_ctx = ssl_ctx = hssl_ctx_new(NULL); + if (ssl_ctx) listenio->alloced_ssl_ctx = 1; + } + } + if (tls_prepare(connio, ssl_ctx) != 0) return -1; + connio->phase = HIO_PHASE_TLS_SERVER_HANDSHAKING; + tls_handshake_step(connio); + return connio->closed ? -1 : 0; +} + +int tls_client_handshake_start(hio_t* io) { + hssl_ctx_t ssl_ctx = io->ssl == NULL ? tls_client_ctx(io) : NULL; + if (tls_prepare(io, ssl_ctx) != 0) return -1; + + const char* sni = NULL; + if (io->proxy && io->proxy->setting.target_host[0] && !is_ipaddr(io->proxy->setting.target_host)) { + sni = io->proxy->setting.target_host; + } else if (io->hostname && !is_ipaddr(io->hostname)) { + sni = io->hostname; + } + if (sni) { + hssl_set_sni_hostname(io->ssl, sni); + } + + io->phase = HIO_PHASE_TLS_CLIENT_HANDSHAKING; + tls_handshake_step(io); + return io->closed ? -1 : 0; +} + +void tls_handshake_step(hio_t* io) { + bool server = io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING; + printd("tls %s handshake...\n", server ? "server" : "client"); + int ret = server ? hssl_accept(io->ssl) : hssl_connect(io->ssl); + if (ret == HSSL_OK) { + hio_del(io, HV_RDWR); + io->phase = HIO_PHASE_TLS_ESTABLISHED; + printd("tls handshake finished.\n"); + return; + } + if (ret == HSSL_WANT_READ) { + if (io->events & HV_WRITE) { + hio_del(io, HV_WRITE); + } + if ((io->events & HV_READ) == 0) { + hio_add(io, NULL, HV_READ); + } + return; + } + if (ret == HSSL_WANT_WRITE) { + if (io->events & HV_READ) { + hio_del(io, HV_READ); + } + if ((io->events & HV_WRITE) == 0) { + hio_add(io, NULL, HV_WRITE); + } + return; + } + + hloge("tls %s handshake failed: %d", server ? "server" : "client", ret); + io->error = ERR_SSL_HANDSHAKE; + hio_close(io); +} diff --git a/event/tls.h b/event/tls.h new file mode 100644 index 000000000..3a63ce2e3 --- /dev/null +++ b/event/tls.h @@ -0,0 +1,14 @@ +#ifndef HV_EVENT_TLS_H_ +#define HV_EVENT_TLS_H_ + +#include "hloop.h" + +// Internal event-loop TLS handshake helpers. The caller installs the NIO +// readiness dispatcher before starting a handshake. These functions only +// manage the TLS transport phase and event mask; nio.c owns accept/connect +// completion and user callbacks. +int tls_server_handshake_start(hio_t* listenio, hio_t* connio); +int tls_client_handshake_start(hio_t* io); +void tls_handshake_step(hio_t* io); + +#endif // HV_EVENT_TLS_H_ diff --git a/unittest/hio_phase_test.c b/unittest/hio_phase_test.c index 779c7eb14..ed1d6f664 100644 --- a/unittest/hio_phase_test.c +++ b/unittest/hio_phase_test.c @@ -5,6 +5,7 @@ #include "hevent.h" #include "hsocket.h" #include "proxy.h" +#include "tls.h" #ifdef EVENT_IOCP int main() { @@ -41,6 +42,74 @@ static void test_proxy_established_transition() { hloop_free(&loop); } +static void test_tls_phase_contract() { + void (*handshake_step)(hio_t*) = tls_handshake_step; + assert(handshake_step != NULL); + assert(HIO_PHASE_TLS_ESTABLISHED != HIO_PHASE_ESTABLISHED); +} + +#ifdef WITH_OPENSSL +static hloop_t* s_tls_loop = NULL; +static hio_t* s_tls_listener = NULL; +static hio_t* s_tls_client = NULL; +static int s_tls_accept_count = 0; +static int s_tls_connect_count = 0; + +static void finish_tls_if_ready() { + if (s_tls_accept_count != 1 || s_tls_connect_count != 1) return; + hloop_stop(s_tls_loop); +} + +static void on_tls_timeout(htimer_t* timer) { + (void)timer; + assert(s_tls_accept_count == 1); + assert(s_tls_connect_count == 1); + hloop_stop(s_tls_loop); +} + +static void on_tls_accept(hio_t* io) { + assert(io->phase == HIO_PHASE_ESTABLISHED); + ++s_tls_accept_count; + hio_close(io); + finish_tls_if_ready(); +} + +static void on_tls_connect(hio_t* io) { + assert(io->phase == HIO_PHASE_ESTABLISHED); + ++s_tls_connect_count; + finish_tls_if_ready(); +} + +static void test_tls_handshake_lifecycle() { + s_tls_loop = hloop_new(0); + assert(s_tls_loop != NULL); + s_tls_listener = hloop_create_ssl_server(s_tls_loop, "127.0.0.1", 0, on_tls_accept); + assert(s_tls_listener != NULL); + + hssl_ctx_opt_t server_opt; + memset(&server_opt, 0, sizeof(server_opt)); + server_opt.crt_file = "cert/server.crt"; + server_opt.key_file = "cert/server.key"; + server_opt.endpoint = HSSL_SERVER; + assert(hio_new_ssl_ctx(s_tls_listener, &server_opt) == 0); + + int port = sockaddr_port((sockaddr_u*)hio_localaddr(s_tls_listener)); + assert(port > 0); + s_tls_client = hio_create_socket(s_tls_loop, "127.0.0.1", port, HIO_TYPE_SSL, HIO_CLIENT_SIDE); + assert(s_tls_client != NULL); + hio_setcb_connect(s_tls_client, on_tls_connect); + assert(hio_connect(s_tls_client) == 0); + + htimer_add(s_tls_loop, on_tls_timeout, 3000, 1); + assert(hloop_run(s_tls_loop) == 0); + assert(s_tls_accept_count == 1); + assert(s_tls_connect_count == 1); + hio_close(s_tls_client); + hio_close(s_tls_listener); + hloop_free(&s_tls_loop); +} +#endif + static void finish_if_ready() { if (s_accept_count != 1 || s_connect_count != 1 || s_read_count != 1) return; hloop_stop(s_loop); @@ -78,6 +147,10 @@ static void on_connect(hio_t* io) { int main() { test_proxy_established_transition(); + test_tls_phase_contract(); +#ifdef WITH_OPENSSL + test_tls_handshake_lifecycle(); +#endif s_loop = hloop_new(0); assert(s_loop != NULL); @@ -104,6 +177,7 @@ int main() { hio_setcb_connect(s_client, on_connect); assert(hio_connect(s_client) == 0); assert(s_client->phase == HIO_PHASE_CONNECTING); + assert((hio_cb)s_client->cb != NULL); assert(hio_write(s_client, s_preconnect_message, sizeof(s_preconnect_message) - 1) == 0); assert(hio_write_bufsize(s_client) == sizeof(s_preconnect_message) - 1); From f6106371ac1e167fceae45b777f1c36bd6471a87 Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 16:57:54 +0800 Subject: [PATCH 4/9] refactor(event): clarify establishing write flow Co-authored-by: TRAE CLI --- event/nio.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/event/nio.c b/event/nio.c index 730492d79..113b4a21e 100644 --- a/event/nio.c +++ b/event/nio.c @@ -69,16 +69,12 @@ static void __close_cb(hio_t* io) { static void hio_handle_events(hio_t* io); -static bool nio_write_deferred(hio_t* io) { - return io->phase == HIO_PHASE_CONNECTING || - io->phase == HIO_PHASE_PROXY_HANDSHAKING || - io->phase == HIO_PHASE_PROXY_ESTABLISHED || - io->phase == HIO_PHASE_TLS_CLIENT_HANDSHAKING || - io->phase == HIO_PHASE_TLS_SERVER_HANDSHAKING || - io->phase == HIO_PHASE_TLS_ESTABLISHED; +static bool nio_is_establishing(hio_t* io) { + return io->phase >= HIO_PHASE_CONNECTING && + io->phase < HIO_PHASE_ESTABLISHED; } -static void nio_flush_deferred_writes(hio_t* io) { +static void nio_flush_write_queue(hio_t* io) { hrecursive_mutex_lock(&io->write_mutex); bool pending = !write_queue_empty(&io->write_queue); hrecursive_mutex_unlock(&io->write_mutex); @@ -90,7 +86,7 @@ static void nio_flush_deferred_writes(hio_t* io) { static void nio_connect_ready(hio_t* io) { io->phase = HIO_PHASE_ESTABLISHED; __connect_cb(io); - nio_flush_deferred_writes(io); + nio_flush_write_queue(io); } static void nio_accept_ready(hio_t* io) { @@ -497,7 +493,7 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* } #endif if (write_queue_empty(&io->write_queue)) { - if (nio_write_deferred(io)) { + if (nio_is_establishing(io)) { nwrite = 0; goto enqueue; } @@ -523,7 +519,7 @@ static int hio_write4 (hio_t* io, const void* buf, size_t len, struct sockaddr* goto disconnect; } enqueue: - if (!nio_write_deferred(io)) { + if (!nio_is_establishing(io)) { hio_add(io, hio_handle_events, HV_WRITE); } } From f45309ea749a289ee52b2028ab833ca9b8ff3063 Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 17:15:47 +0800 Subject: [PATCH 5/9] refactor(event): add initial io phase Co-authored-by: TRAE CLI --- event/hevent.c | 1 + event/hevent.h | 3 ++- unittest/hio_phase_test.c | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/event/hevent.c b/event/hevent.c index c500cfb2d..fdc8c5fee 100644 --- a/event/hevent.c +++ b/event/hevent.c @@ -65,6 +65,7 @@ static void hio_socket_init(hio_t* io) { } void hio_init(hio_t* io) { + io->phase = HIO_PHASE_NONE; // alloc localaddr,peeraddr when hio_socket_init /* if (io->localaddr == NULL) { diff --git a/event/hevent.h b/event/hevent.h index 1792e74b5..2ef1ae715 100644 --- a/event/hevent.h +++ b/event/hevent.h @@ -27,7 +27,8 @@ // Internal NIO transport lifecycle. hio_handle_events is the single readiness // dispatcher; phase selects its current transport or handshake action. typedef enum { - HIO_PHASE_READY = 0, + HIO_PHASE_NONE = 0, + HIO_PHASE_READY, HIO_PHASE_ACCEPTING, HIO_PHASE_CONNECTING, HIO_PHASE_PROXY_HANDSHAKING, diff --git a/unittest/hio_phase_test.c b/unittest/hio_phase_test.c index ed1d6f664..a00df7f54 100644 --- a/unittest/hio_phase_test.c +++ b/unittest/hio_phase_test.c @@ -22,6 +22,15 @@ static hio_t* s_listener = NULL; static hio_t* s_client = NULL; static const char s_preconnect_message[] = "queued before connect"; +static void test_hio_init_phase() { + hio_t io; + memset(&io, 0, sizeof(io)); + io.phase = HIO_PHASE_CLOSED; + hio_init(&io); + assert(io.phase == HIO_PHASE_NONE); + hrecursive_mutex_destroy(&io.write_mutex); +} + static void test_proxy_established_transition() { hloop_t* loop = hloop_new(0); assert(loop != NULL); @@ -146,6 +155,7 @@ static void on_connect(hio_t* io) { } int main() { + test_hio_init_phase(); test_proxy_established_transition(); test_tls_phase_contract(); #ifdef WITH_OPENSSL From 082feb60b2f1c146b095abac0ed772464fbf8dd2 Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 17:26:55 +0800 Subject: [PATCH 6/9] refactor(event): reuse event activation helper Co-authored-by: TRAE CLI --- event/hloop.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/event/hloop.c b/event/hloop.c index 6137592db..4a0acd814 100644 --- a/event/hloop.c +++ b/event/hloop.c @@ -900,11 +900,10 @@ int hio_add(hio_t* io, hio_cb cb, int events) { if (!io->active) { // A NULL callback only changes the watched mask. Keep the transport // dispatcher installed by the initial hio_add call. - hio_cb handler = cb ? cb : (hio_cb)io->cb; - io->loop = loop; - io->event_id = hloop_next_event_id(); - io->cb = (hevent_cb)handler; - EVENT_ACTIVE(io); + if (cb == NULL) { + cb = (hio_cb)io->cb; + } + EVENT_ADD(loop, io, cb); loop->nios++; } return 0; From fb8cd09982a43d4c738e6bac42ac8e5afa1c2dfc Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 19:26:17 +0800 Subject: [PATCH 7/9] refactor(event): address phase review feedback Co-authored-by: TRAE CLI --- Makefile | 1 - docs/cn/hloop.md | 4 +- event/README.md | 2 +- event/hloop.h | 5 +- event/proxy.c | 4 +- event/proxy.h | 2 +- event/socks5.c | 6 +- scripts/unittest.sh | 3 - unittest/CMakeLists.txt | 5 - unittest/hio_phase_test.c | 209 -------------------------------------- 10 files changed, 11 insertions(+), 230 deletions(-) delete mode 100644 unittest/hio_phase_test.c diff --git a/Makefile b/Makefile index 15623635d..2b7f50464 100644 --- a/Makefile +++ b/Makefile @@ -411,7 +411,6 @@ unittest: prepare libhv $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Iprotocol -Iutil -o bin/sendmail unittest/sendmail_test.c protocol/smtp.c base/hsocket.c base/htime.c util/base64.c $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hdns_test unittest/hdns_test.c -Llib -lhv -pthread $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hdns_benchmark unittest/hdns_benchmark.c -Llib -lhv -pthread - $(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hio_phase_test unittest/hio_phase_test.c -Llib -lhv -pthread ifeq ($(WITH_EVPP), yes) $(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Icpputil -Ievpp -o bin/tcpclient_dns_test unittest/tcpclient_dns_test.cpp -Llib -lhv -pthread endif diff --git a/docs/cn/hloop.md b/docs/cn/hloop.md index f799f3e9e..8b6be87bc 100644 --- a/docs/cn/hloop.md +++ b/docs/cn/hloop.md @@ -313,7 +313,7 @@ int hio_accept (hio_t* io); // 连接 // connect => hio_add(io, HV_WRITE) => hconnect_cb -// 在非IOCP的NIO后端,connect_cb表示应用层连接可用;配置代理或TLS时会在相应握手全部成功后才调用。 +// connect_cb表示应用层连接可用;配置代理或TLS时会在相应握手全部成功后才调用。 int hio_connect(hio_t* io); // 读 @@ -651,7 +651,7 @@ int hio_set_kcp(hio_t* io, kcp_setting_t* setting DEFAULT(NULL)); ### IO回调语义与缓冲区生命周期 -- 在非IOCP的NIO后端,`connect_cb` 表示应用层连接已可用:若设置了代理或TLS,代理握手和TLS握手均已成功;它并不只是TCP三次握手完成。 +- `connect_cb` 表示应用层连接已可用:若设置了代理或TLS,代理握手和TLS握手均已成功;它并不只是TCP三次握手完成。 - `write_cb` 的 `writebytes` 表示本次实际写出的字节数,不表示一条业务消息已经全部发送;需要通过 `hio_write_is_complete(io)` 判断写队列是否排空。 - `read_cb` 和 `write_cb` 都可能同步触发:例如 `hio_read` 处理已有读缓存,或 `hio_write` 立即写入成功时。因此回调代码必须能处理可重入调用。 - `close_cb` 在一次 `hio_t` 生命周期内最多触发一次,是释放连接关联上下文的合适位置。 diff --git a/event/README.md b/event/README.md index 59a504295..2bf978048 100644 --- a/event/README.md +++ b/event/README.md @@ -9,7 +9,7 @@ ├── unpack.h 拆包 ├── rudp.h 可靠UDP ├── proxy.c 代理 -├── tls.c TLS握手状态机 +├── tls.c TLS握手 ├── socks5.c SOCKS5代理 ├── iowatcher.h IO多路复用统一抽象接口 ├── select.c EVENT_SELECT实现 diff --git a/event/hloop.h b/event/hloop.h index ca080062f..8855b93c1 100644 --- a/event/hloop.h +++ b/event/hloop.h @@ -431,9 +431,8 @@ HV_EXPORT void hio_set_heartbeat(hio_t* io, int interval_ms, hio_send_heartbeat_ HV_EXPORT int hio_accept (hio_t* io); // connect => hio_add(io, HV_WRITE) => hconnect_cb -// On non-IOCP NIO backends, hconnect_cb means the application connection is -// ready: any configured proxy and TLS handshakes have completed. It is not -// merely TCP connect completion. +// hconnect_cb means the application connection is ready: any configured proxy +// and TLS handshakes have completed. It is not merely TCP connect completion. HV_EXPORT int hio_connect(hio_t* io); // hio_add(io, HV_READ) => read => hread_cb diff --git a/event/proxy.c b/event/proxy.c index ee42a39f8..2e3bf0501 100644 --- a/event/proxy.c +++ b/event/proxy.c @@ -115,7 +115,7 @@ void proxy_handshake_fail(hio_t* io) { hio_close(io); } -int proxy_handshake_send(hio_t* io, const void* buf, int len) { +int proxy_handshake_write(hio_t* io, const void* buf, int len) { int flag = 0; #ifdef MSG_NOSIGNAL flag |= MSG_NOSIGNAL; @@ -184,7 +184,7 @@ static void http_connect_client_handshake(hio_t* io) { static void http_connect_client_start(hio_t* io) { char buf[2048]; int n = http_connect_build_request(io->proxy, buf, (int)sizeof(buf)); - if (n < 0 || proxy_handshake_send(io, buf, n) != 0) { + if (n < 0 || proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } diff --git a/event/proxy.h b/event/proxy.h index f22437820..ce4e3b74f 100644 --- a/event/proxy.h +++ b/event/proxy.h @@ -27,7 +27,7 @@ int http_connect_build_request(const proxy_ctx_t* proxy, char* buf, int bufsize) void proxy_handshake_start(hio_t* io); void proxy_handshake_read(hio_t* io); void proxy_handshake_fail(hio_t* io); -int proxy_handshake_send(hio_t* io, const void* buf, int len); +int proxy_handshake_write(hio_t* io, const void* buf, int len); void proxy_handshake_established(hio_t* io); #endif // HV_PROXY_H_ diff --git a/event/socks5.c b/event/socks5.c index 960e74932..c0c403848 100644 --- a/event/socks5.c +++ b/event/socks5.c @@ -222,7 +222,7 @@ static void socks5_client_expect(hio_t* io, int state, int want) { static void socks5_client_send_connect(hio_t* io) { unsigned char buf[300]; int n = socks5_build_connect_request(io->proxy, buf); - if (n < 0 || proxy_handshake_send(io, buf, n) != 0) { + if (n < 0 || proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } @@ -241,7 +241,7 @@ static void socks5_client_dispatch(hio_t* io) { } else if (buf[1] == SOCKS5_AUTH_USERPASS && proxy->setting.username[0]) { unsigned char req[640]; int n = socks5_build_auth_request(proxy, req); - if (proxy_handshake_send(io, req, n) != 0) { proxy_handshake_fail(io); return; } + if (proxy_handshake_write(io, req, n) != 0) { proxy_handshake_fail(io); return; } socks5_client_expect(io, S5C_RECV_AUTH, 2); } else { proxy_handshake_fail(io); @@ -309,7 +309,7 @@ void socks5_client_handshake_read(hio_t* io) { void socks5_client_handshake_start(hio_t* io) { unsigned char buf[8]; int n = socks5_build_method_request(io->proxy, buf); - if (proxy_handshake_send(io, buf, n) != 0) { proxy_handshake_fail(io); return; } + if (proxy_handshake_write(io, buf, n) != 0) { proxy_handshake_fail(io); return; } socks5_client_expect(io, S5C_RECV_METHOD, 2); hio_add(io, NULL, HV_READ); } diff --git a/scripts/unittest.sh b/scripts/unittest.sh index 8c8aaef0c..5a4d05668 100755 --- a/scripts/unittest.sh +++ b/scripts/unittest.sh @@ -71,9 +71,6 @@ fi if [ -x bin/hdns_test ]; then bin/hdns_test fi -if [ -x bin/hio_phase_test ]; then - bin/hio_phase_test -fi if [ -x bin/tcpclient_dns_test ]; then bin/tcpclient_dns_test fi diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 7f75f6cfb..d0a1327fc 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -157,10 +157,6 @@ endif() endif() # ------event: async dns------ -add_executable(hio_phase_test hio_phase_test.c) -target_include_directories(hio_phase_test PRIVATE .. ../base ../ssl ../event) -target_link_libraries(hio_phase_test ${HV_LIBRARIES}) - add_executable(hdns_test hdns_test.c) target_include_directories(hdns_test PRIVATE .. ../base ../ssl ../event) target_link_libraries(hdns_test ${HV_LIBRARIES}) @@ -227,7 +223,6 @@ add_custom_target(unittest DEPENDS http_router_test ${HTTP_LUA_UNITTEST_TARGETS} ${HTTP_JS_UNITTEST_TARGETS} - hio_phase_test hdns_test hdns_benchmark ${REDIS_UNITTEST_TARGETS} diff --git a/unittest/hio_phase_test.c b/unittest/hio_phase_test.c deleted file mode 100644 index a00df7f54..000000000 --- a/unittest/hio_phase_test.c +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include -#include - -#include "hevent.h" -#include "hsocket.h" -#include "proxy.h" -#include "tls.h" - -#ifdef EVENT_IOCP -int main() { - printf("hio_phase_test skipped: NIO phase dispatch is not used by IOCP\n"); - return 0; -} -#else - -static int s_accept_count = 0; -static int s_connect_count = 0; -static int s_read_count = 0; -static hloop_t* s_loop = NULL; -static hio_t* s_listener = NULL; -static hio_t* s_client = NULL; -static const char s_preconnect_message[] = "queued before connect"; - -static void test_hio_init_phase() { - hio_t io; - memset(&io, 0, sizeof(io)); - io.phase = HIO_PHASE_CLOSED; - hio_init(&io); - assert(io.phase == HIO_PHASE_NONE); - hrecursive_mutex_destroy(&io.write_mutex); -} - -static void test_proxy_established_transition() { - hloop_t* loop = hloop_new(0); - assert(loop != NULL); - hio_t* io = hio_create_socket(loop, "127.0.0.1", 1, HIO_TYPE_TCP, HIO_CLIENT_SIDE); - assert(io != NULL); - - proxy_setting_t setting; - memset(&setting, 0, sizeof(setting)); - setting.protocol = PROXY_PROTOCOL_SOCKS5; - assert(hio_set_proxy(io, &setting) == 0); - io->phase = HIO_PHASE_PROXY_HANDSHAKING; - - proxy_handshake_established(io); - assert(io->phase == HIO_PHASE_PROXY_ESTABLISHED); - assert(!io->closed); - - hio_close(io); - hloop_free(&loop); -} - -static void test_tls_phase_contract() { - void (*handshake_step)(hio_t*) = tls_handshake_step; - assert(handshake_step != NULL); - assert(HIO_PHASE_TLS_ESTABLISHED != HIO_PHASE_ESTABLISHED); -} - -#ifdef WITH_OPENSSL -static hloop_t* s_tls_loop = NULL; -static hio_t* s_tls_listener = NULL; -static hio_t* s_tls_client = NULL; -static int s_tls_accept_count = 0; -static int s_tls_connect_count = 0; - -static void finish_tls_if_ready() { - if (s_tls_accept_count != 1 || s_tls_connect_count != 1) return; - hloop_stop(s_tls_loop); -} - -static void on_tls_timeout(htimer_t* timer) { - (void)timer; - assert(s_tls_accept_count == 1); - assert(s_tls_connect_count == 1); - hloop_stop(s_tls_loop); -} - -static void on_tls_accept(hio_t* io) { - assert(io->phase == HIO_PHASE_ESTABLISHED); - ++s_tls_accept_count; - hio_close(io); - finish_tls_if_ready(); -} - -static void on_tls_connect(hio_t* io) { - assert(io->phase == HIO_PHASE_ESTABLISHED); - ++s_tls_connect_count; - finish_tls_if_ready(); -} - -static void test_tls_handshake_lifecycle() { - s_tls_loop = hloop_new(0); - assert(s_tls_loop != NULL); - s_tls_listener = hloop_create_ssl_server(s_tls_loop, "127.0.0.1", 0, on_tls_accept); - assert(s_tls_listener != NULL); - - hssl_ctx_opt_t server_opt; - memset(&server_opt, 0, sizeof(server_opt)); - server_opt.crt_file = "cert/server.crt"; - server_opt.key_file = "cert/server.key"; - server_opt.endpoint = HSSL_SERVER; - assert(hio_new_ssl_ctx(s_tls_listener, &server_opt) == 0); - - int port = sockaddr_port((sockaddr_u*)hio_localaddr(s_tls_listener)); - assert(port > 0); - s_tls_client = hio_create_socket(s_tls_loop, "127.0.0.1", port, HIO_TYPE_SSL, HIO_CLIENT_SIDE); - assert(s_tls_client != NULL); - hio_setcb_connect(s_tls_client, on_tls_connect); - assert(hio_connect(s_tls_client) == 0); - - htimer_add(s_tls_loop, on_tls_timeout, 3000, 1); - assert(hloop_run(s_tls_loop) == 0); - assert(s_tls_accept_count == 1); - assert(s_tls_connect_count == 1); - hio_close(s_tls_client); - hio_close(s_tls_listener); - hloop_free(&s_tls_loop); -} -#endif - -static void finish_if_ready() { - if (s_accept_count != 1 || s_connect_count != 1 || s_read_count != 1) return; - hloop_stop(s_loop); -} - -static void on_timeout(htimer_t* timer) { - (void)timer; - assert(s_accept_count == 1); - assert(s_connect_count == 1); - assert(s_read_count == 1); - hloop_stop(s_loop); -} - -static void on_read(hio_t* io, void* buf, int readbytes) { - assert(readbytes == (int)sizeof(s_preconnect_message) - 1); - assert(memcmp(buf, s_preconnect_message, readbytes) == 0); - ++s_read_count; - hio_close(io); - finish_if_ready(); -} - -static void on_accept(hio_t* io) { - assert(io->phase == HIO_PHASE_ESTABLISHED); - ++s_accept_count; - hio_setcb_read(io, on_read); - hio_read(io); - finish_if_ready(); -} - -static void on_connect(hio_t* io) { - assert(io->phase == HIO_PHASE_ESTABLISHED); - ++s_connect_count; - finish_if_ready(); -} - -int main() { - test_hio_init_phase(); - test_proxy_established_transition(); - test_tls_phase_contract(); -#ifdef WITH_OPENSSL - test_tls_handshake_lifecycle(); -#endif - - s_loop = hloop_new(0); - assert(s_loop != NULL); - - s_listener = hio_create_socket(s_loop, "127.0.0.1", 0, HIO_TYPE_TCP, HIO_SERVER_SIDE); - assert(s_listener != NULL); - assert(s_listener->phase == HIO_PHASE_READY); - hio_setcb_accept(s_listener, on_accept); - assert(hio_accept(s_listener) == 0); - assert(s_listener->phase == HIO_PHASE_ACCEPTING); - hio_cb dispatcher = (hio_cb)s_listener->cb; - assert(dispatcher != NULL); - assert(hio_del(s_listener, HV_READ) == 0); - assert(hio_add(s_listener, NULL, HV_READ) == 0); - assert((hio_cb)s_listener->cb == dispatcher); - - sockaddr_u* addr = (sockaddr_u*)hio_localaddr(s_listener); - int port = sockaddr_port(addr); - assert(port > 0); - - s_client = hio_create_socket(s_loop, "127.0.0.1", port, HIO_TYPE_TCP, HIO_CLIENT_SIDE); - assert(s_client != NULL); - assert(s_client->phase == HIO_PHASE_READY); - hio_setcb_connect(s_client, on_connect); - assert(hio_connect(s_client) == 0); - assert(s_client->phase == HIO_PHASE_CONNECTING); - assert((hio_cb)s_client->cb != NULL); - assert(hio_write(s_client, s_preconnect_message, sizeof(s_preconnect_message) - 1) == 0); - assert(hio_write_bufsize(s_client) == sizeof(s_preconnect_message) - 1); - - htimer_add(s_loop, on_timeout, 1000, 1); - assert(hloop_run(s_loop) == 0); - assert(s_accept_count == 1); - assert(s_connect_count == 1); - assert(s_read_count == 1); - assert(hio_write_is_complete(s_client)); - hio_close(s_client); - hio_close(s_listener); - assert(s_client->phase == HIO_PHASE_CLOSED); - assert(s_listener->phase == HIO_PHASE_CLOSED); - hloop_free(&s_loop); - printf("hio_phase_test passed\n"); - return 0; -} - -#endif From b1041a3a7abb5598793d2cb80cb9e4a4da7aaf9f Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 23:30:11 +0800 Subject: [PATCH 8/9] refactor(event): release client proxy context when ready Co-authored-by: TRAE CLI --- event/nio.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/event/nio.c b/event/nio.c index 113b4a21e..2438d520f 100644 --- a/event/nio.c +++ b/event/nio.c @@ -86,6 +86,12 @@ static void nio_flush_write_queue(hio_t* io) { static void nio_connect_ready(hio_t* io) { io->phase = HIO_PHASE_ESTABLISHED; __connect_cb(io); + // Keep proxy metadata available during connect_cb. The transport no longer + // needs it after the application has accepted the established connection. + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; + } nio_flush_write_queue(io); } @@ -618,8 +624,10 @@ int hio_close (hio_t* io) { io->ssl_ctx = NULL; } SAFE_FREE(io->hostname); - proxy_ctx_free(io->proxy); - io->proxy = NULL; + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; + } if (io->io_type & HIO_TYPE_SOCKET) { closesocket(io->fd); } else if (io->io_type == HIO_TYPE_PIPE) { From 6baaa60c289cc8f991e971a63b2388ec48096a7d Mon Sep 17 00:00:00 2001 From: ithewei Date: Fri, 25 Sep 2026 23:58:16 +0800 Subject: [PATCH 9/9] refactor(event): release server proxy contexts Co-authored-by: TRAE CLI --- event/proxy.c | 9 +++++++++ event/socks5.c | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/event/proxy.c b/event/proxy.c index 2e3bf0501..98aa2f391 100644 --- a/event/proxy.c +++ b/event/proxy.c @@ -239,6 +239,11 @@ static void on_tcp_proxy_accept(hio_t* io) { if (proxy == NULL || hio_setup_tcp_upstream(io, proxy->setting.target_host, proxy->setting.target_port, 0) == NULL) { hio_close(io); + return; + } + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; } } @@ -265,5 +270,9 @@ hio_t* hloop_create_udp_proxy_server(hloop_t* loop, const proxy_setting_t* setti hio_close(listener); return NULL; } + if (listener->proxy) { + proxy_ctx_free(listener->proxy); + listener->proxy = NULL; + } return listener; } diff --git a/event/socks5.c b/event/socks5.c index c0c403848..91c8a8e8a 100644 --- a/event/socks5.c +++ b/event/socks5.c @@ -75,6 +75,10 @@ static void socks5_server_upstream_connect(hio_t* upstream) { hio_setcb_read(upstream, hio_write_upstream); hio_setcb_close(io, socks5_server_close); hio_setcb_close(upstream, socks5_server_close); + if (io->proxy) { + proxy_ctx_free(io->proxy); + io->proxy = NULL; + } hio_read(io); hio_read(upstream); }