Ben Copsey

nextStreamNumberToCreate -> nextConnectionNumberToCreate

Add helpful comments to some of the new additions, reinstate refs to the mailing list posts that were so helpful
@@ -318,9 +318,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -318,9 +318,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
318 // Default is YES 318 // Default is YES
319 BOOL shouldPresentCredentialsBeforeChallenge; 319 BOOL shouldPresentCredentialsBeforeChallenge;
320 320
  321 + // YES when the request is run with runSynchronous, NO otherwise. READ-ONLY
321 BOOL isSynchronous; 322 BOOL isSynchronous;
322 323
  324 + // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY
323 BOOL inProgress; 325 BOOL inProgress;
  326 +
  327 + // Used internally to track whether the stream is scheduled on the run loop or not
  328 + // Bandwidth throttling can unschedule the stream to slow things down while a request is in progress
324 BOOL readStreamIsScheduled; 329 BOOL readStreamIsScheduled;
325 330
326 // Set to allow a request to automatically retry itself on timeout 331 // Set to allow a request to automatically retry itself on timeout
@@ -338,8 +343,18 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -338,8 +343,18 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
338 // Set to yes when an appropriate keep-alive header is found 343 // Set to yes when an appropriate keep-alive header is found
339 BOOL canUsePersistentConnection; 344 BOOL canUsePersistentConnection;
340 345
  346 + // Populated with the number of seconds the server told us we could keep a persistent connection around
  347 + // A future date is created from this and used for expiring the connection, this is stored in connectionInfo's expires value
341 NSTimeInterval closeStreamTime; 348 NSTimeInterval closeStreamTime;
342 349
  350 + // Stores information about the persistent connection that is currently in use.
  351 + // It may contain:
  352 + // * The id we set for a particular connection, incremented every time we want to specify that we need a new connection
  353 + // * The date that connection should expire
  354 + // * A host, port and scheme for the connection. These are used to determine whether that connection can be reused by a subsequent request (all must match the new request)
  355 + // * An id for the request that is currently using the connection. This is used for determining if a connection is available or not (we store a number rather than a reference to the request so we don't need to hang onto a request until the connection expires)
  356 + // * A reference to the stream that is currently using the connection. This is necessary because we need to keep the old stream open until we've opened a new one.
  357 + // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire
343 NSMutableDictionary *connectionInfo; 358 NSMutableDictionary *connectionInfo;
344 359
345 } 360 }
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 #import "ASIInputStream.h" 21 #import "ASIInputStream.h"
22 22
23 // Automatically set on build 23 // Automatically set on build
24 -NSString *ASIHTTPRequestVersion = @"v1.2-63 2010-01-05"; 24 +NSString *ASIHTTPRequestVersion = @"v1.2-64 2010-01-05";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -57,9 +57,19 @@ static NSMutableArray *bandwidthUsageTracker = nil; @@ -57,9 +57,19 @@ static NSMutableArray *bandwidthUsageTracker = nil;
57 static unsigned long averageBandwidthUsedPerSecond = 0; 57 static unsigned long averageBandwidthUsedPerSecond = 0;
58 58
59 // These are used for queuing persistent connections on the same connection 59 // These are used for queuing persistent connections on the same connection
60 -static unsigned char nextStreamNumberToCreate = 0; 60 +
  61 +// Incremented every time we specify we want a new connection
  62 +static unsigned int nextConnectionNumberToCreate = 0;
  63 +
  64 +// An array of connectionInfo dictionaries.
  65 +// When attempting a persistent connection, we look here to try to find an existing connection to the same server that is currently not in use
61 static NSMutableArray *persistentConnectionsPool = nil; 66 static NSMutableArray *persistentConnectionsPool = nil;
  67 +
  68 +// Mediates access to the persistent connections pool
62 static NSLock *connectionsLock = nil; 69 static NSLock *connectionsLock = nil;
  70 +
  71 +// Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary.
  72 +// We do this so we don't have to keep the request around while we wait for the connection to expire
63 static unsigned int nextRequestID = 0; 73 static unsigned int nextRequestID = 0;
64 74
65 // Records how much bandwidth all requests combined have used in the last second 75 // Records how much bandwidth all requests combined have used in the last second
@@ -98,8 +108,10 @@ static NSRecursiveLock *sessionCookiesLock = nil; @@ -98,8 +108,10 @@ static NSRecursiveLock *sessionCookiesLock = nil;
98 // This is so it can make use of any credentials supplied for the other request, if they are appropriate 108 // This is so it can make use of any credentials supplied for the other request, if they are appropriate
99 static NSRecursiveLock *delegateAuthenticationLock = nil; 109 static NSRecursiveLock *delegateAuthenticationLock = nil;
100 110
  111 +// When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams
101 static NSDate *throttleWakeUpTime = nil; 112 static NSDate *throttleWakeUpTime = nil;
102 113
  114 +// Run once in initalize to record at runtime whether we're on iPhone OS 2. When YES, a workaround for a type conversion bug in iPhone OS 2.2.x is applied in some places
103 static BOOL isiPhoneOS2; 115 static BOOL isiPhoneOS2;
104 116
105 // Private stuff 117 // Private stuff
@@ -116,8 +128,6 @@ static BOOL isiPhoneOS2; @@ -116,8 +128,6 @@ static BOOL isiPhoneOS2;
116 - (BOOL)askDelegateForProxyCredentials; 128 - (BOOL)askDelegateForProxyCredentials;
117 + (void)measureBandwidthUsage; 129 + (void)measureBandwidthUsage;
118 + (void)recordBandwidthUsage; 130 + (void)recordBandwidthUsage;
119 -  
120 -// Start the read stream. Called by loadRequest, and again to restart the request when authentication is needed  
121 - (void)startRequest; 131 - (void)startRequest;
122 132
123 - (void)markAsFinished; 133 - (void)markAsFinished;
@@ -878,6 +888,7 @@ static BOOL isiPhoneOS2; @@ -878,6 +888,7 @@ static BOOL isiPhoneOS2;
878 // Use a persistent connection if possible 888 // Use a persistent connection if possible
879 if (shouldAttemptPersistentConnection) { 889 if (shouldAttemptPersistentConnection) {
880 890
  891 +
881 // If we are redirecting, we will re-use the current connection only if we are connecting to the same server 892 // If we are redirecting, we will re-use the current connection only if we are connecting to the same server
882 if ([self connectionInfo]) { 893 if ([self connectionInfo]) {
883 894
@@ -894,9 +905,10 @@ static BOOL isiPhoneOS2; @@ -894,9 +905,10 @@ static BOOL isiPhoneOS2;
894 } 905 }
895 } 906 }
896 907
897 - if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { 908 +
898 - // Look for a connection to the same server in the pool 909 + if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
899 910
  911 + // Look for a connection to the same server in the pool
900 NSUInteger i; 912 NSUInteger i;
901 NSMutableDictionary *existingConnection; 913 NSMutableDictionary *existingConnection;
902 for (i=0; i<[persistentConnectionsPool count]; i++) { 914 for (i=0; i<[persistentConnectionsPool count]; i++) {
@@ -924,8 +936,8 @@ static BOOL isiPhoneOS2; @@ -924,8 +936,8 @@ static BOOL isiPhoneOS2;
924 // No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one 936 // No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one
925 if (![self connectionInfo]) { 937 if (![self connectionInfo]) {
926 [self setConnectionInfo:[NSMutableDictionary dictionary]]; 938 [self setConnectionInfo:[NSMutableDictionary dictionary]];
927 - nextStreamNumberToCreate++; 939 + nextConnectionNumberToCreate++;
928 - [[self connectionInfo] setObject:[NSNumber numberWithInt:nextStreamNumberToCreate] forKey:@"id"]; 940 + [[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
929 [[self connectionInfo] setObject:[[self url] host] forKey:@"host"]; 941 [[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
930 [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"]; 942 [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
931 [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"]; 943 [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
@@ -940,6 +952,11 @@ static BOOL isiPhoneOS2; @@ -940,6 +952,11 @@ static BOOL isiPhoneOS2;
940 #if DEBUG_PERSISTENT_CONNECTIONS 952 #if DEBUG_PERSISTENT_CONNECTIONS
941 NSLog(@"Request #%hi will use connection #%hi",nextRequestID,[[[self connectionInfo] objectForKey:@"id"] intValue]); 953 NSLog(@"Request #%hi will use connection #%hi",nextRequestID,[[[self connectionInfo] objectForKey:@"id"] intValue]);
942 #endif 954 #endif
  955 +
  956 +
  957 + // Tag the stream with an id that tells it which connection to use behind the scenes
  958 + // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
  959 +
943 CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]); 960 CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
944 961
945 } 962 }
@@ -963,6 +980,9 @@ static BOOL isiPhoneOS2; @@ -963,6 +980,9 @@ static BOOL isiPhoneOS2;
963 return; 980 return;
964 } 981 }
965 982
  983 + // Here, we'll close the stream that was previously using this connection, if there was one
  984 + // We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection
  985 + // http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
966 if (oldStream) { 986 if (oldStream) {
967 CFStreamStatus status = CFReadStreamGetStatus((CFReadStreamRef)oldStream); 987 CFStreamStatus status = CFReadStreamGetStatus((CFReadStreamRef)oldStream);
968 if (status != kCFStreamStatusClosed && status != kCFStreamStatusError) { 988 if (status != kCFStreamStatusClosed && status != kCFStreamStatusError) {
@@ -1549,6 +1569,9 @@ static BOOL isiPhoneOS2; @@ -1549,6 +1569,9 @@ static BOOL isiPhoneOS2;
1549 [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; 1569 [self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
1550 1570
1551 CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader); 1571 CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader);
  1572 + if (!headers) {
  1573 + return;
  1574 + }
1552 if (CFHTTPMessageIsHeaderComplete(headers)) { 1575 if (CFHTTPMessageIsHeaderComplete(headers)) {
1553 1576
1554 CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(headers); 1577 CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(headers);
@@ -2322,6 +2345,12 @@ static BOOL isiPhoneOS2; @@ -2322,6 +2345,12 @@ static BOOL isiPhoneOS2;
2322 2345
2323 - (void)handleBytesAvailable 2346 - (void)handleBytesAvailable
2324 { 2347 {
  2348 + // In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available
  2349 + // We'll check that there is actually data available to prevent blocking on CFReadStreamRead()
  2350 + // So far, I've only seen this in the stress tests, so it might never happen in real-world situations.
  2351 + if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) {
  2352 + return;
  2353 + }
2325 if (![self responseHeaders]) { 2354 if (![self responseHeaders]) {
2326 [self readResponseHeaders]; 2355 [self readResponseHeaders];
2327 } 2356 }
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 #define DEBUG_THROTTLING 0 21 #define DEBUG_THROTTLING 0
22 22
23 // When set to 1, ASIHTTPRequests will print information about persistent connections to the console 23 // When set to 1, ASIHTTPRequests will print information about persistent connections to the console
24 -#define DEBUG_PERSISTENT_CONNECTIONS 1 24 +#define DEBUG_PERSISTENT_CONNECTIONS 0
25 25
26 // ====== 26 // ======
27 // Reachability API (iPhone only) 27 // Reachability API (iPhone only)
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 #import "PerformanceTests.h" 9 #import "PerformanceTests.h"
10 #import "ASIHTTPRequest.h" 10 #import "ASIHTTPRequest.h"
11 11
  12 +// IMPORTANT - these tests need to be run one at a time!
12 13
13 @interface NSURLConnectionSubclass : NSURLConnection { 14 @interface NSURLConnectionSubclass : NSURLConnection {
14 int tag; 15 int tag;