Fix a problem where both finished and failed delegate methods could in some circ…
…umstances be called for the same request Thanks to Mike DeSaro for catching this nasty bug!
Showing
5 changed files
with
97 additions
and
49 deletions
@@ -263,6 +263,10 @@ static NSError *ASITooMuchRedirectionError; | @@ -263,6 +263,10 @@ static NSError *ASITooMuchRedirectionError; | ||
263 | 263 | ||
264 | - (void)cancel | 264 | - (void)cancel |
265 | { | 265 | { |
266 | + // Request may already be complete | ||
267 | + if ([self complete] || [self isCancelled]) { | ||
268 | + return; | ||
269 | + } | ||
266 | [self failWithError:ASIRequestCancelledError]; | 270 | [self failWithError:ASIRequestCancelledError]; |
267 | [super cancel]; | 271 | [super cancel]; |
268 | [self cancelLoad]; | 272 | [self cancelLoad]; |
@@ -897,7 +901,7 @@ static NSError *ASITooMuchRedirectionError; | @@ -897,7 +901,7 @@ static NSError *ASITooMuchRedirectionError; | ||
897 | // If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done | 901 | // If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done |
898 | - (void)requestFinished | 902 | - (void)requestFinished |
899 | { | 903 | { |
900 | - if ([self isCancelled] || [self mainRequest]) { | 904 | + if ([self error] || [self mainRequest]) { |
901 | return; | 905 | return; |
902 | } | 906 | } |
903 | // Let the queue know we are done | 907 | // Let the queue know we are done |
@@ -917,36 +921,32 @@ static NSError *ASITooMuchRedirectionError; | @@ -917,36 +921,32 @@ static NSError *ASITooMuchRedirectionError; | ||
917 | { | 921 | { |
918 | [self setComplete:YES]; | 922 | [self setComplete:YES]; |
919 | 923 | ||
920 | - if ([self isCancelled]) { | 924 | + if ([self isCancelled] || [self error]) { |
921 | return; | 925 | return; |
922 | } | 926 | } |
923 | 927 | ||
924 | - if (!error) { | 928 | + // If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail |
929 | + if ([self mainRequest]) { | ||
930 | + ASIHTTPRequest *mRequest = [self mainRequest]; | ||
931 | + [mRequest setError:theError]; | ||
932 | + | ||
933 | + // Let the queue know something went wrong | ||
934 | + if ([queue respondsToSelector:@selector(requestDidFail:)]) { | ||
935 | + [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:mRequest waitUntilDone:[NSThread isMainThread]]; | ||
936 | + } | ||
937 | + | ||
938 | + } else { | ||
939 | + [self setError:theError]; | ||
925 | 940 | ||
926 | - // If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail | 941 | + // Let the queue know something went wrong |
927 | - if ([self mainRequest]) { | 942 | + if ([queue respondsToSelector:@selector(requestDidFail:)]) { |
928 | - ASIHTTPRequest *mRequest = [self mainRequest]; | 943 | + [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:self waitUntilDone:[NSThread isMainThread]]; |
929 | - [mRequest setError:theError]; | 944 | + } |
930 | - | ||
931 | - // Let the queue know something went wrong | ||
932 | - if ([queue respondsToSelector:@selector(requestDidFail:)]) { | ||
933 | - [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:mRequest waitUntilDone:[NSThread isMainThread]]; | ||
934 | - } | ||
935 | 945 | ||
936 | - } else { | 946 | + // Let the delegate know something went wrong |
937 | - [self setError:theError]; | 947 | + if (didFailSelector && [delegate respondsToSelector:didFailSelector]) { |
938 | - | 948 | + [delegate performSelectorOnMainThread:didFailSelector withObject:self waitUntilDone:[NSThread isMainThread]]; |
939 | - // Let the queue know something went wrong | ||
940 | - if ([queue respondsToSelector:@selector(requestDidFail:)]) { | ||
941 | - [queue performSelectorOnMainThread:@selector(requestDidFail:) withObject:self waitUntilDone:[NSThread isMainThread]]; | ||
942 | - } | ||
943 | - | ||
944 | - // Let the delegate know something went wrong | ||
945 | - if (didFailSelector && [delegate respondsToSelector:didFailSelector]) { | ||
946 | - [delegate performSelectorOnMainThread:didFailSelector withObject:self waitUntilDone:[NSThread isMainThread]]; | ||
947 | - } | ||
948 | } | 949 | } |
949 | - | ||
950 | } | 950 | } |
951 | } | 951 | } |
952 | 952 |
@@ -13,15 +13,17 @@ | @@ -13,15 +13,17 @@ | ||
13 | #endif | 13 | #endif |
14 | 14 | ||
15 | @class ASIHTTPRequest; | 15 | @class ASIHTTPRequest; |
16 | -@class ASINetworkQueue; | ||
17 | 16 | ||
18 | @interface ASINetworkQueueTests : GHTestCase { | 17 | @interface ASINetworkQueueTests : GHTestCase { |
19 | ASIHTTPRequest *requestThatShouldFail; | 18 | ASIHTTPRequest *requestThatShouldFail; |
20 | - ASINetworkQueue *networkQueue; | ||
21 | BOOL complete; | 19 | BOOL complete; |
22 | BOOL request_didfail; | 20 | BOOL request_didfail; |
23 | BOOL request_succeeded; | 21 | BOOL request_succeeded; |
24 | float progress; | 22 | float progress; |
23 | + | ||
24 | + NSOperationQueue *immediateCancelQueue; | ||
25 | + NSMutableArray *failedRequests; | ||
26 | + NSMutableArray *finishedRequests; | ||
25 | } | 27 | } |
26 | 28 | ||
27 | - (void)testFailure; | 29 | - (void)testFailure; |
@@ -31,7 +33,11 @@ | @@ -31,7 +33,11 @@ | ||
31 | - (void)testProgressWithAuthentication; | 33 | - (void)testProgressWithAuthentication; |
32 | - (void)testWithNoListener; | 34 | - (void)testWithNoListener; |
33 | - (void)testPartialResume; | 35 | - (void)testPartialResume; |
36 | +- (void)testImmediateCancel; | ||
34 | 37 | ||
35 | - (void)setProgress:(float)newProgress; | 38 | - (void)setProgress:(float)newProgress; |
36 | 39 | ||
40 | +@property (retain) NSOperationQueue *immediateCancelQueue; | ||
41 | +@property (retain) NSMutableArray *failedRequests; | ||
42 | +@property (retain) NSMutableArray *finishedRequests; | ||
37 | @end | 43 | @end |
@@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
19 | complete = NO; | 19 | complete = NO; |
20 | progress = 0; | 20 | progress = 0; |
21 | 21 | ||
22 | - networkQueue = [[ASINetworkQueue queue] retain]; | 22 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
23 | [networkQueue setDownloadProgressDelegate:self]; | 23 | [networkQueue setDownloadProgressDelegate:self]; |
24 | [networkQueue setDelegate:self]; | 24 | [networkQueue setDelegate:self]; |
25 | [networkQueue setShowAccurateProgress:NO]; | 25 | [networkQueue setShowAccurateProgress:NO]; |
@@ -73,9 +73,6 @@ | @@ -73,9 +73,6 @@ | ||
73 | success = (progress > 0.95); | 73 | success = (progress > 0.95); |
74 | GHAssertTrue(success,@"Failed to increment progress properly"); | 74 | GHAssertTrue(success,@"Failed to increment progress properly"); |
75 | 75 | ||
76 | - | ||
77 | - [networkQueue release]; | ||
78 | - | ||
79 | } | 76 | } |
80 | 77 | ||
81 | - (void)testUploadProgress | 78 | - (void)testUploadProgress |
@@ -83,7 +80,7 @@ | @@ -83,7 +80,7 @@ | ||
83 | complete = NO; | 80 | complete = NO; |
84 | progress = 0; | 81 | progress = 0; |
85 | 82 | ||
86 | - networkQueue = [[ASINetworkQueue alloc] init]; | 83 | + ASINetworkQueue *networkQueue = [[[ASINetworkQueue alloc] init] autorelease]; |
87 | [networkQueue setUploadProgressDelegate:self]; | 84 | [networkQueue setUploadProgressDelegate:self]; |
88 | [networkQueue setDelegate:self]; | 85 | [networkQueue setDelegate:self]; |
89 | [networkQueue setShowAccurateProgress:NO]; | 86 | [networkQueue setShowAccurateProgress:NO]; |
@@ -135,8 +132,6 @@ | @@ -135,8 +132,6 @@ | ||
135 | success = (progress > 0.95); | 132 | success = (progress > 0.95); |
136 | GHAssertTrue(success,@"Failed to increment progress properly"); | 133 | GHAssertTrue(success,@"Failed to increment progress properly"); |
137 | 134 | ||
138 | - [networkQueue release]; | ||
139 | - | ||
140 | } | 135 | } |
141 | 136 | ||
142 | 137 | ||
@@ -153,7 +148,7 @@ | @@ -153,7 +148,7 @@ | ||
153 | { | 148 | { |
154 | complete = NO; | 149 | complete = NO; |
155 | 150 | ||
156 | - networkQueue = [[ASINetworkQueue alloc] init]; | 151 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
157 | [networkQueue setDelegate:self]; | 152 | [networkQueue setDelegate:self]; |
158 | [networkQueue setRequestDidFailSelector:@selector(requestFailed:)]; | 153 | [networkQueue setRequestDidFailSelector:@selector(requestFailed:)]; |
159 | [networkQueue setQueueDidFinishSelector:@selector(queueFinished:)]; | 154 | [networkQueue setQueueDidFinishSelector:@selector(queueFinished:)]; |
@@ -224,7 +219,7 @@ | @@ -224,7 +219,7 @@ | ||
224 | { | 219 | { |
225 | complete = NO; | 220 | complete = NO; |
226 | 221 | ||
227 | - networkQueue = [[ASINetworkQueue alloc] init]; | 222 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
228 | [networkQueue setDelegate:self]; | 223 | [networkQueue setDelegate:self]; |
229 | [networkQueue setRequestDidFailSelector:@selector(requestFailedCancellingOthers:)]; | 224 | [networkQueue setRequestDidFailSelector:@selector(requestFailedCancellingOthers:)]; |
230 | [networkQueue setQueueDidFinishSelector:@selector(queueFinished:)]; | 225 | [networkQueue setQueueDidFinishSelector:@selector(queueFinished:)]; |
@@ -278,7 +273,7 @@ | @@ -278,7 +273,7 @@ | ||
278 | complete = NO; | 273 | complete = NO; |
279 | progress = 0; | 274 | progress = 0; |
280 | 275 | ||
281 | - networkQueue = [[ASINetworkQueue alloc] init]; | 276 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
282 | [networkQueue setDownloadProgressDelegate:self]; | 277 | [networkQueue setDownloadProgressDelegate:self]; |
283 | [networkQueue setDelegate:self]; | 278 | [networkQueue setDelegate:self]; |
284 | [networkQueue setShowAccurateProgress:YES]; | 279 | [networkQueue setShowAccurateProgress:YES]; |
@@ -299,11 +294,10 @@ | @@ -299,11 +294,10 @@ | ||
299 | 294 | ||
300 | NSError *error = [request error]; | 295 | NSError *error = [request error]; |
301 | GHAssertNotNil(error,@"The HEAD request failed, but it didn't tell the main request to fail"); | 296 | GHAssertNotNil(error,@"The HEAD request failed, but it didn't tell the main request to fail"); |
302 | - [networkQueue release]; | ||
303 | 297 | ||
304 | complete = NO; | 298 | complete = NO; |
305 | progress = 0; | 299 | progress = 0; |
306 | - networkQueue = [[ASINetworkQueue alloc] init]; | 300 | + networkQueue = [ASINetworkQueue queue]; |
307 | [networkQueue setDownloadProgressDelegate:self]; | 301 | [networkQueue setDownloadProgressDelegate:self]; |
308 | [networkQueue setDelegate:self]; | 302 | [networkQueue setDelegate:self]; |
309 | [networkQueue setShowAccurateProgress:YES]; | 303 | [networkQueue setShowAccurateProgress:YES]; |
@@ -322,7 +316,6 @@ | @@ -322,7 +316,6 @@ | ||
322 | 316 | ||
323 | error = [request error]; | 317 | error = [request error]; |
324 | GHAssertNil(error,@"Failed to use authentication in a queue"); | 318 | GHAssertNil(error,@"Failed to use authentication in a queue"); |
325 | - [networkQueue release]; | ||
326 | 319 | ||
327 | } | 320 | } |
328 | 321 | ||
@@ -345,7 +338,7 @@ | @@ -345,7 +338,7 @@ | ||
345 | { | 338 | { |
346 | request_succeeded = NO; | 339 | request_succeeded = NO; |
347 | request_didfail = NO; | 340 | request_didfail = NO; |
348 | - networkQueue = [[ASINetworkQueue alloc] init]; | 341 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
349 | [networkQueue setDownloadProgressDelegate:self]; | 342 | [networkQueue setDownloadProgressDelegate:self]; |
350 | [networkQueue setDelegate:self]; | 343 | [networkQueue setDelegate:self]; |
351 | [networkQueue setShowAccurateProgress:YES]; | 344 | [networkQueue setShowAccurateProgress:YES]; |
@@ -364,7 +357,6 @@ | @@ -364,7 +357,6 @@ | ||
364 | // This test may fail if you are using a proxy and it returns a page when you try to connect to a bad port. | 357 | // This test may fail if you are using a proxy and it returns a page when you try to connect to a bad port. |
365 | GHAssertTrue(!request_succeeded && request_didfail,@"Request to resource without listener succeeded but should have failed"); | 358 | GHAssertTrue(!request_succeeded && request_didfail,@"Request to resource without listener succeeded but should have failed"); |
366 | 359 | ||
367 | - [networkQueue release]; | ||
368 | } | 360 | } |
369 | 361 | ||
370 | - (void)testPartialResume | 362 | - (void)testPartialResume |
@@ -383,7 +375,7 @@ | @@ -383,7 +375,7 @@ | ||
383 | } | 375 | } |
384 | 376 | ||
385 | NSURL *downloadURL = [NSURL URLWithString:@"http://trails-network.net/Downloads/MemexTrails_1.0b1.zip"]; | 377 | NSURL *downloadURL = [NSURL URLWithString:@"http://trails-network.net/Downloads/MemexTrails_1.0b1.zip"]; |
386 | - networkQueue = [[ASINetworkQueue alloc] init]; | 378 | + ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
387 | 379 | ||
388 | ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:downloadURL] autorelease]; | 380 | ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:downloadURL] autorelease]; |
389 | [request setDownloadDestinationPath:downloadPath]; | 381 | [request setDownloadDestinationPath:downloadPath]; |
@@ -402,8 +394,7 @@ | @@ -402,8 +394,7 @@ | ||
402 | // 5 seconds is up, let's tell the queue to stop | 394 | // 5 seconds is up, let's tell the queue to stop |
403 | [networkQueue cancelAllOperations]; | 395 | [networkQueue cancelAllOperations]; |
404 | 396 | ||
405 | - [networkQueue release]; | 397 | + networkQueue = [ASINetworkQueue queue]; |
406 | - networkQueue = [[ASINetworkQueue alloc] init]; | ||
407 | [networkQueue setDownloadProgressDelegate:self]; | 398 | [networkQueue setDownloadProgressDelegate:self]; |
408 | [networkQueue setShowAccurateProgress:YES]; | 399 | [networkQueue setShowAccurateProgress:YES]; |
409 | [networkQueue setDelegate:self]; | 400 | [networkQueue setDelegate:self]; |
@@ -435,13 +426,12 @@ | @@ -435,13 +426,12 @@ | ||
435 | success = (progress > 0.95); | 426 | success = (progress > 0.95); |
436 | GHAssertTrue(success,@"Failed to increment progress properly"); | 427 | GHAssertTrue(success,@"Failed to increment progress properly"); |
437 | 428 | ||
438 | - [networkQueue release]; | 429 | + |
439 | - | ||
440 | 430 | ||
441 | //Test the temporary file cleanup | 431 | //Test the temporary file cleanup |
442 | complete = NO; | 432 | complete = NO; |
443 | progress = 0; | 433 | progress = 0; |
444 | - networkQueue = [[ASINetworkQueue alloc] init]; | 434 | + networkQueue = [ASINetworkQueue queue]; |
445 | [networkQueue setDownloadProgressDelegate:self]; | 435 | [networkQueue setDownloadProgressDelegate:self]; |
446 | [networkQueue setShowAccurateProgress:YES]; | 436 | [networkQueue setShowAccurateProgress:YES]; |
447 | [networkQueue setDelegate:self]; | 437 | [networkQueue setDelegate:self]; |
@@ -470,7 +460,6 @@ | @@ -470,7 +460,6 @@ | ||
470 | GHAssertTrue(success,@"Temporary download file should have been deleted"); | 460 | GHAssertTrue(success,@"Temporary download file should have been deleted"); |
471 | 461 | ||
472 | timeoutTimer = nil; | 462 | timeoutTimer = nil; |
473 | - [networkQueue release]; | ||
474 | 463 | ||
475 | } | 464 | } |
476 | 465 | ||
@@ -480,5 +469,55 @@ | @@ -480,5 +469,55 @@ | ||
480 | } | 469 | } |
481 | 470 | ||
482 | 471 | ||
472 | +// Not strictly an ASINetworkQueue test, but queue related | ||
473 | +// As soon as one request finishes or fails, we'll cancel the others and ensure that no requests are both finished and failed | ||
474 | +- (void)testImmediateCancel | ||
475 | +{ | ||
476 | + [self setFailedRequests:[[[NSMutableArray alloc] init] autorelease]]; | ||
477 | + [self setFinishedRequests:[[[NSMutableArray alloc] init] autorelease]]; | ||
478 | + [self setImmediateCancelQueue:[[[NSOperationQueue alloc] init] autorelease]]; | ||
479 | + int i; | ||
480 | + for (i=0; i<100; i++) { | ||
481 | + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://asi"]]; | ||
482 | + [request setDelegate:self]; | ||
483 | + [request setDidFailSelector:@selector(immediateCancelFail:)]; | ||
484 | + [request setDidFinishSelector:@selector(immediateCancelFinish:)]; | ||
485 | + [[self immediateCancelQueue] addOperation:request]; | ||
486 | + } | ||
487 | + | ||
488 | +} | ||
489 | + | ||
490 | +- (void)immediateCancelFail:(ASIHTTPRequest *)request | ||
491 | +{ | ||
492 | + [[self immediateCancelQueue] cancelAllOperations]; | ||
493 | + if ([[self failedRequests] containsObject:request]) { | ||
494 | + GHFail(@"A request called its fail delegate method twice"); | ||
495 | + } | ||
496 | + if ([[self finishedRequests] containsObject:request]) { | ||
497 | + GHFail(@"A request that had already finished called its fail delegate method"); | ||
498 | + } | ||
499 | + [[self failedRequests] addObject:request]; | ||
500 | + if ([[self failedRequests] count]+[[self finishedRequests] count] > 100) { | ||
501 | + GHFail(@"We got more than 100 delegate fail/finish calls - this shouldn't happen!"); | ||
502 | + } | ||
503 | +} | ||
504 | + | ||
505 | +- (void)immediateCancelFinish:(ASIHTTPRequest *)request | ||
506 | +{ | ||
507 | + [[self immediateCancelQueue] cancelAllOperations]; | ||
508 | + if ([[self finishedRequests] containsObject:request]) { | ||
509 | + GHFail(@"A request called its finish delegate method twice"); | ||
510 | + } | ||
511 | + if ([[self failedRequests] containsObject:request]) { | ||
512 | + GHFail(@"A request that had already failed called its finish delegate method"); | ||
513 | + } | ||
514 | + [[self finishedRequests] addObject:request]; | ||
515 | + if ([[self failedRequests] count]+[[self finishedRequests] count] > 100) { | ||
516 | + GHFail(@"We got more than 100 delegate fail/finish calls - this shouldn't happen!"); | ||
517 | + } | ||
518 | +} | ||
483 | 519 | ||
520 | +@synthesize immediateCancelQueue; | ||
521 | +@synthesize failedRequests; | ||
522 | +@synthesize finishedRequests; | ||
484 | @end | 523 | @end |
-
Please register or login to post a comment