feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,774 @@
//
// HttpdnsNWHTTPClient_PoolManagementTests.m
// TrustHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 trustapp.com. All rights reserved.
//
// <EFBFBD><EFBFBD>?- <EFBFBD><EFBFBD>?(K) (L) (O)<EFBFBD><EFBFBD>?(S) <EFBFBD><EFBFBD>?
// <EFBFBD><EFBFBD>?6 K:5 + L:3 + O:3 + S:5<EFBFBD><EFBFBD>?
//
#import "HttpdnsNWHTTPClientTestBase.h"
@interface HttpdnsNWHTTPClient_PoolManagementTests : HttpdnsNWHTTPClientTestBase
@end
@implementation HttpdnsNWHTTPClient_PoolManagementTests
#pragma mark - K. <EFBFBD><EFBFBD>?
// K.1 HTTPS 使<EFBFBD><EFBFBD>?
- (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<EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
- (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 <EFBFBD><EFBFBD>?
- (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", // 访<EFBFBD><EFBFBD>?11443
@"https://127.0.0.1:11444/get", // 访<EFBFBD><EFBFBD>?11444
@"https://127.0.0.1:11445/get", // 访<EFBFBD><EFBFBD>?11445
@"https://127.0.0.1:11443/get", // 访<EFBFBD><EFBFBD>?11443<EFBFBD><EFBFBD>?
@"https://127.0.0.1:11444/get", // 访<EFBFBD><EFBFBD>?11444<EFBFBD><EFBFBD>?
];
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), ^{
// <EFBFBD><EFBFBD>?11443 <EFBFBD><EFBFBD>?10 <EFBFBD><EFBFBD>?
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);
}
// <EFBFBD><EFBFBD>?11444 <EFBFBD><EFBFBD>?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];
// <EFBFBD><EFBFBD>?
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;
// 访<EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
- (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;
// <EFBFBD><EFBFBD>?4 10 <EFBFBD><EFBFBD>?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];
// <EFBFBD><EFBFBD>?> 95%
XCTAssertGreaterThan(successCount, 38, @"At least 95%% of 40 requests should succeed");
}
// L.2 <EFBFBD><EFBFBD>?
- (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];
// <EFBFBD><EFBFBD>?11443 20 <EFBFBD><EFBFBD>?
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];
});
}
// <EFBFBD><EFBFBD>?11444 5 11443 <EFBFBD><EFBFBD>?
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 使<EFBFBD><EFBFBD>?
- (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];
// <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
[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. 使<EFBFBD><EFBFBD>?API<EFBFBD><EFBFBD>?
// O.1 <EFBFBD><EFBFBD>?- <EFBFBD><EFBFBD>?
- (void)testPoolVerification_ComprehensiveCheck_AllAspectsVerified {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?
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");
// <EFBFBD><EFBFBD>?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);
}
// <EFBFBD><EFBFBD>?
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");
// <EFBFBD><EFBFBD>?
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";
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 0);
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0);
// <EFBFBD><EFBFBD>?11443 <EFBFBD><EFBFBD>?3 <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:key11443], 1,
@"Port 11443 should have 1 connection");
XCTAssertEqual([self.client connectionPoolCountForKey:key11444], 0,
@"Port 11444 should still be empty");
// <EFBFBD><EFBFBD>?11444 <EFBFBD><EFBFBD>?3 <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?4 <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
- (void)testPoolVerification_PoolCapacity_MaxFourConnections {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?10 <EFBFBD><EFBFBD>?
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<EFBFBD><EFBFBD>?
NSUInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed 4 (actual: %lu)", (unsigned long)poolSize);
// <EFBFBD><EFBFBD>?1 <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?- <EFBFBD><EFBFBD>?
- (void)testIdleTimeout_MixedExpiredValid_SelectivePruning {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?5 <EFBFBD><EFBFBD>?
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];
// <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1,
@"Should have only 1 connection (expired A removed, valid B kept)");
// <EFBFBD><EFBFBD>?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<EFBFBD><EFBFBD>?
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";
// <EFBFBD><EFBFBD>?
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];
// <EFBFBD><EFBFBD>?inUse=YES
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
XCTAssertEqual(connections.count, 1);
HttpdnsNWReusableConnection *conn = connections.firstObject;
// <EFBFBD><EFBFBD>?60 <EFBFBD><EFBFBD>?30
NSDate *veryOldDate = [NSDate dateWithTimeIntervalSinceNow:-60.0];
[conn debugSetLastUsedDate:veryOldDate];
// 使<EFBFBD><EFBFBD>?
[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<EFBFBD><EFBFBD>?
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));
// <EFBFBD><EFBFBD>?inUse <EFBFBD><EFBFBD>?
[conn debugSetInUse:NO];
// inUse=YES <EFBFBD><EFBFBD>?
// connectionsBefore = 1 (<EFBFBD><EFBFBD>?, connectionsAfter = 2 (<EFBFBD><EFBFBD>?+ <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?-
- (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];
// <EFBFBD><EFBFBD>?
[NSThread sleepForTimeInterval:1.0];
// <EFBFBD><EFBFBD>?
NSUInteger poolSizeBefore = [self.client connectionPoolCountForKey:poolKey];
XCTAssertGreaterThan(poolSizeBefore, 0, @"Pool should have connections");
// <EFBFBD><EFBFBD>?1 <EFBFBD><EFBFBD>?
NSArray<HttpdnsNWReusableConnection *> *connections = [self.client connectionsInPoolForKey:poolKey];
NSDate *expiredDate = [NSDate dateWithTimeIntervalSinceNow:-31.0];
for (HttpdnsNWReusableConnection *conn in connections) {
[conn debugSetLastUsedDate:expiredDate];
}
// <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>?
- (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];
// <EFBFBD><EFBFBD>?
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection");
// <EFBFBD><EFBFBD>?
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];
// <EFBFBD><EFBFBD>?
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 <EFBFBD><EFBFBD>? <EFBFBD><EFBFBD>?
- (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 <EFBFBD><EFBFBD>?
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+ <EFBFBD><EFBFBD>?
XCTAssertLessThan(elapsed, 5.0,
@"Fast expiry test should complete quickly (%.1fs) without 30s wait", elapsed);
}
@end