Hello,
Our app has an internal job processing queue. All jobs are built as a NSOperation and involve a network request, and they are added to NSOperationQueue. When the app is closed while a request is being sent, the app sometimes crashes, but it also keeps crashing whenever we build the operation again and retry it. This happens rarely, but we can systematically reproduce it after a few tries with many jobs.
This issue blocks the queue in our app. I understand if this is an issue deep within the framework, but it would be very useful to at least find a way to work around this issue so the queue can continue processing other jobs.
The full crash report is attached. I also submitted a bug report: FB13734737
There seems to be an internal assertion fired in CFNetwork:
Assertion failed: (CFReadStreamGetStatus(_stream.get()) == kCFStreamStatusNotOpen) function _onqueue_setupStream_block_invoke file HTTPRequestBody.cpp line 878.
Crashed: com.apple.NSURLConnectionLoader
0 libsystem_kernel.dylib 0xa974 __pthread_kill + 8
1 libsystem_pthread.dylib 0x60ec pthread_kill + 268
2 libsystem_c.dylib 0x75b80 abort + 180
3 libsystem_c.dylib 0x74e70 err + 282
4 CFNetwork 0x1f73b8 CFHTTPCookieStorageUnscheduleFromRunLoop + 278252
5 libdispatch.dylib 0x3dd4 _dispatch_client_callout + 20
6 libdispatch.dylib 0x786c _dispatch_block_invoke_direct + 288
7 CFNetwork 0x259ab0 estimatedPropertyListSize + 33724
8 CoreFoundation 0x24b34 CFArrayApplyFunction + 72
9 CFNetwork 0x2599a0 estimatedPropertyListSize + 33452
10 CFNetwork 0x25c084 estimatedPropertyListSize + 43408
11 CoreFoundation 0x3762c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
12 CoreFoundation 0x368a8 __CFRunLoopDoSource0 + 176
13 CoreFoundation 0x35058 __CFRunLoopDoSources0 + 244
14 CoreFoundation 0x33d88 __CFRunLoopRun + 828
15 CoreFoundation 0x33968 CFRunLoopRunSpecific + 608
16 CFNetwork 0x25ac48 estimatedPropertyListSize + 38228
17 Foundation 0x9ca9c __NSThread__start__ + 732
18 libsystem_pthread.dylib 0x2a90 _pthread_start + 136
19 libsystem_pthread.dylib 0x1fcc thread_start + 8
This is how we build the operation:
-(NSOperation*)operationForRequest:(Job*)job
{
NSURL *url = [NSURL URLWithString:job.url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:@"application/json, application/xml, text/plain" forHTTPHeaderField:@"Accept"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"no-cache" forHTTPHeaderField:@"Cache-Control"];
[request setValue:[NSString stringWithFormat:@"Bearer %@", [self getToken]] forHTTPHeaderField:@"Authorization"];
[request setHTTPMethod:job.method];
NSData *bodyData = [job.payload dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:bodyData];
return [[NetworkOperation alloc] initWithRequest:request uuid:job.jobId completionHandler:^(NSString* jobId, NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
@autoreleasepool {
RLMRealm *realm = [RLMRealm defaultRealm];
Job *opJob = [Job objectInRealm:realm forPrimaryKey:jobId];
[self processJobResponse:opJob response:response data:data error:error realm:realm];
}
});
}];
}
This is how the NetworkOperation executes the request:
- (void)main {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task = [session dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (self.networkOperationCompletionBlock) {
self.networkOperationCompletionBlock(self.uuid, data, response, error);
self.networkOperationCompletionBlock = nil;
}
[self completeOperation];
}];
[task resume];
self.task = task;
}