6

I'm having a really hard time when trying to cancel requests in an NSOperationQueue.

Before deallocating my 'engine' object, I call a cancelOperations method to cancel everything in the queue, so this will include in flight ASIHTTPRequests and queued ones... e.g.

Engine.m

-(void)getContent {
    if (![self queue]) {
        [self setQueue:[[[NSOperationQueue alloc] init] autorelease]];
    }

    NSURL *url = [NSURL URLWithString:@"http://allseeing-i.com"];
    ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
    [request setDelegate:self];
    [request setDidFinishSelector:@selector(requestDone:)];
    [[self queue] addOperation:request]; //queue is an NSOperationQueue
}

-(void)requestDone:(ASIHTTPRequest)*request {

    // Do something with request
}

-(void)cancelOperations {
    [self.queue cancelAllOperations];
    [self.queue waitUntilAllOperationsAreFinished];
}

-(void)dealloc {
    [super dealloc];
}

Now, In my engine I have a number of getContent type methods, so my queue has different request objects in it. The flow when using an Engine object is:

1) Open View Controller - view did load - alloc and init Engine object
2) // Call various getContent style methods to queue up some operations
3) Upon view exit, call [engine cancelOperations]; in order to (a) Stop any network requests in flight, and to also empty the queue
4) Deallocate the view, and with that the engine: [engine release];

This works fine if all requests have finished in the queue (as it's empty), however if I cancelOperations whilst a request is active, the application crashes with a EXC_BAD_ACCESS error... but AFTER the engine is deallocated successfully...

Any ideas why this would be?

--EDIT-- Adding Backtrace for error:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00000000b0000000
Crashed Thread:  0  Dispatch queue: com.apple.main-thread

Application Specific Information:
objc_msgSend() selector name: respondsToSelector:
iPhone Simulator 225, iPhone OS 4.1 (iPhone 4/8B117)

Thread 0 Crashed:  Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 0x02cb5907 objc_msgSend + 27
1   CoreFoundation                  0x02ac95cd __invoking___ + 29
2   CoreFoundation                  0x02ac94a1 -[NSInvocation invoke] + 145
3   Foundation                      0x0015a3ca __NSThreadPerformPerform + 251
4   CoreFoundation                  0x02b39faf __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
5   CoreFoundation                  0x02a9839b __CFRunLoopDoSources0 + 571
6   CoreFoundation                  0x02a97896 __CFRunLoopRun + 470
7   CoreFoundation                  0x02a97350 CFRunLoopRunSpecific + 208
8   CoreFoundation                  0x02a97271 CFRunLoopRunInMode + 97
9   GraphicsServices                0x0320c00c GSEventRunModal + 217
10  GraphicsServices                0x0320c0d1 GSEventRun + 115
11  UIKit                           0x003e9af2 UIApplicationMain + 1160
12  Clicky                          0x000027d4 main + 102 (main.m:14)
13  Clicky                          0x00002765 start + 53

Thread 1:  Dispatch queue: com.apple.libdispatch-manager
0   libSystem.B.dylib               0x97cfe942 kevent + 10
1   libSystem.B.dylib               0x97cff05c _dispatch_mgr_invoke + 215
2   libSystem.B.dylib               0x97cfe519 _dispatch_queue_invoke + 163
3   libSystem.B.dylib               0x97cfe2be _dispatch_worker_thread2 + 240
4   libSystem.B.dylib               0x97cfdd41 _pthread_wqthread + 390
5   libSystem.B.dylib               0x97cfdb86 start_wqthread + 30

Thread 2:
0   libSystem.B.dylib               0x97cfd9d2 __workq_kernreturn + 10
1   libSystem.B.dylib               0x97cfdf68 _pthread_wqthread + 941
2   libSystem.B.dylib               0x97cfdb86 start_wqthread + 30

Thread 3:  WebThread
0   libSystem.B.dylib               0x97cd80fa mach_msg_trap + 10
1   libSystem.B.dylib               0x97cd8867 mach_msg + 68
2   CoreFoundation                  0x02b3a436 __CFRunLoopServiceMachPort + 150
3   CoreFoundation                  0x02a97984 __CFRunLoopRun + 708
4   CoreFoundation                  0x02a97350 CFRunLoopRunSpecific + 208
5   CoreFoundation                  0x02a97271 CFRunLoopRunInMode + 97
6   WebCore                         0x034093a3 RunWebThread(void*) + 483
7   libSystem.B.dylib               0x97d0581d _pthread_start + 345
8   libSystem.B.dylib               0x97d056a2 thread_start + 34

Thread 4:
0   libSystem.B.dylib               0x97cfd9d2 __workq_kernreturn + 10
1   libSystem.B.dylib               0x97cfdf68 _pthread_wqthread + 941
2   libSystem.B.dylib               0x97cfdb86 start_wqthread + 30

Thread 5:
0   libSystem.B.dylib               0x97cd80fa mach_msg_trap + 10
1   libSystem.B.dylib               0x97cd8867 mach_msg + 68
2   CoreFoundation                  0x02b3a436 __CFRunLoopServiceMachPort + 150
3   CoreFoundation                  0x02a97984 __CFRunLoopRun + 708
4   CoreFoundation                  0x02a97350 CFRunLoopRunSpecific + 208
5   CoreFoundation                  0x02a9a614 CFRunLoopRun + 84
6   Clicky                          0x0001fdb7 +[ASIHTTPRequest runRequests] + 167 (ASIHTTPRequest.m:4093)
7   Foundation                      0x0014576c -[NSThread main] + 81
8   Foundation                      0x001456f8 __NSThread__main__ + 1387
9   libSystem.B.dylib               0x97d0581d _pthread_start + 345
10  libSystem.B.dylib               0x97d056a2 thread_start + 34

Thread 6:
0   libSystem.B.dylib               0x97cd80fa mach_msg_trap + 10
1   libSystem.B.dylib               0x97cd8867 mach_msg + 68
2   CoreFoundation                  0x02b3a436 __CFRunLoopServiceMachPort + 150
3   CoreFoundation                  0x02a97984 __CFRunLoopRun + 708
4   CoreFoundation                  0x02a97350 CFRunLoopRunSpecific + 208
5   CoreFoundation                  0x02a97271 CFRunLoopRunInMode + 97
6   Foundation                      0x0017ab86 +[NSURLConnection(NSURLConnectionReallyInternal) _resourceLoadLoop:] + 398
7   Foundation                      0x0014576c -[NSThread main] + 81
8   Foundation                      0x001456f8 __NSThread__main__ + 1387
9   libSystem.B.dylib               0x97d0581d _pthread_start + 345
10  libSystem.B.dylib               0x97d056a2 thread_start + 34

Thread 7:
0   libSystem.B.dylib               0x97cf7086 select$DARWIN_EXTSN + 10
1   CoreFoundation                  0x02acbb5e __CFSocketManager + 798
2   libSystem.B.dylib               0x97d0581d _pthread_start + 345
3   libSystem.B.dylib               0x97d056a2 thread_start + 34

Thread 8:
0   libSystem.B.dylib               0x97cfd9d2 __workq_kernreturn + 10
1   libSystem.B.dylib               0x97cfdf68 _pthread_wqthread + 941
2   libSystem.B.dylib               0x97cfdb86 start_wqthread + 30

Thread 0 crashed with X86 Thread State (32-bit):
  eax: 0x06641500  ebx: 0x000143f3  ecx: 0x0008e1c8  edx: 0x06606075
  edi: 0xb0000000  esi: 0x066554c4  ebp: 0xbfffdef8  esp: 0xbfffdec4
   ss: 0x0000001f  efl: 0x00010206  eip: 0x02cb5907   cs: 0x00000017
   ds: 0x0000001f   es: 0x0000001f   fs: 0x00000000   gs: 0x00000037
  cr2: 0xb0000000
jv42
  • 8,521
  • 5
  • 40
  • 64
mootymoots
  • 4,545
  • 9
  • 46
  • 74

4 Answers4

4

You do not release your operation queue in dealloc...

One suspect is the delegation pattern:

[request setDelegate:self];

If the self object is destroyed, and the request isn't, when the request 'completes', it will try to notify some garbage in memory, hence crash.

UPDATE: to patch this, add this code before canceling:

for (ASIHTTPRequest *request in queue.operations) 
{ 
    [request setDelegate: nil]; 
    [request setDidFinishSelector: nil];
}
jv42
  • 8,521
  • 5
  • 40
  • 64
  • Do I need to release it if it's set to autorelease? I know for sure that I am destroying engine after I call [engine cancelOperations] - so could that still validly be an issue? – mootymoots Oct 22 '10 at 13:58
  • You do `[self setQueue:...]`. I'm assuming you have a queue property which is retain. If it's not, that's a big danger, since your queue can be released at any time. – jv42 Oct 22 '10 at 13:59
  • sorry, yes you are right... what about my self destroy? I thought that the waitUntilAllOperationsAreFinished would stop my app from continuing until the queue was empty? – mootymoots Oct 22 '10 at 14:04
  • Are you sure your cancel method gets called? I would add it to `dealloc` also, to be safe. – jv42 Oct 22 '10 at 14:16
  • I have moved the code to the dealloc - even so it crashes. I have added NSLogging to various places... it seems that everything deallocs fine, but crashes as soon as the view finishes dealloc. – mootymoots Oct 22 '10 at 14:36
  • Only explanation I see now is that ASIHTTPRequest doesn't respond properly to the cancel order. – jv42 Oct 22 '10 at 15:29
  • I've added the crash log FYI - might help? – mootymoots Oct 22 '10 at 15:47
  • Well, it looks exactly like what it would if the request called a method on your object. I'm looking for a quick fix to that... – jv42 Oct 22 '10 at 16:06
  • 1
    Mmmm... could you try to add: `for (ASIHTTPRequest *request in queue.operations) { [operation setDelegate: nil]; [operation setDidFinishSelector: nil]; }` before calling cancelAll? – jv42 Oct 22 '10 at 16:08
  • That fixed it :-) Thank you so much!! Although your code was slightly wrong FIXED: for (ASIHTTPRequest *request in queue.operations) { [request setDelegate: nil]; [request setDidFinishSelector: nil]; } – mootymoots Oct 22 '10 at 16:16
  • It might be worth doing a [request cancel] too; I'm not sure it will help but equally it won't hurt. I think the explanation /might/ be that ASIHTTPRequest tells the NSOperationQueue it's finished before it has actually completely finished (I know it does do this), which I guess could make waitUntilAllOperationsAreFinished return before everything has actually finished. – JosephH Oct 22 '10 at 16:22
  • I see. That would definitely be viable... Thanks for sticking with it guys :-) – mootymoots Oct 22 '10 at 16:27
4

I think jv42 is correct about the cause. However, there's a handy helper for cleaning up delegates...

-(void)dealloc {
    for (ASIHTTPRequest *request in queue.operations) { 
     [request clearDelegatesAndCancel]; 
    }
    [queue release];
    [super dealloc];
}

Has anyone found a quicker method for this... maybe one that doesn't involve looping over the requests?

Leah Culver
  • 138
  • 1
  • 4
0

Make sure you are running a recent git version of ASIHTTPRequest - v1.7 contains a couple of race conditions that will causes crashes when cancelling requests.

JosephH
  • 37,173
  • 19
  • 130
  • 154
  • Oh well, was worth checking. Can you add a backtrace for the crash to your question so we can see exactly which area of code is still running? – JosephH Oct 22 '10 at 15:38
0

I was having the same situation while running a ASINetworkQueue with multiple requests.

I removed the dealloc release for the queue, and the autorelease. I then applied Leah's excellent suggestion to loop through to nil the delegates. Then I put the queue's release in the queueRequestFinished delegate and it resolved my issue!

bulkQueue = [[ASINetworkQueue alloc] init];

[bulkQueue setQueueDidFinishSelector:@selector(queueRequestFinished:)];

-(void)queueRequestFinished:(ASINetworkQueue *)queue
{
    for (ASIHTTPRequest *request in queue.operations) 
    { 
        [request setDelegate: nil]; 
        [request setDidFinishSelector: nil];
    }

}

Hope this helps someone out! :-)

August
  • 698
  • 4
  • 12
  • 21
  • Oh, this example worked for version 1.8.1 and I also tested to make sure it wasn't a potential memory leak in my case. – August Jun 08 '11 at 20:22