Crash caused by CFNetwork assertion

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;
}

Replies

That crash report is from iOS 15.0.2, and there’s been a loth of water under the bridge since there. Are you seeing this on newer iOS versions? If so, please post a crash report from something in the iOS 17 timeframe.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Apologies. From what I tested, this happens on all iOS versions. Here's a crash report from iOS 17.3.1

Thanks for the new crash report.

Your third-party crash reporter (thread 9) means that I can’t 100% trust it [1], but it seems to be generally OK.

The crashing thread backtrace looks like this:

Thread 16 Crashed:
0  libsystem_kernel.dylib  … __pthread_kill + 8 (:-1)
1  libsystem_pthread.dylib … pthread_kill + 268 (pthread.c:1681)
2  libsystem_c.dylib       … __abort + 136 (abort.c:159)
3  libsystem_c.dylib       … abort + 192 (abort.c:126)
4  libsystem_c.dylib       … __assert_rtn + 284 (assert.c:94)
5  CFNetwork               … invocation function for block in RequestBodyStream::_onqueue_setupStream() + 252 (…
6  libdispatch.dylib       … _dispatch_client_callout + 20 (object.m:561)
7  libdispatch.dylib       … _dispatch_block_invoke_direct + 284 (queue.c:496)
8  CFNetwork               … RunloopBlockContext::_invoke_block(void const*, void*) + 44 (CoreSchedulingSet.mm:…
9  CoreFoundation          … CFArrayApplyFunction + 72 (CFArray.c:672)
10 CFNetwork               … RunloopBlockContext::perform() + 136 (CoreSchedulingSet.mm:291)
11 CFNetwork               … MultiplexerSource::_perform(void*) + 336 (CFNRunLoopMultiplexer.c:39)
12 CoreFoundation          … __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 (CFRunLoop.c:1957)
13 CoreFoundation          … __CFRunLoopDoSource0 + 176 (CFRunLoop.c:2001)
14 CoreFoundation          … __CFRunLoopDoSources0 + 244 (CFRunLoop.c:2038)
15 CoreFoundation          … __CFRunLoopRun + 828 (CFRunLoop.c:2955)
16 CoreFoundation          … CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420)
17 CFNetwork               … +[__CFN_CoreSchedulingSetRunnable _run:] + 384 (CoreSchedulingSet.mm:1479)
18 Foundation              … __NSThread__start__ + 732 (NSThread.m:991)
19 libsystem_pthread.dylib … _pthread_start + 136 (pthread.c:904)
20 libsystem_pthread.dylib … thread_start + 8 (:-1)

Frames 4 through 0 indicate that you tripped an assert within CFNetwork. Frame 5 is part of the RequestBodyStream::_onqueue_setupStream() method. This is setting up the body stream for a streamed request. The assert looks something like this:

assert(CFReadStreamGetStatus(bodyStream) == .notOpen)

When you use a streamed body, CFNetwork opens the stream itself. This assert traps if you give it a stream that’s already been opened.

It’s possible that this might be an error is CFNetwork’s internal logic. However, the first step for you is to look at all your uses of streamed bodies to see if there’s any chance you’re giving CFNetwork an open stream.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] See Implementing Your Own Crash Reporter.

Thank you for the insights!

I reviewed our code and we don't send any streamed requests. All requests are built similarly to the snippet in my original message (with an NSData object) and they are all executed in the queue as a NetworkOperation.