Files
waf-platform/EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests/Network/HttpdnsNWHTTPClient_PoolManagementTests.m
2026-02-20 17:56:24 +08:00

775 lines
37 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// HttpdnsNWHTTPClient_PoolManagementTests.m
// AlicloudHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
//
// 连接池管理测试 - 包含多端口隔离 (K)、端口池耗尽 (L)、池验证 (O)、空闲超时 (S) 测试组
// 测试总数16 个K:5 + L:3 + O:3 + S:5
//
#import "HttpdnsNWHTTPClientTestBase.h"
@interface HttpdnsNWHTTPClient_PoolManagementTests : HttpdnsNWHTTPClientTestBase
@end
@implementation HttpdnsNWHTTPClient_PoolManagementTests
#pragma mark - K. 多端口连接隔离测试
// K.1 不同 HTTPS 端口使用不同连接池
- (void)testMultiPort_DifferentHTTPSPorts_SeparatePoolKeys {
XCTestExpectation *expectation = [self expectationWithDescription:@"Different ports use different pools"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 请求端口 11443
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"First request to port 11443 should succeed");
XCTAssertEqual(response1.statusCode, 200);
// 请求端口 11444应该创建新连接不复用 11443 的)
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"First request to port 11444 should succeed");
XCTAssertEqual(response2.statusCode, 200);
// 再次请求端口 11443应该复用之前的连接
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443Again"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3, @"Second request to port 11443 should reuse connection");
XCTAssertEqual(response3.statusCode, 200);
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:50.0];
}
// K.2 三个不同 HTTPS 端口的并发请求
- (void)testMultiPort_ConcurrentThreePorts_AllSucceed {
NSInteger requestsPerPort = 10;
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *successCountLock = [[NSLock alloc] init];
__block NSInteger successCount = 0;
for (NSNumber *port in ports) {
for (NSInteger i = 0; i < requestsPerPort; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Request %ld", port, (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentMultiPort"
timeout:15.0
error:&error];
if (response && response.statusCode == 200) {
[successCountLock lock];
successCount++;
[successCountLock unlock];
}
[expectation fulfill];
});
}
}
[self waitForExpectations:expectations timeout:60.0];
// 验证所有请求都成功
XCTAssertEqual(successCount, ports.count * requestsPerPort, @"All 30 requests should succeed");
}
// K.3 快速切换端口模式
- (void)testMultiPort_SequentialPortSwitching_ConnectionReusePerPort {
XCTestExpectation *expectation = [self expectationWithDescription:@"Sequential port switching"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSString *> *urls = @[
@"https://127.0.0.1:11443/get", // 第一次访问 11443
@"https://127.0.0.1:11444/get", // 第一次访问 11444
@"https://127.0.0.1:11445/get", // 第一次访问 11445
@"https://127.0.0.1:11443/get", // 第二次访问 11443应复用
@"https://127.0.0.1:11444/get", // 第二次访问 11444应复用
];
NSMutableArray<NSNumber *> *responseTimes = [NSMutableArray array];
for (NSString *url in urls) {
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:url
userAgent:@"SwitchingPorts"
timeout:15.0
error:&error];
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
XCTAssertNotNil(response, @"Request to %@ should succeed", url);
XCTAssertEqual(response.statusCode, 200);
[responseTimes addObject:@(elapsed)];
}
// 验证所有请求都完成
XCTAssertEqual(responseTimes.count, urls.count);
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:80.0];
}
// K.4 每个端口独立的连接池限制
- (void)testMultiPort_PerPortPoolLimit_IndependentPools {
XCTestExpectation *expectation = [self expectationWithDescription:@"Per-port pool limits"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 向端口 11443 发送 10 个请求
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Pool11443"
timeout:15.0
error:&error];
XCTAssertTrue(response != nil || error != nil);
}
// 向端口 11444 发送 10 个请求(应该有独立的池)
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Pool11444"
timeout:15.0
error:&error];
XCTAssertTrue(response != nil || error != nil);
}
// 等待连接归还
[NSThread sleepForTimeInterval:1.0];
// 验证两个端口都仍然可用
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Verify11443"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Port 11443 should still work after heavy usage");
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Verify11444"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Port 11444 should still work after heavy usage");
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:150.0];
}
// K.5 交错访问多个端口
- (void)testMultiPort_InterleavedRequests_AllPortsAccessible {
XCTestExpectation *expectation = [self expectationWithDescription:@"Interleaved multi-port requests"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
NSInteger totalRequests = 20;
NSInteger successCount = 0;
// 交错请求:依次循环访问所有端口
for (NSInteger i = 0; i < totalRequests; i++) {
NSNumber *port = ports[i % ports.count];
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"Interleaved"
timeout:15.0
error:&error];
if (response && response.statusCode == 200) {
successCount++;
}
}
XCTAssertEqual(successCount, totalRequests, @"All interleaved requests should succeed");
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:120.0];
}
#pragma mark - L. 基于端口的池耗尽测试
// L.1 四个端口同时承载高负载
- (void)testPoolExhaustion_FourPortsSimultaneous_AllSucceed {
NSInteger requestsPerPort = 10;
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *successCountLock = [[NSLock alloc] init];
__block NSInteger successCount = 0;
// 向 4 个端口各发起 10 个并发请求(共 40 个)
for (NSNumber *port in ports) {
for (NSInteger i = 0; i < requestsPerPort; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Request %ld", port, (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"FourPortLoad"
timeout:15.0
error:&error];
if (response && response.statusCode == 200) {
[successCountLock lock];
successCount++;
[successCountLock unlock];
}
[expectation fulfill];
});
}
}
[self waitForExpectations:expectations timeout:80.0];
// 验证成功率 > 95%(允许少量因并发导致的失败)
XCTAssertGreaterThan(successCount, 38, @"At least 95%% of 40 requests should succeed");
}
// L.2 单个端口耗尽时其他端口不受影响
- (void)testPoolExhaustion_SinglePortExhausted_OthersUnaffected {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSInteger port11444SuccessCount = 0;
NSLock *countLock = [[NSLock alloc] init];
// 向端口 11443 发起 20 个并发请求(可能导致池耗尽)
for (NSInteger i = 0; i < 20; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Exhaust11443 %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Exhaust11443"
timeout:15.0
error:&error];
[expectation fulfill];
});
}
// 同时向端口 11444 发起 5 个请求(应该不受 11443 影响)
for (NSInteger i = 0; i < 5; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port11444 %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Independent11444"
timeout:15.0
error:&error];
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
if (response && response.statusCode == 200) {
[countLock lock];
port11444SuccessCount++;
[countLock unlock];
}
// 验证响应时间合理(不应因 11443 负载而显著延迟)
XCTAssertLessThan(elapsed, 10.0, @"Port 11444 should not be delayed by port 11443 load");
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:60.0];
// 验证端口 11444 的请求都成功
XCTAssertEqual(port11444SuccessCount, 5, @"All port 11444 requests should succeed despite 11443 load");
}
// L.3 多端口使用后的连接清理
- (void)testPoolExhaustion_MultiPortCleanup_ExpiredConnectionsPruned {
if (getenv("SKIP_SLOW_TESTS")) {
return;
}
XCTestExpectation *expectation = [self expectationWithDescription:@"Multi-port connection cleanup"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
// 向三个端口各发起一个请求
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"Initial"
timeout:15.0
error:&error];
XCTAssertTrue(response != nil || error != nil);
}
// 等待超过 30 秒,让所有连接过期
[NSThread sleepForTimeInterval:31.0];
// 再次向三个端口发起请求(应该创建新连接)
for (NSNumber *port in ports) {
NSString *urlString = [NSString stringWithFormat:@"https://127.0.0.1:%@/get", port];
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"AfterExpiry"
timeout:15.0
error:&error];
XCTAssertTrue(response != nil || error != nil, @"Requests should succeed after expiry");
}
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:80.0];
}
#pragma mark - O. 连接池验证测试(使用新增的检查 API
// O.1 综合连接池验证 - 演示所有检查能力
- (void)testPoolVerification_ComprehensiveCheck_AllAspectsVerified {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 初始状态:无连接
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty initially");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"Total connections should be 0 initially");
XCTAssertEqual(self.client.connectionCreationCount, 0,
@"Creation count should be 0 initially");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"Reuse count should be 0 initially");
// 发送 5 个请求到同一端点
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"PoolVerificationTest"
timeout:15.0
error:&error];
XCTAssertNotNil(response, @"Request %ld should succeed", (long)i);
XCTAssertEqual(response.statusCode, 200, @"Request %ld should return 200", (long)i);
}
// 验证连接池状态
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Should have exactly 1 connection in pool for key: %@", poolKey);
XCTAssertEqual([self.client totalConnectionCount], 1,
@"Total connection count should be 1");
// 验证统计计数
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create only 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 4,
@"Should reuse connection 4 times");
// 验证连接复用率
CGFloat reuseRate = (CGFloat)self.client.connectionReuseCount /
(self.client.connectionCreationCount + self.client.connectionReuseCount);
XCTAssertGreaterThanOrEqual(reuseRate, 0.8,
@"Reuse rate should be at least 80%% (actual: %.1f%%)", reuseRate * 100);
// 验证 pool keys
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
XCTAssertEqual(allKeys.count, 1, @"Should have exactly 1 pool key");
XCTAssertTrue([allKeys containsObject:poolKey], @"Should contain the expected pool key");
}
// O.2 多端口连接池隔离验证
- (void)testPoolVerification_MultiPort_IndependentPools {
[self.client resetPoolStatistics];
NSString *key11443 = @"127.0.0.1:11443:tls";
NSString *key11444 = @"127.0.0.1:11444:tls";
// 初始:两个池都为空
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 0);
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0);
// 向端口 11443 发送 3 个请求
for (NSInteger i = 0; i < 3; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
timeout:15.0
error:&error];
XCTAssertNotNil(response);
}
// 验证端口 11443 的池状态
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 1,
@"Port 11443 should have 1 connection");
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0,
@"Port 11444 should still be empty");
// 向端口 11444 发送 3 个请求
for (NSInteger i = 0; i < 3; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444"
timeout:15.0
error:&error];
XCTAssertNotNil(response);
}
// 验证两个端口的池都存在且独立
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 1,
@"Port 11443 should still have 1 connection");
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 1,
@"Port 11444 should now have 1 connection");
XCTAssertEqual([self.client totalConnectionCount], 2,
@"Total should be 2 connections (one per port)");
// 验证统计:应该创建了 2 个连接,复用了 4 次
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should create 2 connections (one per port)");
XCTAssertEqual(self.client.connectionReuseCount, 4,
@"Should reuse connections 4 times total");
// 验证 pool keys
NSArray<NSString *> *allKeys = [self.client allConnectionPoolKeys];
XCTAssertEqual(allKeys.count, 2, @"Should have 2 pool keys");
XCTAssertTrue([allKeys containsObject:key11443], @"Should contain key for port 11443");
XCTAssertTrue([allKeys containsObject:key11444], @"Should contain key for port 11444");
}
// O.3 连接池容量限制验证
- (void)testPoolVerification_PoolCapacity_MaxFourConnections {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 发送 10 个连续请求(每个请求都会归还连接到池)
for (NSInteger i = 0; i < 10; i++) {
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"CapacityTest"
timeout:15.0
error:&error];
XCTAssertNotNil(response);
}
// 等待连接归还
[NSThread sleepForTimeInterval:1.0];
// 验证池大小不超过 4kHttpdnsNWHTTPClientMaxIdleConnectionsPerKey
NSUInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed 4 (actual: %lu)", (unsigned long)poolSize);
// 验证统计:应该只创建了 1 个连接(因为串行请求,每次都复用)
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create only 1 connection for sequential requests");
XCTAssertEqual(self.client.connectionReuseCount, 9,
@"Should reuse connection 9 times");
}
#pragma mark - S. 空闲超时详细测试
// S.1 混合过期和有效连接 - 选择性清理
- (void)testIdleTimeout_MixedExpiredValid_SelectivePruning {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 创建第一个连接
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ConnectionA"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
// 等待连接归还
[NSThread sleepForTimeInterval:0.5];
// 使用 DEBUG API 获取连接 A 并设置为过期35 秒前)
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1, @"Should have 1 connection in pool");
HttpdnsNWReusableConnection *connectionA = connections.firstObject;
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-35.0];
[connectionA debugSetLastUsedDate:expiredDate];
// 创建第二个连接(通过并发请求)
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ConnectionB"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
// 等待归还
[NSThread sleepForTimeInterval:0.5];
// 验证:应该有 1 个连接connectionA 过期被移除connectionB 留下)
connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1,
@"Should have only 1 connection (expired A removed, valid B kept)");
// 第三个请求应该复用 connectionB
[self.client resetPoolStatistics];
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ReuseB"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3);
// 验证:复用了 connectionB没有创建新连接
XCTAssertEqual(self.client.connectionCreationCount, 0,
@"Should not create new connection (reuse existing valid connection)");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Should reuse the valid connection B");
}
// S.2 In-Use 保护 - 使用中的连接不会过期
- (void)testIdleTimeout_InUseProtection_ActiveConnectionNotPruned {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 创建第一个连接
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"Initial"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
[NSThread sleepForTimeInterval:0.5];
// 借出连接并保持 inUse=YES
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1);
HttpdnsNWReusableConnection *conn = connections.firstObject;
// 手动设置为 60 秒前(远超 30 秒超时)
NSDate *veryOldDate = [NSDate dateWithTimeIntervalSinceNow:-60.0];
[conn debugSetLastUsedDate:veryOldDate];
// 将连接标记为使用中
[conn debugSetInUse:YES];
// 触发清理(通过发起另一个并发请求)
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger connectionsBefore = 0;
__block NSInteger connectionsAfter = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
connectionsBefore = [self.client totalConnectionCount];
// 发起请求(会触发 pruneConnectionPool
NSError *error2 = nil;
[self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"TriggerPrune"
timeout:15.0
error:&error2];
[NSThread sleepForTimeInterval:0.5];
connectionsAfter = [self.client totalConnectionCount];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
// 清理:重置 inUse 状态
[conn debugSetInUse:NO];
// 验证inUse=YES 的连接不应该被清理
// connectionsBefore = 1 (旧连接), connectionsAfter = 2 (旧连接 + 新连接)
XCTAssertEqual(connectionsBefore, 1,
@"Should have 1 connection before (in-use protected)");
XCTAssertEqual(connectionsAfter, 2,
@"Should have 2 connections after (in-use connection NOT pruned, new connection added)");
}
// S.3 所有连接过期 - 批量清理
- (void)testIdleTimeout_AllExpired_BulkPruning {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 创建 4 个连接(填满池)
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger i = 0; i < 4; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"FillPool"
timeout:15.0
error:&error];
XCTAssertNotNil(response);
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:30.0];
// 等待所有连接归还
[NSThread sleepForTimeInterval:1.0];
// 验证池已满
NSUInteger poolSizeBefore = [self.client connectionPoolCountForKey:poolKey];
XCTAssertGreaterThan(poolSizeBefore, 0, @"Pool should have connections");
// 将所有连接设置为过期31 秒前)
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-31.0];
for (HttpdnsNWReusableConnection *conn in connections) {
[conn debugSetLastUsedDate:expiredDate];
}
// 发起新请求(触发批量清理)
NSError *errorNew = nil;
HttpdnsNWHTTPClientResponse *responseNew = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"AfterBulkExpiry"
timeout:15.0
error:&errorNew];
XCTAssertNotNil(responseNew, @"Request should succeed after bulk pruning");
XCTAssertEqual(responseNew.statusCode, 200);
// 等待归还
[NSThread sleepForTimeInterval:0.5];
// 验证:池中只有新连接(所有旧连接被清理)
NSUInteger poolSizeAfter = [self.client connectionPoolCountForKey:poolKey];
XCTAssertEqual(poolSizeAfter, 1,
@"Pool should have only 1 connection (new one after bulk pruning)");
}
// S.4 过期后池状态验证
- (void)testIdleTimeout_PoolStateAfterExpiry_DirectVerification {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 创建连接
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"CreateConnection"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
[NSThread sleepForTimeInterval:0.5];
// 验证连接在池中
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection");
// 设置连接为过期
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
HttpdnsNWReusableConnection *conn = connections.firstObject;
[conn debugSetLastUsedDate:[NSDate dateWithTimeIntervalSinceNow:-31.0]];
// 发起请求(触发清理)
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"TriggerPrune"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
[NSThread sleepForTimeInterval:0.5];
// 直接验证池状态:过期连接已被移除,新连接已加入
NSUInteger poolSizeAfter = [self.client connectionPoolCountForKey:poolKey];
XCTAssertEqual(poolSizeAfter, 1,
@"Pool should have 1 connection (expired removed, new added)");
// 验证统计:创建了新连接(旧连接过期不可复用)
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 1,
@"Should have created at least 1 new connection");
}
// S.5 快速过期测试(无需等待)- 演示最佳实践
- (void)testIdleTimeout_FastExpiry_NoWaiting {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
// 第一个请求:创建连接
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"FastTest1"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
XCTAssertEqual(self.client.connectionCreationCount, 1, @"Should create 1 connection");
[NSThread sleepForTimeInterval:0.5];
// 使用 DEBUG 辅助函数模拟 31 秒过期(无需实际等待)
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1);
HttpdnsNWReusableConnection *conn = connections.firstObject;
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-31.0];
[conn debugSetLastUsedDate:expiredDate];
// 第二个请求:应该检测到过期并创建新连接
[self.client resetPoolStatistics];
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"FastTest2"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 验证:创建了新连接(而非复用过期的)
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should create new connection (expired connection not reused)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"Should not reuse expired connection");
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
// 关键验证:测试应该很快完成(< 5 秒),而非等待 30+ 秒
XCTAssertLessThan(elapsed, 5.0,
@"Fast expiry test should complete quickly (%.1fs) without 30s wait", elapsed);
}
@end