nextStreamNumberToCreate -> nextConnectionNumberToCreate
Add helpful comments to some of the new additions, reinstate refs to the mailing list posts that were so helpful
Showing
4 changed files
with
54 additions
and
9 deletions
| @@ -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; |
-
Please register or login to post a comment