Skip to content

Commit

Permalink
Add runRequestsInBackgroundThread property to ASINetworkQueue
Browse files Browse the repository at this point in the history
Add back in Snow Leopard detection for deciding whether to spawn a new thread
Made delegate authentication work again in a background thread
Tweak tests
  • Loading branch information
pokeb committed Dec 18, 2009
1 parent fbc34f8 commit 027c38c
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 68 deletions.
65 changes: 45 additions & 20 deletions Classes/ASIHTTPRequest.m
Expand Up @@ -21,7 +21,7 @@
#import "ASIInputStream.h"

// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.2-40 2009-12-17";
NSString *ASIHTTPRequestVersion = @"v1.2-43 2009-12-18";

NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";

Expand Down Expand Up @@ -468,11 +468,37 @@ - (void)startSynchronous

- (void)start
{
#if TARGET_OS_IPHONE
if ([self shouldRunInBackgroundThread]) {
[self performSelectorInBackground:@selector(startAsynchronous) withObject:nil];
} else {
[self startAsynchronous];
}
#else
// If we aren't running in a queue, behave as normal
if (![self queue]) {
if ([self shouldRunInBackgroundThread]) {
[self performSelectorInBackground:@selector(startAsynchronous) withObject:nil];
} else {
[self startAsynchronous];
}
return;
}

SInt32 versionMajor;
OSErr err = Gestalt(gestaltSystemVersionMajor, &versionMajor);
if (err != noErr) {
[NSException raise:@"FailedToDetectOSVersion" format:@"Unable to determine OS version, must give up"];
}
// GCD will run the operation in its thread pool on Snow Leopard
if (versionMajor >= 6) {
[self startAsynchronous];

// On Leopard, we'll create the thread ourselves
} else {
[self performSelectorInBackground:@selector(startAsynchronous) withObject:nil];
}
#endif
}

- (void)startAsynchronous
Expand Down Expand Up @@ -530,7 +556,6 @@ - (BOOL)isExecuting {
// Create the request
- (void)main
{

[self setComplete:NO];

if (![self url]) {
Expand Down Expand Up @@ -735,6 +760,7 @@ - (void)startRequest
[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
}
// Create the stream for the request
readStreamIsScheduled = NO;

// Do we need to stream the request body from disk
if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self postBodyFilePath]]) {
Expand Down Expand Up @@ -840,22 +866,6 @@ - (void)startRequest
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to setup read stream",NSLocalizedDescriptionKey,nil]]];
return;
}

// Schedule the stream
if (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0) {
[self scheduleReadStream];
}


// Start the HTTP connection
if (!CFReadStreamOpen(readStream)) {
[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 (shouldResetProgressIndicators) {
double amount = 1;
Expand All @@ -872,6 +882,14 @@ - (void)startRequest
}
// Record when the request started, so we can timeout if nothing happens
[self setLastActivityTime:[NSDate date]];


// Schedule the stream
if (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0) {
[self scheduleReadStream];
}

[[self cancelledLock] unlock];
}

- (void)loadAsynchronous
Expand All @@ -897,7 +915,7 @@ - (void)loadSynchronous

// This gets fired every 1/4 of a second in asynchronous requests to update the progress and work out if we need to timeout
- (void)updateStatus:(NSTimer*)timer
{
{
[self checkRequestStatus];
if ([self complete] || [self error]) {
[timer invalidate];
Expand All @@ -923,7 +941,7 @@ - (void)checkRequestStatus
[self performThrottling];

// See if we need to timeout
if (readStreamIsScheduled && lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
if (readStream && readStreamIsScheduled && lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {

// Prevent timeouts before 128KB* has been sent when the size of data to upload is greater than 128KB* (*32KB on iPhone 3.0 SDK)
// This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent
Expand Down Expand Up @@ -2356,6 +2374,13 @@ - (void)scheduleReadStream
CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt);
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
readStreamIsScheduled = YES;

// Start the HTTP connection
if (!CFReadStreamOpen(readStream)) {
[self destroyReadStream];
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
return;
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions Classes/ASINetworkQueue.h
Expand Up @@ -59,6 +59,11 @@

// Storage container for additional queue information.
NSDictionary *userInfo;

// When set to YES, the queue will run requests (and HEAD requests it creates) in a background thread
// On Snow Leopard, this does nothing, all requests that run in a queue run in a background thread (via GCD)
// Default is NO
BOOL runRequestsInBackgroundThread;
}

// Convenience constructor
Expand Down Expand Up @@ -117,4 +122,5 @@
@property (assign) unsigned long long bytesDownloadedSoFar;
@property (assign) unsigned long long totalBytesToDownload;

@property (assign) BOOL runRequestsInBackgroundThread;
@end
8 changes: 7 additions & 1 deletion Classes/ASINetworkQueue.m
Expand Up @@ -146,6 +146,9 @@ - (void)addOperation:(NSOperation *)operation
[self setRequestsCount:[self requestsCount]+1];

ASIHTTPRequest *request = (ASIHTTPRequest *)operation;
if ([self runRequestsInBackgroundThread]) {
[request setShouldRunInBackgroundThread:YES];
}

if ([self showAccurateProgress]) {

Expand All @@ -155,6 +158,9 @@ - (void)addOperation:(NSOperation *)operation
// Instead, they'll update the total progress if and when they recieve a content-length header
if ([[request requestMethod] isEqualToString:@"GET"] && [self isSuspended]) {
ASIHTTPRequest *HEADRequest = [request HEADRequest];
if ([self runRequestsInBackgroundThread]) {
[HEADRequest setShouldRunInBackgroundThread:YES];
}
[self addHEADOperation:HEADRequest];

//Tell the request not to reset the progress indicator when it gets a content-length, as we will get the length from the HEAD request
Expand Down Expand Up @@ -345,5 +351,5 @@ - (BOOL)respondsToSelector:(SEL)selector
@synthesize delegate;
@synthesize showAccurateProgress;
@synthesize userInfo;

@synthesize runRequestsInBackgroundThread;
@end
6 changes: 1 addition & 5 deletions Classes/Tests/ASINetworkQueueTests.h
Expand Up @@ -47,7 +47,7 @@ IMPORTANT
BOOL finished;
BOOL failed;
}

- (void)testBackgroundThreadedRequests;
- (void)testFailure;
- (void)testFailureCancelsOtherRequests;
- (void)testDownloadProgress;
Expand All @@ -65,10 +65,6 @@ IMPORTANT
- (void)testMultipleDownloadsThrottlingBandwidth;
- (void)testMultipleUploadsThrottlingBandwidth;

/*
- (void)testCancelStressTest;
*/

- (void)testDelegateAuthenticationCredentialsReuse;
- (void)testPOSTWithAuthentication;

Expand Down
52 changes: 30 additions & 22 deletions Classes/Tests/ASINetworkQueueTests.m
Expand Up @@ -28,6 +28,35 @@ @implementation ASINetworkQueueSubclass

@implementation ASINetworkQueueTests

// Just a santity check to make sure running requests in a background thread still works with the new (1.5) code
- (void)testBackgroundThreadedRequests
{
complete = NO;
ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
[networkQueue setRunRequestsInBackgroundThread:YES];
[networkQueue setDelegate:self];
[networkQueue setQueueDidFinishSelector:@selector(backgroundQueueDone:)];

int i;
for (i=0; i<5; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com"]];
[networkQueue addOperation:request];
}
[networkQueue go];

while (!complete) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
}
}


- (void)backgroundQueueDone:(ASINetworkQueue *)queue
{
complete = YES;
BOOL success = ([NSThread currentThread] == [NSThread mainThread]);
GHAssertTrue(success,@"Failed to call a delegate method in the main thread");
}

- (void)testDelegateAuthenticationCredentialsReuse
{
complete = NO;
Expand Down Expand Up @@ -758,28 +787,7 @@ - (void)stopQueue:(id)sender
complete = YES;
}

// A test for a potential crasher that used to exist when requests were cancelled
// We aren't testing a specific condition here, but rather attempting to trigger a crash
// This test is commented out because it may generate enough load to kill a low-memory server
// PLEASE DO NOT RUN THIS TEST ON A NON-LOCAL SERVER
/*
- (void)testCancelStressTest
{
[self setCancelQueue:[ASINetworkQueue queue]];
// Increase the risk of this crash
[[self cancelQueue] setMaxConcurrentOperationCount:25];
int i;
for (i=0; i<100; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1"]];
[[self cancelQueue] addOperation:request];
}
[[self cancelQueue] go];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[[self cancelQueue] cancelAllOperations];
[self setCancelQueue:nil];
}
*/


// Not strictly an ASINetworkQueue test, but queue related
// As soon as one request finishes or fails, we'll cancel the others and ensure that no requests are both finished and failed
Expand Down
2 changes: 2 additions & 0 deletions Classes/Tests/StressTests.h
Expand Up @@ -26,6 +26,8 @@
NSLock *createRequestLock;
}

- (void)testCancelQueue;

- (void)testCancelStressTest;
- (void)performCancelRequest;

Expand Down
37 changes: 36 additions & 1 deletion Classes/Tests/StressTests.m
Expand Up @@ -15,7 +15,7 @@

#import "StressTests.h"
#import "ASIHTTPRequest.h"

#import "ASINetworkQueue.h"



Expand All @@ -32,6 +32,38 @@ - (void)dealloc

@implementation StressTests

// A test for a potential crasher that used to exist when requests were cancelled
// We aren't testing a specific condition here, but rather attempting to trigger a crash
- (void)testCancelQueue
{
ASINetworkQueue *queue = [ASINetworkQueue queue];

// Increase the risk of this crash
[queue setMaxConcurrentOperationCount:25];
int i;
for (i=0; i<100; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1"]];
[queue addOperation:request];
}
[queue go];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[queue cancelAllOperations];

// Run the test again with requests running on a background thread
queue = [ASINetworkQueue queue];
[queue setRunRequestsInBackgroundThread:YES];

[queue setMaxConcurrentOperationCount:25];
for (i=0; i<100; i++) {
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://127.0.0.1"]];
[queue addOperation:request];
}
[queue go];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[queue cancelAllOperations];

}

// This test looks for thread-safety problems with cancelling requests
// It will run for 30 seconds, creating a request, then cancelling it and creating another as soon as it gets some indication of progress

Expand Down Expand Up @@ -145,6 +177,9 @@ - (void)setProgress:(float)newProgress
}





@synthesize cancelRequest;
@synthesize cancelStartDate;
@synthesize delegate;
Expand Down
1 change: 1 addition & 0 deletions Mac Sample/AppDelegate.m
Expand Up @@ -190,6 +190,7 @@ - (IBAction)fetchTopSecretInformation:(id)sender
ASIHTTPRequest *request;
request = [[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/top_secret/"]] autorelease];
[request setDidFinishSelector:@selector(topSecretFetchComplete:)];
//[request setShouldRunInBackgroundThread:YES];
[request setDelegate:self];
[request setUseKeychainPersistance:[keychainCheckbox state]];
[request start];
Expand Down
3 changes: 0 additions & 3 deletions iPhone Sample/AuthenticationViewController.h
Expand Up @@ -7,11 +7,9 @@
//

#import <UIKit/UIKit.h>
@class ASINetworkQueue;
@class ASIHTTPRequest;

@interface AuthenticationViewController : UIViewController {
ASINetworkQueue *networkQueue;
IBOutlet UISwitch *useKeychain;
IBOutlet UISwitch *useBuiltInDialog;
IBOutlet UILabel *topSecretInfo;
Expand All @@ -20,7 +18,6 @@
}
- (IBAction)fetchTopSecretInformation:(id)sender;

@property (retain) ASINetworkQueue *networkQueue;
@property (retain) ASIHTTPRequest *requestRequiringAuthentication;
@property (retain) ASIHTTPRequest *requestRequiringProxyAuthentication;
@end

0 comments on commit 027c38c

Please sign in to comment.