Ben Copsey

* Hopefully fix problems with requests being cancelled while they are in main/startRequest

* Tweak cancel stress test to make it more likely to catch above issue
* Use an empty CFRunLoopSource instead of a timer to keep request thread runloop from exiting
* Only run the version numbering script if git is available at the path the script expects
* Tweaks
... ... @@ -573,6 +573,8 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
- (void)handleStreamComplete;
- (void)handleStreamError;
#pragma mark persistent connections
// Get the ID of the connection this request used (only really useful in tests and debugging)
- (NSNumber *)connectionID;
... ... @@ -598,7 +600,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
- (NSDictionary *)findSessionProxyAuthenticationCredentials;
- (NSDictionary *)findSessionAuthenticationCredentials;
#pragma mark keychain storage
// Save credentials for this request to the keychain
... ... @@ -687,8 +688,8 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
// Turns on throttling automatically when WWAN is connected using a custom limit, and turns it off automatically when it isn't
+ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit;
#pragma mark reachability
// Returns YES when an iPhone OS device is connected via WWAN, false when connected via WIFI or not connected
+ (BOOL)isNetworkReachableViaWWAN;
... ...
... ... @@ -24,7 +24,7 @@
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.6.2-65 2010-06-23";
NSString *ASIHTTPRequestVersion = @"v1.6.2-66 2010-06-24";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ... @@ -489,6 +489,7 @@ static NSOperationQueue *sharedQueue = nil;
#if DEBUG_REQUEST_STATUS
NSLog(@"Request cancelled: %@",self);
#endif
[[self cancelledLock] lock];
if ([self isCancelled] || [self complete]) {
... ... @@ -499,10 +500,11 @@ static NSOperationQueue *sharedQueue = nil;
[self failWithError:ASIRequestCancelledError];
[self setComplete:YES];
[self cancelLoad];
[[self cancelledLock] unlock];
// Must tell the operation to cancel after we unlock, as this request might be dealloced and then NSLock will log an error
[[self retain] autorelease];
[super cancel];
[[self cancelledLock] unlock];
}
... ... @@ -589,16 +591,21 @@ static NSOperationQueue *sharedQueue = nil;
{
@try {
[self setComplete:NO];
[[self cancelledLock] lock];
// A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
if ([self error]) {
[self failWithError:nil];
[self setComplete:YES];
[self markAsFinished];
[[self cancelledLock] unlock];
return;
}
[self setComplete:NO];
if (![self url]) {
[self failWithError:ASIUnableToCreateRequestError];
[[self cancelledLock] unlock];
return;
}
... ... @@ -621,6 +628,7 @@ static NSOperationQueue *sharedQueue = nil;
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
if (!request) {
[self failWithError:ASIUnableToCreateRequestError];
[[self cancelledLock] unlock];
return;
}
... ... @@ -640,6 +648,7 @@ static NSOperationQueue *sharedQueue = nil;
// See if we should pull from the cache rather than fetching the data
if ([self cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy) {
if ([self useDataFromCache]) {
[[self cancelledLock] unlock];
return;
}
} else if ([self cachePolicy] == ASIReloadIfDifferentCachePolicy) {
... ... @@ -672,6 +681,9 @@ static NSOperationQueue *sharedQueue = nil;
} @catch (NSException *exception) {
NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
} @finally {
[[self cancelledLock] unlock];
}
}
... ... @@ -808,10 +820,7 @@ static NSOperationQueue *sharedQueue = nil;
- (void)startRequest
{
[[self cancelledLock] lock];
if ([self isCancelled]) {
[[self cancelledLock] unlock];
return;
}
... ... @@ -872,7 +881,6 @@ static NSOperationQueue *sharedQueue = nil;
}
if (![self readStream]) {
[[self cancelledLock] unlock];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
return;
}
... ... @@ -923,7 +931,6 @@ static NSOperationQueue *sharedQueue = nil;
if (!proxies) {
[self setReadStream:nil];
[[self cancelledLock] unlock];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]];
return;
}
... ... @@ -1058,17 +1065,13 @@ static NSOperationQueue *sharedQueue = nil;
[oldStream release];
oldStream = nil;
}
if (!streamSuccessfullyOpened) {
[self setConnectionCanBeReused:NO];
[self destroyReadStream];
[[self cancelledLock] unlock];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
return;
}
[[self cancelledLock] unlock];
if (![self mainRequest]) {
if ([self shouldResetUploadProgress]) {
... ... @@ -1115,20 +1118,15 @@ static NSOperationQueue *sharedQueue = nil;
- (void)performRedirect
{
[[self cancelledLock] lock];
// Do we need to redirect?
[self setComplete:YES];
[self setNeedsRedirect:NO];
[self setRedirectCount:[self redirectCount]+1];
if ([self redirectCount] > RedirectionLimit) {
// Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
[self failWithError:ASITooMuchRedirectionError];
[self setComplete:YES];
[[self cancelledLock] unlock];
} else {
[[self cancelledLock] unlock];
// Go all the way back to the beginning and build the request again, so that we can apply any new cookies
[self main];
}
... ... @@ -1572,7 +1570,7 @@ static NSOperationQueue *sharedQueue = nil;
- (void)failWithError:(NSError *)theError
{
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
NSLog(@"Request failed: %@",self);
NSLog(@"Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed"));
#endif
[self setComplete:YES];
... ... @@ -1625,9 +1623,6 @@ static NSOperationQueue *sharedQueue = nil;
// markAsFinished may well cause this object to be dealloced
[self retain];
[self markAsFinished];
if ([self mainRequest]) {
[[self mainRequest] markAsFinished];
}
[self release];
}
... ... @@ -2435,10 +2430,14 @@ static NSOperationQueue *sharedQueue = nil;
- (void)handleNetworkEvent:(CFStreamEventType)type
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[self retain] autorelease];
[[self cancelledLock] lock];
if ([self complete] || [self isCancelled]) {
[[self cancelledLock] unlock];
[pool release];
return;
}
... ... @@ -2466,12 +2465,10 @@ static NSOperationQueue *sharedQueue = nil;
if ([self downloadComplete] && [self needsRedirect]) {
[self performRedirect];
return;
} else if ([self downloadComplete] && [self authenticationNeeded]) {
[self attemptToApplyCredentialsAndResume];
return;
}
[pool release];
}
... ... @@ -2689,7 +2686,6 @@ static NSOperationQueue *sharedQueue = nil;
- (void)markAsFinished
{
// Autoreleased requests may well be dealloced here otherwise
// We use manual retain release rather than [[self retain] autorelease] because the pool is only released when all requests are finished
[self retain];
// dealloc won't be called when running with GC, so we'll clean these up now
... ... @@ -2702,11 +2698,13 @@ static NSOperationQueue *sharedQueue = nil;
if (proxyAuthentication) {
CFMakeCollectable(proxyAuthentication);
}
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
[self setInProgress:NO];
[self setStatusTimer:nil];
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
CFRunLoopStop(CFRunLoopGetCurrent());
... ... @@ -2886,6 +2884,8 @@ static NSOperationQueue *sharedQueue = nil;
}
}
#pragma mark persistent connections
- (NSNumber *)connectionID
{
return [[self connectionInfo] objectForKey:@"id"];
... ... @@ -3827,24 +3827,22 @@ static NSOperationQueue *sharedQueue = nil;
+ (void)runRequests
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a timer that fires extremely infrequently and runs forever to keep the runloop from exiting
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(doNothing:) userInfo:nil repeats:YES];
timer = nil;
// Should keep the runloop from exiting
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
while (1) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CFRunLoopRun();
[pool release];
}
[pool release];
// Should never be called, but anyway
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
+ (void)doNothing:(NSTimer *)timer
{
}
+ (void)setDefaultCache:(id <ASICacheDelegate>)cache
{
[defaultCache release];
... ...
... ... @@ -8,7 +8,6 @@
#import "ASIHTTPRequestTests.h"
#import "ASIHTTPRequest.h"
#import "ASINSStringAdditions.h"
#import "ASINetworkQueue.h"
#import "ASIFormDataRequest.h"
#import <SystemConfiguration/SystemConfiguration.h>
... ... @@ -712,7 +711,7 @@
for (cookie in cookies) {
if ([[cookie name] isEqualToString:@"ASIHTTPRequestTestCookie"]) {
foundCookie = YES;
success = [[[cookie value] decodedCookieValue] isEqualToString:@"This is the value"];
success = [[cookie value] isEqualToString:@"This+is+the+value"];
GHAssertTrue(success,@"Failed to store the correct value for a cookie");
success = [[cookie domain] isEqualToString:@"allseeing-i.com"];
GHAssertTrue(success,@"Failed to store the correct domain for a cookie");
... ... @@ -765,7 +764,7 @@
NSDictionary *cookieProperties = [[[NSMutableDictionary alloc] init] autorelease];
// We'll add a line break to our cookie value to test it gets correctly encoded
[cookieProperties setValue:[@"Test\r\nValue" encodedCookieValue] forKey:NSHTTPCookieValue];
[cookieProperties setValue:@"Test%0D%0AValue" forKey:NSHTTPCookieValue];
[cookieProperties setValue:@"ASIHTTPRequestTestCookie" forKey:NSHTTPCookieName];
[cookieProperties setValue:@"allseeing-i.com" forKey:NSHTTPCookieDomain];
[cookieProperties setValue:[NSDate dateWithTimeIntervalSinceNow:60*60*4] forKey:NSHTTPCookieExpires];
... ...
... ... @@ -46,7 +46,6 @@ IMPORTANT
[queue addOperation:request];
}
[queue go];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[queue cancelAllOperations];
// Run the test again with requests running on a background thread
... ...
This diff was suppressed by a .gitattributes entry.
... ... @@ -101,7 +101,7 @@ static NSString *intro = @"Demonstrates fetching a web page synchronously, the H
switch ([indexPath section]) {
case 1:
urlField = [[[UITextField alloc] initWithFrame:CGRectMake(10,12,tableWidth-tablePadding-40,20)] autorelease];
urlField = [[[UITextField alloc] initWithFrame:CGRectMake(10,12,tableWidth-tablePadding-50,20)] autorelease];
if ([self request]) {
[urlField setText:[[[self request] url] absoluteString]];
} else {
... ...
#!/usr/bin/env ruby
# This script sets a version number for ASIHTTPRequest based on the last commit, and is run when you build one of the targets in the Xcode projects that come with ASIHTTPRequest
# It only really needs to run on my computer, not on yours... :)
require 'find'
newversion = `/opt/local/bin/git describe --tags`.match(/(v([0-9]+)(\.([0-9]+)){1,}-([0-9]+))/).to_s.gsub(/[0-9]+$/){|commit| (commit.to_i + 1).to_s}+Time.now.strftime(" %Y-%m-%d")
buffer = File.new('Classes/ASIHTTPRequest.m','r').read
if !buffer.match(/#{Regexp.quote(newversion)}/)
buffer = buffer.sub(/(NSString \*ASIHTTPRequestVersion = @\")(.*)(";)/,'\1'+newversion+'\3');
File.open('Classes/ASIHTTPRequest.m','w') {|fw| fw.write(buffer)}
end
if (File.exists?('/opt/local/bin/git'))
newversion = `/opt/local/bin/git describe --tags`.match(/(v([0-9]+)(\.([0-9]+)){1,}-([0-9]+))/).to_s.gsub(/[0-9]+$/){|commit| (commit.to_i + 1).to_s}+Time.now.strftime(" %Y-%m-%d")
buffer = File.new('Classes/ASIHTTPRequest.m','r').read
if !buffer.match(/#{Regexp.quote(newversion)}/)
buffer = buffer.sub(/(NSString \*ASIHTTPRequestVersion = @\")(.*)(";)/,'\1'+newversion+'\3');
File.open('Classes/ASIHTTPRequest.m','w') {|fw| fw.write(buffer)}
end
end
\ No newline at end of file
... ...