Why cancelled AFHTTPRequestOperation sometimes hit the success block?
Asked Answered
T

2

7

I'm integrating autocomplete on a search bar through Google Places API. And for the networking requests, I use AFNetworking.

I want to have only one request running at a time. So everytime I type a new letter, I do the following:

1 - Cancel the previous AFHTTPRequestOperation:

[self.currentGlobalSearchListRequest cancel];
NSLog(@"%@ cancelled", self.currentGlobalSearchListRequest);

2 - Start a new AFHTTPRequestOperation:

self.currentGlobalSearchListRequest = [searchService getGlobalSearchListItemsForString:searchString inRegion:region delegate:self];
NSLog(@"%@ started", self.currentGlobalSearchListRequest);

Here is the callback called when the request has finished running:

- (void)request:(PointSearchRequest *)request didLoadSearchListItems:(NSArray *)searchListItems {
    NSAssert(request == self.currentGlobalSearchListRequest, @"!!!callback from a request that should be cancelled!!!");
    [self.delegate searchListLoader:self didLoadGlobalSearchList:searchListItems];
}

Sometimes I hit the assertion, so I investigated a bit and discovered that most of the times the failure block is called with an error code NSURLErrorCancelled which is the expected behavior but sometimes, the success block is called!

This stack trace tells me that things in my code occurred in the right order

2013-12-22 09:38:46.484 Point[63595:a0b] <PointSearchRequest: 0x18202b50> started
2013-12-22 09:38:46.486 Point[63595:a0b] <PointSearchRequest: 0x18202b50> cancelled
2013-12-22 09:38:46.487 Point[63595:a0b] <PointSearchRequest: 0x181a5dd0> started
2013-12-22 09:38:46.496 Point[63595:a0b] *** Assertion failure in -[SearchListLoader request:didLoadSearchListItems:], /Users/aurelienporte/Documents/Developpement/Perso/iOS/Point/Point/Classes/Models/SearchListLoader.m:82
(lldb) po request
<PointSearchRequest: 0x18202b50>

Plus, I looked at the property isCancelled on AFHTTPRequestOperation when success block is called but it gives me NO (!!!) I know I could end up just testing instead of using NSAssert but would like to find the origin of the problem.

Have you ever encountered similar issues where cancel does not actually cancel the request? And then the success block is called instead of the failure block? Is this an issue to report to AFNetworking team? Thanks!

If it can help, the requests are freaking fast (Google autocomplete aPI is impressive...)

Traveled answered 22/12, 2013 at 9:24 Comment(1)
I'd expect this. Cancelling a completed operation that hasn't fired its completion handler is ambiguous; it could either fire the completion handler or accept the cancel. I wouldn't count on either one happening 100% of the time.Ladonnalady
F
1

Looking into AFNetworkingCode, see AFURLConnectionOperation.m, line 461

- (void)cancel {
    [self.lock lock];
    if (![self isFinished] && ![self isCancelled]) {
        [super cancel];

        if ([self isExecuting]) {
            [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        }
    }
    [self.lock unlock];
}

It seems that the only possibility for the race condition you are seeing is the case when the operation has already been finished ([self isFinished]). Note that the interval between your cancel and completion block is very small (10 ms). Maybe you could check isFinished before trying to cancel the request?

Fulmer answered 22/12, 2013 at 15:41 Comment(2)
I looked at this and indeed, the operation is finished in the situations where my NSAssert fires. Thanks. It means that there is a delay between the time where isFinished is set to YES and the time where success or failure block are called back. I guess it's because these two "moments" don't occur in the same thread, right?Traveled
@AurelienPorte Yes. NSOperation should call its completionBlock almost immediately but there are two more dispatch_async before your success or failure block is called. Every dispatch_async will postpone the execution a bit. That's because AFNetworking uses several dispatch queues and the execution has to go through all of them before reaching your success block.Fulmer
M
0

As we all know, a thread function will go no although you cancel the thread in the midway, when it start, it will complete the method. So as the request. That is, if the request is still in the NSOperationQueue, you can cancel it, but as long as you summit the operation,and the block is copy,it will call back.

If you don't want to accept the call back, you can just in the call back to tell whether the request is canceled.

Hope that it will help you.

Mania answered 22/12, 2013 at 14:10 Comment(1)
Thanks. I agree in the general case. And this is supposed to be handled by AFNetworking library itself so that it's my failure block that should be called.Traveled

© 2022 - 2024 — McMap. All rights reserved.