阿里sdk

This commit is contained in:
Robin
2026-02-20 17:56:24 +08:00
parent 39524692e5
commit f3af234308
524 changed files with 58345 additions and 0 deletions

View File

@@ -0,0 +1,740 @@
//
// HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests.m
// AlicloudHttpDNSTests
//
// @author Created by Claude Code on 2025-11-01
// Copyright © 2025 alibaba-inc.com. All rights reserved.
//
// - (M) (P) Connection (R)
// 15 M:4 + P:6 + R:5
//
#import "HttpdnsNWHTTPClientTestBase.h"
@interface HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests : HttpdnsNWHTTPClientTestBase
@end
@implementation HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests
#pragma mark - M.
// M.1
- (void)testEdgeCase_ConnectionReuseWithinPortOnly_NotAcross {
XCTestExpectation *expectation = [self expectationWithDescription:@"Reuse boundaries"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// A 11443
CFAbsoluteTime timeA = CFAbsoluteTimeGetCurrent();
NSError *errorA = nil;
HttpdnsNWHTTPClientResponse *responseA = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"RequestA"
timeout:15.0
error:&errorA];
CFAbsoluteTime elapsedA = CFAbsoluteTimeGetCurrent() - timeA;
XCTAssertNotNil(responseA);
// B 11443
CFAbsoluteTime timeB = CFAbsoluteTimeGetCurrent();
NSError *errorB = nil;
HttpdnsNWHTTPClientResponse *responseB = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"RequestB"
timeout:15.0
error:&errorB];
CFAbsoluteTime elapsedB = CFAbsoluteTimeGetCurrent() - timeB;
XCTAssertNotNil(responseB);
// C 11444
CFAbsoluteTime timeC = CFAbsoluteTimeGetCurrent();
NSError *errorC = nil;
HttpdnsNWHTTPClientResponse *responseC = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"RequestC"
timeout:15.0
error:&errorC];
CFAbsoluteTime elapsedC = CFAbsoluteTimeGetCurrent() - timeC;
XCTAssertNotNil(responseC);
// D 11444 11444
CFAbsoluteTime timeD = CFAbsoluteTimeGetCurrent();
NSError *errorD = nil;
HttpdnsNWHTTPClientResponse *responseD = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"RequestD"
timeout:15.0
error:&errorD];
CFAbsoluteTime elapsedD = CFAbsoluteTimeGetCurrent() - timeD;
XCTAssertNotNil(responseD);
//
XCTAssertEqual(responseA.statusCode, 200);
XCTAssertEqual(responseB.statusCode, 200);
XCTAssertEqual(responseC.statusCode, 200);
XCTAssertEqual(responseD.statusCode, 200);
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:70.0];
}
// M.2
- (void)testEdgeCase_HighPortCount_AllPortsManaged {
XCTestExpectation *expectation = [self expectationWithDescription:@"High port count"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445, @11446];
//
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:@"Round1"
timeout:15.0
error:&error];
XCTAssertNotNil(response, @"First round request to port %@ should succeed", port);
}
//
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:@"Round2"
timeout:15.0
error:&error];
XCTAssertNotNil(response, @"Second round request to port %@ should reuse connection", port);
}
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:120.0];
}
// M.3 访
- (void)testEdgeCase_ConcurrentPoolAccess_NoDataRace {
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSArray<NSNumber *> *ports = @[@11443, @11444, @11445];
NSInteger requestsPerPort = 5;
//
for (NSNumber *port in ports) {
for (NSInteger i = 0; i < requestsPerPort; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Port %@ Req %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:@"ConcurrentAccess"
timeout:15.0
error:&error];
// 访
XCTAssertTrue(response != nil || error != nil);
[expectation fulfill];
});
}
}
[self waitForExpectations:expectations timeout:50.0];
}
// M.4
- (void)testEdgeCase_PortMigration_OldConnectionsEventuallyExpire {
if (getenv("SKIP_SLOW_TESTS")) {
return;
}
XCTestExpectation *expectation = [self expectationWithDescription:@"Port migration"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 1 11443
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443"
timeout:15.0
error:&error];
}
// 2 11444
for (NSInteger i = 0; i < 5; i++) {
NSError *error = nil;
[self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444"
timeout:15.0
error:&error];
}
// 30 11443
[NSThread sleepForTimeInterval:31.0];
// 3 11444
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444After"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Port 11444 should still work after 11443 expired");
// 4 11443
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/get"
userAgent:@"Port11443New"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Port 11443 should work with new connection after expiry");
[expectation fulfill];
});
[self waitForExpectations:@[expectation] timeout:120.0];
}
#pragma mark - P.
// P.1
- (void)testTimeout_SingleRequest_ConnectionRemovedFromPool {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty initially");
// delay 10s, timeout 1s
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutTest"
timeout:1.0
error:&error];
//
XCTAssertNil(response, @"Response should be nil on timeout");
XCTAssertNotNil(error, @"Error should be set on timeout");
// returnConnection
[NSThread sleepForTimeInterval:0.5];
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Timed-out connection should be removed from pool");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"No connections should remain in pool");
//
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should have created 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for timed-out connection");
}
// P.2
- (void)testTimeout_PoolRecovery_SubsequentRequestSucceeds {
[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/delay/10"
userAgent:@"TimeoutTest"
timeout:1.0
error:&error1];
XCTAssertNil(response1, @"First request should timeout");
XCTAssertNotNil(error1);
//
[NSThread sleepForTimeInterval:0.5];
//
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Second request should succeed after timeout");
XCTAssertEqual(response2.statusCode, 200);
// returnConnection
[NSThread sleepForTimeInterval:0.5];
// 1
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from second request");
//
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"ReuseTest"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3);
XCTAssertEqual(response3.statusCode, 200);
// 1 + 1 + 1
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (1 timed out, 1 succeeded)");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Third request should reuse second's connection");
}
// P.3
- (void)testTimeout_ConcurrentPartialTimeout_SuccessfulRequestsReuse {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *successCountLock = [[NSLock alloc] init];
NSLock *timeoutCountLock = [[NSLock alloc] init];
__block NSInteger successCount = 0;
__block NSInteger timeoutCount = 0;
// 10 5 5
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
NSString *urlString;
NSTimeInterval timeout;
if (i % 2 == 0) {
//
urlString = @"http://127.0.0.1:11080/get";
timeout = 15.0;
} else {
//
urlString = @"http://127.0.0.1:11080/delay/10";
timeout = 0.5;
}
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentTest"
timeout:timeout
error:&error];
if (response && response.statusCode == 200) {
[successCountLock lock];
successCount++;
[successCountLock unlock];
} else {
[timeoutCountLock lock];
timeoutCount++;
[timeoutCountLock unlock];
}
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:20.0];
//
XCTAssertEqual(successCount, 5, @"5 requests should succeed");
XCTAssertEqual(timeoutCount, 5, @"5 requests should timeout");
// 5 + 5 = 10
XCTAssertGreaterThan(self.client.connectionCreationCount, 0,
@"Should have created connections for concurrent requests");
XCTAssertLessThanOrEqual(self.client.connectionCreationCount, 10,
@"Should not create more than 10 connections");
//
[NSThread sleepForTimeInterval:2.0];
// -
// remote close
XCTAssertLessThanOrEqual([self.client totalConnectionCount], 4,
@"Total connections should not exceed pool limit (no leak)");
//
NSError *recoveryError = nil;
HttpdnsNWHTTPClientResponse *recoveryResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&recoveryError];
XCTAssertNotNil(recoveryResponse, @"Pool should recover and handle new requests after mixed timeout/success");
XCTAssertEqual(recoveryResponse.statusCode, 200, @"Recovery request should succeed");
}
// P.4
- (void)testTimeout_ConsecutiveTimeouts_NoConnectionLeak {
[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/delay/10"
userAgent:@"LeakTest"
timeout:0.5
error:&error];
XCTAssertNil(response, @"Request %ld should timeout", (long)i);
//
[NSThread sleepForTimeInterval:0.2];
}
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Pool should be empty after consecutive timeouts");
XCTAssertEqual([self.client totalConnectionCount], 0,
@"No connections should leak");
//
XCTAssertEqual(self.client.connectionCreationCount, 10,
@"Should have created 10 connections (all timed out and removed)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for timed-out connections");
}
// P.5
- (void)testTimeout_NonBlocking_ConcurrentNormalRequestSucceeds {
[self.client resetPoolStatistics];
XCTestExpectation *timeoutExpectation = [self expectationWithDescription:@"Timeout request"];
XCTestExpectation *successExpectation = [self expectationWithDescription:@"Success request"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Adelay 10s, timeout 2s
dispatch_async(queue, ^{
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/delay/10"
userAgent:@"TimeoutRequest"
timeout:2.0
error:&error];
XCTAssertNil(response, @"Request A should timeout");
[timeoutExpectation fulfill];
});
// B A
dispatch_async(queue, ^{
// A
[NSThread sleepForTimeInterval:0.1];
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSError *error = nil;
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"NormalRequest"
timeout:15.0
error:&error];
CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - startTime;
XCTAssertNotNil(response, @"Request B should succeed despite A timing out");
XCTAssertEqual(response.statusCode, 200);
// B A
XCTAssertLessThan(elapsed, 5.0,
@"Request B should complete quickly, not blocked by A's timeout");
[successExpectation fulfill];
});
[self waitForExpectations:@[timeoutExpectation, successExpectation] timeout:20.0];
//
[NSThread sleepForTimeInterval:0.5];
// B
NSString *poolKey = @"127.0.0.1:11080:tcp";
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Pool should have 1 connection from successful request B");
}
// P.6
- (void)testTimeout_MultiPort_IsolatedPoolCleaning {
[self.client resetPoolStatistics];
NSString *poolKey11443 = @"127.0.0.1:11443:tls";
NSString *poolKey11444 = @"127.0.0.1:11444:tls";
// 11443
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"https://127.0.0.1:11443/delay/10"
userAgent:@"Port11443Timeout"
timeout:1.0
error:&error1];
XCTAssertNil(response1, @"Port 11443 request should timeout");
// 11444
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Success"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2, @"Port 11444 request should succeed");
XCTAssertEqual(response2.statusCode, 200);
//
[NSThread sleepForTimeInterval:0.5];
// 11443 11444 1
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11443], 0,
@"Port 11443 pool should be empty (timed out)");
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey11444], 1,
@"Port 11444 pool should have 1 connection");
//
XCTAssertEqual([self.client totalConnectionCount], 1,
@"Total should be 1 (only from port 11444)");
// 11444
NSError *error3 = nil;
HttpdnsNWHTTPClientResponse *response3 = [self.client performRequestWithURLString:@"https://127.0.0.1:11444/get"
userAgent:@"Port11444Reuse"
timeout:15.0
error:&error3];
XCTAssertNotNil(response3);
XCTAssertEqual(response3.statusCode, 200);
//
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Second request to port 11444 should reuse connection");
}
#pragma mark - R. Connection
// R.1 Connection: close
- (void)testConnectionHeader_Close_ConnectionInvalidated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1 Connection: close
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close"
userAgent:@"CloseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Request with Connection: close should succeed");
XCTAssertEqual(response1.statusCode, 200);
// returnConnection
[NSThread sleepForTimeInterval:0.5];
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Connection: close' header should be invalidated and not returned to pool");
// 2
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAliveTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 2 close
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by server)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse after Connection: close");
}
// R.2 Connection: keep-alive
- (void)testConnectionHeader_KeepAlive_ConnectionReused {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1 Connection: keep-alive
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAlive1"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
//
[NSThread sleepForTimeInterval:0.5];
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 1,
@"Connection with 'Connection: keep-alive' should be returned to pool");
// 2
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"KeepAlive2"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 1
XCTAssertEqual(self.client.connectionCreationCount, 1,
@"Should have created only 1 connection");
XCTAssertEqual(self.client.connectionReuseCount, 1,
@"Second request should reuse the first connection");
}
// R.3 Proxy-Connection: close
- (void)testConnectionHeader_ProxyClose_ConnectionInvalidated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1 Proxy-Connection: close (+ Connection: keep-alive)
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=proxy-close"
userAgent:@"ProxyCloseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1, @"Request with Proxy-Connection: close should succeed");
XCTAssertEqual(response1.statusCode, 200);
// returnConnection
[NSThread sleepForTimeInterval:0.5];
// Proxy-Connection: close
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"Connection with 'Proxy-Connection: close' header should be invalidated");
// 2
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=keep-alive"
userAgent:@"RecoveryTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
// 2
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (first closed by proxy header)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse after Proxy-Connection: close");
}
// R.4 Connection
- (void)testConnectionHeader_CaseInsensitive_AllVariantsWork {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
// 1CONNECTION: CLOSE ()
NSError *error1 = nil;
HttpdnsNWHTTPClientResponse *response1 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-uppercase"
userAgent:@"UppercaseTest"
timeout:15.0
error:&error1];
XCTAssertNotNil(response1);
XCTAssertEqual(response1.statusCode, 200);
[NSThread sleepForTimeInterval:0.5];
// CLOSE
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"'CONNECTION: CLOSE' (uppercase) should also close connection");
// 2Connection: Close ()
NSError *error2 = nil;
HttpdnsNWHTTPClientResponse *response2 = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/connection-test?mode=close-mixed"
userAgent:@"MixedCaseTest"
timeout:15.0
error:&error2];
XCTAssertNotNil(response2);
XCTAssertEqual(response2.statusCode, 200);
[NSThread sleepForTimeInterval:0.5];
//
XCTAssertEqual([self.client connectionPoolCountForKey:poolKey], 0,
@"'Connection: Close' (mixed case) should also close connection");
// 2 close
XCTAssertEqual(self.client.connectionCreationCount, 2,
@"Should have created 2 connections (both closed due to case-insensitive matching)");
XCTAssertEqual(self.client.connectionReuseCount, 0,
@"No reuse for any closed connections");
}
// R.5 close keep-alive
- (void)testConnectionHeader_ConcurrentMixed_CloseAndKeepAliveIsolated {
[self.client resetPoolStatistics];
NSString *poolKey = @"127.0.0.1:11080:tcp";
NSMutableArray<XCTestExpectation *> *expectations = [NSMutableArray array];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLock *closeCountLock = [[NSLock alloc] init];
NSLock *keepAliveCountLock = [[NSLock alloc] init];
__block NSInteger closeCount = 0;
__block NSInteger keepAliveCount = 0;
// 10 5 close5 keep-alive
for (NSInteger i = 0; i < 10; i++) {
XCTestExpectation *expectation = [self expectationWithDescription:[NSString stringWithFormat:@"Request %ld", (long)i]];
[expectations addObject:expectation];
dispatch_async(queue, ^{
NSError *error = nil;
NSString *urlString;
if (i % 2 == 0) {
// close
urlString = @"http://127.0.0.1:11080/connection-test?mode=close";
} else {
// keep-alive
urlString = @"http://127.0.0.1:11080/connection-test?mode=keep-alive";
}
HttpdnsNWHTTPClientResponse *response = [self.client performRequestWithURLString:urlString
userAgent:@"ConcurrentMixed"
timeout:15.0
error:&error];
if (response && response.statusCode == 200) {
if (i % 2 == 0) {
[closeCountLock lock];
closeCount++;
[closeCountLock unlock];
} else {
[keepAliveCountLock lock];
keepAliveCount++;
[keepAliveCountLock unlock];
}
}
[expectation fulfill];
});
}
[self waitForExpectations:expectations timeout:30.0];
//
XCTAssertEqual(closeCount, 5, @"5 close requests should succeed");
XCTAssertEqual(keepAliveCount, 5, @"5 keep-alive requests should succeed");
//
[NSThread sleepForTimeInterval:1.0];
//
// - close
// - keep-alive remote close
// - 4
NSInteger poolSize = [self.client connectionPoolCountForKey:poolKey];
XCTAssertLessThanOrEqual(poolSize, 4,
@"Pool size should not exceed limit (no leak from close connections)");
// close
// >= 6 ( 5 close + 1 keep-alive close )
XCTAssertGreaterThanOrEqual(self.client.connectionCreationCount, 6,
@"Should have created at least 6 connections (5 close + at least 1 keep-alive)");
//
NSError *recoveryError = nil;
HttpdnsNWHTTPClientResponse *recoveryResponse = [self.client performRequestWithURLString:@"http://127.0.0.1:11080/get"
userAgent:@"RecoveryTest"
timeout:15.0
error:&recoveryError];
XCTAssertNotNil(recoveryResponse, @"Pool should still be healthy after mixed close/keep-alive scenario");
XCTAssertEqual(recoveryResponse.statusCode, 200);
}
@end