Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ set(NETWORK_TLS_SHUTDOWN_TIMEOUT_MS 5000 CACHE STRING
"TCP safe_shutdown TLS close-notify timeout in milliseconds")
set(NETWORK_THREAD_WORKER_POLL_MS 1 CACHE STRING
"thread_executor run_one_for polling interval in milliseconds")
set(NETWORK_WAIT_SLEEP_MS 1 CACHE STRING
"Sleep duration in milliseconds for wait_impl / drain loops to avoid busy-waiting")
set(NETWORK_FAST_SPIN_COUNT 10 CACHE STRING
"Number of fast-spin (yield without sleep) iterations in wait_impl before escalating to sleep")

find_package(OpenSSL REQUIRED)

Expand All @@ -40,6 +44,8 @@ target_compile_definitions(network PRIVATE
NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS=${NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS}
NETWORK_TLS_SHUTDOWN_TIMEOUT_MS=${NETWORK_TLS_SHUTDOWN_TIMEOUT_MS}
NETWORK_THREAD_WORKER_POLL_MS=${NETWORK_THREAD_WORKER_POLL_MS}
NETWORK_WAIT_SLEEP_MS=${NETWORK_WAIT_SLEEP_MS}
NETWORK_FAST_SPIN_COUNT=${NETWORK_FAST_SPIN_COUNT}
)

target_link_libraries(network covscript OpenSSL::SSL OpenSSL::Crypto)
Expand Down
4 changes: 2 additions & 2 deletions CNI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ sock.connect_ssl("localhost", {"trust_mode": "insecure"}.to_hash_map())
| `send` | `(data: string) → int` | 发送数据(单次部分写入,返回实际写入字节数)。需完整发送时使用 `write` |
| `write` | `(data: string)` | 发送数据(保证全部写入,阻塞直到完成) |
| `shutdown` | `()` | 关闭套接字通信通道。与 `close()` 的区别:`shutdown()` 仅关闭通信,socket 保持打开且资源不释放;`close()` 释放所有资源 |
| `safe_shutdown` | `() → boolean` | 安全关闭:协作式等待异步操作全部完成后关闭 TLS 和 TCP。成功返回 `true`;关闭过程中发生错误返回 `false`。在 fiber 环境中通过 `poll` + `yield` 协作等待,不阻塞 OS 线程 |
| `safe_shutdown` | `() → boolean` | 安全关闭:协作式等待异步操作全部完成后关闭 TLS 和 TCP。成功返回 `true`;关闭过程中发生错误返回 `false`。在 fiber 环境中通过 `poll` + 分级等待(`yield` × N,然后 `sleep_for`)协作等待,不阻塞 OS 线程 |
| `local_endpoint` | `() → endpoint` | 获取本地端点地址 |
| `remote_endpoint` | `() → endpoint` | 获取远程端点地址 |

Expand Down Expand Up @@ -341,7 +341,7 @@ end
1. **异步操作生命周期**:异步操作期间 socket 必须保持存活。创建 `work_guard` 可防止事件循环在所有异步操作完成前停止。
2. **TLS 连接**:同步 `connect_ssl` 方法先建立 TCP 连接再执行 TLS 握手;异步 `async.connect_ssl` 仅执行 TLS 握手(socket 必须先建立 TCP 连接)。握手失败时 SSL 上下文会被自动清理。
3. **`send` vs `write`**:`send` 执行单次写入并返回实际写入字节数,类似 BSD `send()`;`write` 保证全部写入,类似 POSIX `write()`。需要可靠传输时使用 `write`。
4. **`shutdown` vs `close` vs `safe_shutdown`**:`shutdown` 关闭通信通道但不释放资源(socket 保持 `is_open()` 为 true);`close` 立即关闭并释放 TLS 上下文(如有进行中的异步操作会抛出异常);`safe_shutdown` 协作式等待所有异步操作完成后关闭 TLS 和 TCP。异步任务等待无超时;TLS close-notify 超时默认 5000ms(`NETWORK_TLS_SHUTDOWN_TIMEOUT_MS`)。在 fiber 环境中通过 `poll` + `yield` 协作等待,不阻塞 OS 线程;非 fiber 环境调用线程一直阻塞到关闭完成。推荐在异步场景中使用 `safe_shutdown`。
4. **`shutdown` vs `close` vs `safe_shutdown`**:`shutdown` 关闭通信通道但不释放资源(socket 保持 `is_open()` 为 true);`close` 立即关闭并释放 TLS 上下文(如有进行中的异步操作会抛出异常);`safe_shutdown` 协作式等待所有异步操作完成后关闭 TLS 和 TCP。异步任务等待无超时;TLS close-notify 超时默认 5000ms(`NETWORK_TLS_SHUTDOWN_TIMEOUT_MS`)。在 fiber 环境中通过 `poll` + 分级等待(`yield` × N,然后 `sleep_for`)协作等待,不阻塞 OS 线程;非 fiber 环境调用线程一直阻塞到关闭完成。推荐在异步场景中使用 `safe_shutdown`。
5. **信任报告**:建议使用 `sock.get_ssl_trust_report()`(每个 socket 独立),而非全局的 `get_last_global_ssl_trust_report()`(线程级别,可能被覆盖)。
6. **线程安全**:同一 socket 不应并发混合同步和异步操作。异步 API 最多允许一个 pending read/receive 和一个 pending write/send;同方向重叠操作会被拒绝,读写可全双工并行。TLS 异步 handler 绑定到每个 socket 的 strand,可由多个 `async.thread_worker` 安全驱动。
7. **netutils HTTP 客户端**:`netutils` 提供 `http_client` 类(`http_request` / `post` 方法)和 `openai_client` 子类。TLS 验证通过客户端实例的 `set_tls_options({"trust_mode": "auto"}.to_hash_map())` 控制,不再使用全局 `ssl_verify` 标志。详见 [NETUTILS.md](NETUTILS.md)。
Expand Down
22 changes: 11 additions & 11 deletions NETUTILS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CovScript NetUtils 协议文档

版本:2.2
版本:2.3

作者:Covariant Script OSC

Expand All @@ -27,7 +27,7 @@
## 2. 基本常量与工具函数

* `server_name = "CovScript-NetUtils"`
* `server_version = "2.2"`
* `server_version = "2.3"`

与协议/实现相关的重要工具函数:

Expand Down Expand Up @@ -168,7 +168,7 @@ Master 分配 `rank`:先尝试使用 `deprecated_rank`(回收的编号),
| 配置项 | 含义 | 默认值 |
| -------------------------- | -----------------------------------: | :----: |
| `thread_count` | 底层 Asio I/O 线程数(不等于 handler 并发数) | `4` |
| `worker_count` | 单进程 HTTP 连接处理 fiber 数(控制可同时服务的活动/keep-alive 连接数) | `64` |
| `worker_count` | 单进程 HTTP 连接处理 fiber 数(控制可同时服务的活动/keep-alive 连接数) | `32` |
| `max_keep_alive` | 每连接允许的最大请求数 | `100` |
| `keep_alive_timeout` | 保持连接的最大空闲时间(ms) | `5000` |
| `max_body_size` | 单个请求体的最大字节数 | `67108864` (64 MiB) |
Expand Down Expand Up @@ -240,17 +240,17 @@ Master 分配 `rank`:先尝试使用 `deprecated_rank`(回收的编号),
转发时保留原始请求头,但会过滤 hop-by-hop 头(`Host`、`Connection`、`Content-Length`、`Transfer-Encoding`、`Keep-Alive`、`Proxy-*`、`TE`、`Trailer`、`Upgrade` 等由 `http_request` 内部管理,不透传);同时**注入 `X-Forwarded-For: <客户端真实 IP>`**(客户端伪造的同名头会被丢弃后重写),后端据此可获得真实来源地址而非代理自身 IP。

* `stop()`
优雅关闭服务器:停止所有 worker、释放 async_guard、关闭 acceptor。
优雅关闭服务器:标记所有 worker 退出、关闭 acceptor 和 Socket,通知所有纤程检查退出标志。资源释放(thread_pool、async_guard)在 `run()` 的事件循环退出后统一执行,避免从 handler 内调用 `stop()` 时纤程在 foreach 迭代中访问已释放的 `worker_list` 导致崩溃。可重复调用(重入安全)

* `run()`
启动事件循环(`listen` 或 `set_master` 后调用)。

* `set_optimal_scheduling(policy : string)`
设置纤程调度策略,影响事件循环在恢复休眠 Worker 纤程时的反压行为。推荐在 `run()`/`poll()` 前调用,可选值:`"responsive"`(低延迟,适合轮询模式)、`"balanced"`(默认)、`"throughput"`、`"efficient"`。

* `poll()`
启动服务(非阻塞),仅轮询一次。需循环调用才能使服务正常运行。
启动服务(非阻塞),仅轮询一次。需循环调用才能使服务正常运行。当所有 Worker 意外终止时会自动标记 `stopped` 并释放资源。

* `run()`
启动服务(阻塞),适合守护进程模式。

启动服务(阻塞),适合守护进程模式。事件循环退出后自动释放 thread_pool、async_guard、worker_list。当所有 Worker 意外终止时会记录日志并退出。
---

## 9. 单进程(simple_worker)与多进程(master/slave)Worker 行为
Expand All @@ -263,7 +263,7 @@ Master 分配 `rank`:先尝试使用 `deprecated_rank`(回收的编号),

* Accept Worker:接收新的客户端连接并把 `http_conn` 放入 `conn_list`。
* Request Worker:从 `conn_list` 中挑选一个处于 `state == 0` 的 `http_conn`,读取 header 并把 `session` push 到 `request_queue`。
* Dispatch Worker:为空闲 Slave 分配 `conn->request_queue[conn->request_idx]` 并发送。
* Dispatch Worker:为空闲 Slave 分配 `conn->request_queue[conn->request_idx]` 并发送。当无可用 Slave 时,以 10 ms 间隔轮询并每秒记录一次日志。有待处理工作时使用 `fiber.yield()` 快速重新调度,否则使用 `fiber.sleep_for(FAST_POLL_MS)`。
* Response Worker:负责把 Slave 返回的响应写回客户端 socket,并在必要时关闭连接与清理。

* **slave_worker**(Slave)
Expand Down Expand Up @@ -375,7 +375,7 @@ slave.set_slave("127.0.0.1", 9000)
* **POST Body 处理**:Master/Slave 之间传输 POST Body 时依赖 `content_length`,若客户端或中间链路错误导致长度不匹配,会引发阻塞或提前 EOF,要在应用层做好验证(如 Content-Length 校验)。
* **版本兼容**:Master 与 Slave 的 `server_version` 必须一致,握手会校验版本号;当升级版本时请同时升级 Master/Slave。
* **资源限制**:`max_connections` / `worker_count` / `master_worker_count` 应根据机器资源和负载曲线调整,避免出现大量 socket backlog 或内存耗尽。
* **异常恢复**:Master 会在 Slave 出现错误或心跳失败时把该 Slave 标记为 `-1` 并尝试回收其 rank,以便后续新 Slave 使用;Slave 端在崩溃后会循环重连 Master(`slave_worker` 有短时 delay 重试)。
* **异常恢复**:Master 会在 Slave 出现错误或心跳失败时把该 Slave 标记为 `-1` 并尝试回收其 rank,以便后续新 Slave 使用;Slave 端在崩溃后会循环重连 Master(`slave_worker` 使用 `fiber.sleep_for(ERROR_RECOVERY_MS)`(100 ms)重试)。

## 14. 常见问题与调优建议

Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ A high-performance network extension for the [Covariant Script](http://covscript

| Package | Type | Version | Description |
|---|---|---|---|
| `network` | C++ Extension | `1.38.0_v6.5` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
| `netutils` | CovScript | `2.2` | HTTP server/client framework with single-process, distributed master/slave, and OpenAI API modes |
| `network` | C++ Extension | `1.38.0_v6.6` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
| `netutils` | CovScript | `2.3` | HTTP server/client framework with single-process, distributed master/slave, and OpenAI API modes |
| `argparse` | CovScript | `1.1` | Lightweight command-line argument parser |

> **Note:** `netutils` and `argparse` are provided as both source (`.ecs`), compiled package (`.csp`), and bytecode module (`.csym`) — place them in your project's `imports/` directory.
Expand All @@ -36,7 +36,7 @@ A high-performance network extension for the [Covariant Script](http://covscript

| Dependency | Notes |
|---|---|
| [Covariant Script](http://covscript.org.cn) | Set `CS_DEV_PATH` to the SDK root |
| [Covariant Script](http://covscript.org.cn) | Set `CS_DEV_PATH` to the SDK root (ABI version 260701 or higher) |
| [OpenSSL](https://www.openssl.org/) | Runtime and development headers |
| CMake ≥ 3.16 | Build system |
| C++17 compiler | GCC, Clang, or MSVC |
Expand Down Expand Up @@ -85,6 +85,8 @@ All options can be overridden with `-D<option>=<value>`:
| `NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS` | `200` | Drain-loop deadline for UDP `safe_close` and TCP `safe_shutdown` |
| `NETWORK_TLS_SHUTDOWN_TIMEOUT_MS` | `5000` | TLS close-notify timeout |
| `NETWORK_THREAD_WORKER_POLL_MS` | `1` | Thread executor polling interval |
| `NETWORK_WAIT_SLEEP_MS` | `1` | Sleep duration (ms) in `wait_impl` / drain loops between poll iterations |
| `NETWORK_FAST_SPIN_COUNT` | `10` | Yield-without-sleep iterations in graduated wait before escalating to sleep |

Comment thread
mikecovlee marked this conversation as resolved.
---

Expand Down
2 changes: 1 addition & 1 deletion argparse.csp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Generated by Extended CovScript Compiler
# DO NOT MODIFY
# Date: Fri Jul 17 12:26:29 2026
# Date: Wed Jul 22 09:48:24 2026
@charset: utf8
import ecs as argparse_ecs
package argparse
Expand Down
2 changes: 1 addition & 1 deletion csbuild/netutils.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Name": "netutils",
"Info": "Network Utilities",
"Author": "CovScript Organization",
"Version": "2.2",
"Version": "2.3",
"Source": "netutils.ecs",
"Target": "netutils.csp",
"Dependencies": [
Expand Down
2 changes: 1 addition & 1 deletion csbuild/network.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Name": "network",
"Info": "Socket Extension",
"Author": "CovScript Organization",
"Version": "1.38.0_v6.5",
"Version": "1.38.0_v6.6",
"Target": "build/imports/network.cse",
"Dependencies": []
}
20 changes: 12 additions & 8 deletions docs/async-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,15 @@ sequenceDiagram
CNI->>Socket: begin_draining_exclusive()<br/>(先阻止新 I/O)
loop while async_jobs > 0 (NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS)
CNI->>Asio: io.poll()
CNI->>Script: cs_runtime_yield()
CNI->>Script: cs_runtime_yield() (spins <= 10)
CNI->>Script: cs_runtime_sleep_for(1ms) (spins > 10)
end
opt TLS enabled
CNI->>TLS: async_shutdown(strand-bound callback)
loop close-notify 未完成且未超时
CNI->>Asio: io.poll()
CNI->>Script: cs_runtime_yield()
CNI->>Script: cs_runtime_yield() (spins <= 10)
CNI->>Script: cs_runtime_sleep_for(1ms) (spins > 10)
end
alt close-notify 超时
CNI->>Socket: cancel + close raw socket
Expand Down Expand Up @@ -412,13 +414,15 @@ sequenceDiagram
Note over Sock: 新 I/O 被 exclusive_operation 拒绝
loop async_jobs > 0 (NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS)
Script->>IO: io.poll()
Script->>Script: runtime.yield()
Script->>Script: cs_runtime_yield() (spins <= 10)
Script->>Script: cs_runtime_sleep_for(1ms) (spins > 10)
end
opt TLS enabled
Sock->>IO: tls_stream.async_shutdown(strand)
loop close-notify 未完成且未超时
Script->>IO: io.poll()
Script->>Script: runtime.yield()
Script->>Script: cs_runtime_yield() (spins <= 10)
Script->>Script: cs_runtime_sleep_for(1ms) (spins > 10)
end
alt TLS timeout
Sock->>Sock: cancel + close raw socket
Expand All @@ -431,7 +435,7 @@ sequenceDiagram
Sock-->>Script: return success
```

> **设计要点**:`close()` 和 `shutdown()` 通过 `scoped_exclusive_operation` 原子地占用 socket 独占权,防止 TOCTOU 窗口。普通 I/O 先预约 read/write 方向,再双检 `exclusive_operation`;同方向重叠直接拒绝,一读一写仍可全双工并行。TLS composed operation 的 handler 绑定到每个 socket 的 strand,多个 worker 不会并发访问同一 SSL stream。TCP `safe_shutdown()` 和 UDP `safe_close()` 的 drain 超时(默认 200ms)均通过 CMake 的 `NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS` 调整。TLS close-notify 阶段的超时独立使用 `NETWORK_TLS_SHUTDOWN_TIMEOUT_MS`(默认 5000ms)。
> **设计要点**:`close()` 和 `shutdown()` 通过 `scoped_exclusive_operation` 原子地占用 socket 独占权,防止 TOCTOU 窗口。普通 I/O 先预约 read/write 方向,再双检 `exclusive_operation`;同方向重叠直接拒绝,一读一写仍可全双工并行。TLS composed operation 的 handler 绑定到每个 socket 的 strand,多个 worker 不会并发访问同一 SSL stream。TCP `safe_shutdown()` 和 UDP `safe_close()` 的 drain 超时(默认 200ms)均通过 CMake 的 `NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS` 调整。TLS close-notify 阶段的超时独立使用 `NETWORK_TLS_SHUTDOWN_TIMEOUT_MS`(默认 5000ms)。Drain 循环和 `wait_impl()` 使用分级等待:前 `NETWORK_FAST_SPIN_COUNT`(默认 10)次迭代使用 `cs_runtime_yield()`,之后升级为 `cs_runtime_sleep_for(NETWORK_WAIT_SLEEP_MS)`(默认 1ms)。

---

Expand All @@ -455,7 +459,7 @@ sequenceDiagram

loop until state.has_done()
SrvFiber->>CNI: async.poll_once()
SrvFiber->>SrvFiber: fiber.yield()
SrvFiber->>SrvFiber: fiber.sleep_for(FAST_POLL_MS)
deactivate SrvFiber
Main->>SrvFiber: server_fiber.resume()
activate SrvFiber
Expand Down Expand Up @@ -504,8 +508,8 @@ UDP 的同步 `receive_from` / `send_to` 使用 `scoped_io_job`,`close` 使用

| 方法 | 超时 | 行为 |
|------|------|------|
| `state.wait()` | 无(直到完成) | 循环 poll + yield |
| `state.wait_for(ms)` | 自定义 | 循环 poll + yield |
| `state.wait()` | 无(直到完成) | 循环 poll + 分级等待(yield × NETWORK_FAST_SPIN_COUNT,然后 sleep_for(NETWORK_WAIT_SLEEP_MS)) |
| `state.wait_for(ms)` | 自定义 | 循环 poll + 分级等待(同上) |
| `state.has_done()` | 无 | 非阻塞检查 |

### 生命周期管理
Expand Down
Loading