775 lines
37 KiB
Objective-C
775 lines
37 KiB
Objective-C
//
|
||
// 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];
|
||
|
||
// 验证池大小不超过 4(kHttpdnsNWHTTPClientMaxIdleConnectionsPerKey)
|
||
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
|