Skip to content

Commit 9e74f5f

Browse files
committed
Update netutils and network components for improved TLS handling and configuration
- Bump server version from 1.6 to 1.7 in netutils.ecs. - Introduce default HTTP thread and worker counts in netutils.ecs for better configurability. - Enhance TLS connection handling in network.cpp with timeout management during safe shutdown. - Add tests for TLS handshake failures and timeouts in test_http_client.csc. - Validate default worker and thread counts in test_http_server.csc, ensuring correct behavior under concurrent connections.
1 parent df9f3a0 commit 9e74f5f

11 files changed

Lines changed: 275 additions & 30 deletions

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ set(NETWORK_MAX_PORT 65535 CACHE STRING
2323
set(NETWORK_MAX_IO_BUFFER_SIZE 67108864 CACHE STRING
2424
"Maximum bytes accepted by a single read/receive/get_buffer request")
2525
set(NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS 200 CACHE STRING
26-
"UDP safe_close drain-loop timeout in milliseconds (TCP safe_shutdown waits indefinitely for async jobs)")
26+
"Drain-loop deadline in milliseconds for both UDP safe_close and TCP safe_shutdown exclusive-operation and queued-I/O drain")
2727
set(NETWORK_TLS_SHUTDOWN_TIMEOUT_MS 5000 CACHE STRING
2828
"TCP safe_shutdown TLS close-notify timeout in milliseconds")
2929
set(NETWORK_THREAD_WORKER_POLL_MS 1 CACHE STRING

NETUTILS.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CovScript NetUtils 协议文档
22

3-
版本:1.6
3+
版本:1.7
44

55
作者:Covariant Script OSC
66

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

2929
* `server_name = "CovScript-NetUtils"`
30-
* `server_version = "1.6"`
30+
* `server_version = "1.7"`
3131

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

@@ -156,7 +156,8 @@ Master 分配 `rank`:先尝试使用 `deprecated_rank`(回收的编号),
156156

157157
| 配置项 | 含义 | 默认值 |
158158
| -------------------------- | -----------------------------------: | :----: |
159-
| `worker_count` | 每进程启动的 worker 数 | `4` |
159+
| `thread_count` | 底层 Asio I/O 线程数(不等于 handler 并发数) | `4` |
160+
| `worker_count` | 单进程 HTTP 连接处理 fiber 数(控制可同时服务的活动/keep-alive 连接数) | `64` |
160161
| `max_keep_alive` | 每连接允许的最大请求数 | `100` |
161162
| `keep_alive_timeout` | 保持连接的最大空闲时间(ms) | `5000` |
162163
| `max_connections` | Master 接入的最大并发连接数 | `100` |
@@ -336,7 +337,7 @@ slave.set_slave("127.0.0.1", 9000)
336337

337338
* **性能调优建议**
338339

339-
* `worker_count` 不建议调得过高,单核能力有限,会增加调度开销
340+
* `worker_count` 控制可同时服务的活动/keep-alive 连接数量,默认为 64。高并发部署仍应根据内存、keep-alive 时间和后端延迟调整。不建议无限制调高——每个 worker fiber 持有 accept 操作的异步状态
340341
* Master 模式下可以单独调整 `master_worker_count` 以改善 Master 对高并发连接的处理能力。同时,`max_connections` 也需要相应更改,防止大量连接排队的情况。
341342
* 由于 NetUtils 依赖 Master 节点进行分发,过多的 Slave 节点也会增加系统资源和调度的开销,一般可以取 2~8。
342343
* 调整 `max_keep_alive``keep_alive_timeout` 平衡连接复用与资源占用。

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ A high-performance network extension for the [Covariant Script](http://covscript
88

99
| Package | Type | Version | Description |
1010
|---|---|---|---|
11-
| `network` | C++ Extension | `1.38.0_v6.2` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
12-
| `netutils` | CovScript | `1.6` | HTTP server framework with single-process and distributed master/slave modes |
11+
| `network` | C++ Extension | `1.38.0_v6.3` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
12+
| `netutils` | CovScript | `1.7` | HTTP server framework with single-process and distributed master/slave modes |
1313
| `argparse` | CovScript | `1.1` | Lightweight command-line argument parser |
1414

1515
> **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.
@@ -76,7 +76,7 @@ All options can be overridden with `-D<option>=<value>`:
7676
| `NETWORK_FIXED_HEX_SIZE` | `16` | Hex string length for framing protocol helpers |
7777
| `NETWORK_MAX_PORT` | `65535` | Maximum valid TCP/UDP port number |
7878
| `NETWORK_MAX_IO_BUFFER_SIZE` | `67108864` | Max bytes per single read/receive call (64 MiB) |
79-
| `NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS` | `200` | UDP `safe_close` drain timeout |
79+
| `NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS` | `200` | Drain-loop deadline for UDP `safe_close` and TCP `safe_shutdown` |
8080
| `NETWORK_TLS_SHUTDOWN_TIMEOUT_MS` | `5000` | TLS close-notify timeout |
8181
| `NETWORK_THREAD_WORKER_POLL_MS` | `1` | Thread executor polling interval |
8282

csbuild/netutils.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"Name": "netutils",
44
"Info": "Network Utilities",
55
"Author": "CovScript Organization",
6-
"Version": "1.6",
6+
"Version": "1.7",
77
"Source": "netutils.ecs",
88
"Target": "netutils.csp",
99
"Dependencies": [

csbuild/network.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"Name": "network",
44
"Info": "Socket Extension",
55
"Author": "CovScript Organization",
6-
"Version": "1.38.0_v6.2",
6+
"Version": "1.38.0_v6.3",
77
"Target": "build/imports/network.cse",
88
"Dependencies": []
99
}

netutils.csp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Generated by Extended CovScript Compiler
22
# DO NOT MODIFY
3-
# Date: Tue Jul 14 16:30:44 2026
3+
# Date: Tue Jul 14 18:56:51 2026
44
@charset: utf8
55
import ecs as netutils_ecs
66
struct __netutils_ecs_lambda_impl_1__
@@ -122,14 +122,16 @@ import network; using network
122122
import regex
123123
import codec.json as json
124124
constant server_name = "CovScript-NetUtils"
125-
constant server_version = "1.6"
125+
constant server_version = "1.7"
126126
constant http_client_read_chunk = 8192
127127
constant http_max_header_line_size = 8192
128128
constant http_max_header_size = 65536
129129
constant http_max_body_size = 67108864
130130
constant framing_hex_size = 16
131131
constant default_http_port = 80
132132
constant default_https_port = 443
133+
constant default_http_thread_count = 4
134+
constant default_http_worker_count = 64
133135
var request_line_reg = regex.build_optimize("^([A-Z]+) ([^ ?]+)(?:\\?([^ ]*))? HTTP/([0-9.]+)$")
134136
var request_header_reg = regex.build_optimize("^([^:]*): ?(.*)$")
135137
namespace state_codes
@@ -1257,7 +1259,12 @@ class http_client
12571259
return false
12581260
end
12591261
if target["scheme"] == "https"
1260-
sock.connect_ssl(host, tls_options)
1262+
var tls_state = async.connect_ssl(sock, host, tls_options)
1263+
if !_await(tls_state)
1264+
log("TLS handshake failed: " + tls_state.get_error())
1265+
sock.safe_shutdown()
1266+
return false
1267+
end
12611268
end
12621269
sock.set_opt_no_delay(true)
12631270
return true
@@ -1733,9 +1740,9 @@ class http_server
17331740
var wwwroot_path = null
17341741
var url_map = new hash_map
17351742
var thread_pool = null
1736-
var thread_count = 4
1743+
var thread_count = default_http_thread_count
17371744
var worker_list = null
1738-
var worker_count = 4
1745+
var worker_count = default_http_worker_count
17391746
var max_keep_alive = 100
17401747
var keep_alive_timeout = 5000
17411748
var mtime_map = new hash_map

netutils.csym

Lines changed: 14 additions & 5 deletions
Large diffs are not rendered by default.

netutils.ecs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import network.*, regex
55
import codec.json
66

77
constant server_name = "CovScript-NetUtils"
8-
constant server_version = "1.6"
8+
constant server_version = "1.7"
99

1010
# ============================================================================
1111
# Configuration constants — override before use to adjust behavior
@@ -24,6 +24,10 @@ constant framing_hex_size = 16 # length of fixed-hex size head
2424
constant default_http_port = 80
2525
constant default_https_port = 443
2626

27+
# Server defaults — tuning knobs exposed for documentation and test assertions
28+
constant default_http_thread_count = 4 # Asio I/O threads
29+
constant default_http_worker_count = 64 # per-process HTTP handler fibers
30+
2731
# Internal Functions
2832

2933
var request_line_reg = regex.build_optimize("^([A-Z]+) ([^ ?]+)(?:\\?([^ ]*))? HTTP/([0-9.]+)$")
@@ -1236,7 +1240,12 @@ class http_client
12361240
return false
12371241
end
12381242
if target["scheme"] == "https"
1239-
sock.connect_ssl(host, tls_options)
1243+
var tls_state = async.connect_ssl(sock, host, tls_options)
1244+
if !_await(tls_state)
1245+
log("TLS handshake failed: " + tls_state.get_error())
1246+
sock.safe_shutdown()
1247+
return false
1248+
end
12401249
end
12411250
sock.set_opt_no_delay(true)
12421251
return true
@@ -1716,9 +1725,9 @@ class http_server
17161725
var wwwroot_path = null
17171726
var url_map = new hash_map
17181727
var thread_pool = null
1719-
var thread_count = 4
1728+
var thread_count = default_http_thread_count
17201729
var worker_list = null
1721-
var worker_count = 4
1730+
var worker_count = default_http_worker_count
17221731
var max_keep_alive = 100
17231732
var keep_alive_timeout = 5000
17241733
var mtime_map = new hash_map

network.cpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,13 +1246,32 @@ bool network_cs_ext::tcp::socket::safe_shutdown(socket_t &sock)
12461246
if (!sock->get_raw().is_open())
12471247
return true;
12481248
// Block new I/O atomically, then wait for existing jobs to drain.
1249-
// No timeout — safe_shutdown blocks until all pending I/O completes
1250-
// or the peer closes the connection.
1249+
// Uses NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS as a deadline for both
1250+
// the exclusive-lock retry path (TLS handshake collision) and the
1251+
// drain-queued-I/O path.
12511252
try {
12521253
sock->begin_draining_exclusive();
12531254
}
12541255
catch (const std::exception &) {
1255-
return false;
1256+
// Another exclusive operation (e.g. TLS handshake) is in flight.
1257+
// Cancel the raw socket to force its completion handler to fire,
1258+
// then retry the lock.
1259+
asio::error_code ignored;
1260+
sock->get_raw().cancel(ignored);
1261+
network_cs_ext::async::restart();
1262+
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS);
1263+
while (sock->async_jobs.load(std::memory_order_acquire) > 0) {
1264+
if (std::chrono::steady_clock::now() >= deadline)
1265+
break;
1266+
network_cs_ext::async::get_global_settings().poll();
1267+
cs_runtime_yield();
1268+
}
1269+
try {
1270+
sock->begin_draining_exclusive();
1271+
}
1272+
catch (const std::exception &) {
1273+
return false;
1274+
}
12561275
}
12571276
// RAII: release exclusive reservation on any exit path.
12581277
struct exclusive_guard {
@@ -1263,18 +1282,25 @@ bool network_cs_ext::tcp::socket::safe_shutdown(socket_t &sock)
12631282
s->end_draining_exclusive();
12641283
}
12651284
} guard(sock);
1285+
bool success = true;
12661286
network_cs_ext::async::restart();
12671287
// Cancel pending operations so timed-out reads/writes
12681288
// do not keep async_jobs > 0 permanently.
12691289
{
12701290
asio::error_code ignored;
12711291
sock->get_raw().cancel(ignored);
12721292
}
1293+
auto drain_dl = std::chrono::steady_clock::now() + std::chrono::milliseconds(NETWORK_SAFE_SHUTDOWN_TIMEOUT_MS);
12731294
while (sock->async_jobs.load(std::memory_order_acquire) > 0) {
1295+
if (std::chrono::steady_clock::now() >= drain_dl) {
1296+
success = false;
1297+
break;
1298+
}
12741299
network_cs_ext::async::get_global_settings().poll();
12751300
cs_runtime_yield();
12761301
}
1277-
bool success = true;
1302+
if (!success)
1303+
return false;
12781304
// TLS: async_shutdown avoids blocking the OS thread.
12791305
// A strand-bound timer closes the raw socket on timeout so the
12801306
// shutdown operation completes before TLS state is destroyed.

tests/test_http_client.csc

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,13 @@ function skip(label, reason)
5151
system.out.println("[SKIP] " + _section + " | " + label + " -- " + reason)
5252
end
5353

54+
var _httpclient_port = 15300
55+
5456
function find_free_port()
55-
var port = 15000
56-
while port < 15100
57+
var port = _httpclient_port
58+
_httpclient_port += 1
59+
var max_port = port + 100
60+
while port < max_port
5761
try
5862
var acpt = tcp.acceptor(tcp.endpoint_v4(port))
5963
acpt = null
@@ -370,6 +374,121 @@ client14.close()
370374
srv_sock8.close()
371375
acpt8 = null
372376

377+
# ============================================================
378+
# C09 -- TLS handshake failure against plain TCP server
379+
# ============================================================
380+
section("C09: TLS handshake failure")
381+
382+
var port9 = find_free_port()
383+
if port9 == 0
384+
skip("C09-all", "no free port")
385+
else
386+
var acpt9 = tcp.acceptor(tcp.endpoint_v4(port9))
387+
var srv9 = new tcp.socket
388+
var ast9 = async.accept(srv9, acpt9)
389+
390+
var client15 = new netutils.http_client
391+
client15.set_timeout_ms(2000)
392+
var target9 = client15.parse_url("https://127.0.0.1:" + to_string(port9) + "/")
393+
check_not_null("C09-01: https target parsed", target9)
394+
395+
# TLS handshake against a plain TCP server must fail
396+
var result9 = client15.connect_target(target9)
397+
check_false("C09-02: TLS to plain TCP returns false", result9)
398+
399+
if ast9.wait_for(1000)
400+
srv9.close()
401+
end
402+
acpt9 = null
403+
end
404+
405+
# ============================================================
406+
# C10 -- TLS handshake timeout (server accepts but never responds)
407+
# ============================================================
408+
section("C10: TLS handshake timeout")
409+
410+
var port10 = find_free_port()
411+
if port10 == 0
412+
skip("C10-all", "no free port")
413+
else
414+
var acpt10 = tcp.acceptor(tcp.endpoint_v4(port10))
415+
var srv10 = new tcp.socket
416+
var ast10 = async.accept(srv10, acpt10)
417+
418+
var client16 = new netutils.http_client
419+
client16.set_timeout_ms(500)
420+
var target10 = client16.parse_url("https://127.0.0.1:" + to_string(port10) + "/")
421+
check_not_null("C10-01: https target parsed", target10)
422+
423+
var start10 = runtime.time()
424+
var result10 = client16.connect_target(target10)
425+
var elapsed10 = runtime.time() - start10
426+
check_false("C10-02: TLS timeout returns false", result10)
427+
check("C10-03: timeout respected (elapsed < 3000ms)", elapsed10 < 3000)
428+
429+
if ast10.wait_for(1000)
430+
srv10.close()
431+
end
432+
acpt10 = null
433+
end
434+
435+
# ============================================================
436+
# C11 -- TLS handshake: other fiber runs while this fiber waits
437+
# ============================================================
438+
section("C11: other fiber runs during TLS wait")
439+
440+
var c11_port = find_free_port()
441+
if c11_port == 0
442+
skip("C11-all", "no free port")
443+
else
444+
var c11_tls_done = false
445+
var c11_tls_error = false
446+
447+
function c11_tls_fiber_func(sock)
448+
var state = async.connect_ssl(sock, "127.0.0.1", null)
449+
state.wait_for(2000)
450+
c11_tls_error = !state.has_done() || state.get_error() != null
451+
c11_tls_done = true
452+
end
453+
454+
var c11_acpt = tcp.acceptor(tcp.endpoint_v4(c11_port))
455+
var c11_srv = new tcp.socket
456+
var c11_ast = async.accept(c11_srv, c11_acpt)
457+
458+
# Pre-establish TCP connection outside the fiber
459+
var c11_sock = new tcp.socket
460+
c11_sock.connect(tcp.endpoint("127.0.0.1", c11_port))
461+
462+
# Fiber only does TLS handshake (not TCP connect)
463+
var c11_fiber = fiber.create(c11_tls_fiber_func, c11_sock)
464+
465+
# Resume once — TLS handshake should yield
466+
c11_fiber.resume()
467+
check("C11-01: TLS not complete after first resume", !c11_tls_done)
468+
469+
# Drive TLS fiber to completion
470+
var c11_start = runtime.time()
471+
loop
472+
c11_fiber.resume()
473+
async.poll_once()
474+
runtime.delay(5)
475+
if runtime.time() - c11_start >= 4000
476+
break
477+
end
478+
until c11_tls_done
479+
480+
check("C11-02: TLS fiber completed", c11_tls_done)
481+
check("C11-03: TLS reported error (plain TCP server)", c11_tls_error)
482+
483+
# Cancel pending handshake cleanly — safe_shutdown, not close()
484+
c11_sock.safe_shutdown()
485+
486+
if c11_ast.has_done()
487+
c11_srv.close()
488+
end
489+
c11_acpt = null
490+
end
491+
373492
# ============================================================
374493
# Cleanup and results
375494
# ============================================================

0 commit comments

Comments
 (0)