rFlex / SCRecorder

iOS camera engine with Vine-like tap to record, animatable filters, slow motion, segments editing
Apache License 2.0
3.06k stars 583 forks source link

Crash on SCRecordSession Line 161 #423

Open byjoh opened 6 years ago

byjoh commented 6 years ago

I have received multiple crash reports from various users over the last month related to this same issue (per Crashlytics: BUG IN CLIENT OF LIBDISPATCH: dispatch_sync called on queue already owned by current thread). All devices are running 11.3.1.

Crashed: me.corsin.SCRecorder.Audio
0  libdispatch.dylib              0x1841cec54 _dispatch_sync_wait + 604
1  SCRecorder                     0x10898df9c -[SCRecordSession dispatchSyncOnSessionQueue:] (SCRecordSession.m:161)
2  SCRecorder                     0x10898df9c -[SCRecordSession dispatchSyncOnSessionQueue:] (SCRecordSession.m:161)
3  SCRecorder                     0x10898fc80 -[SCRecordSession appendRecordSegmentUrl:info:error:completionHandler:] (SCRecordSession.m:491)
4  SCRecorder                     0x108990434 __56-[SCRecordSession endSegmentWithInfo:completionHandler:]_block_invoke.257 (SCRecordSession.m:523)
5  SCRecorder                     0x108990230 __56-[SCRecordSession endSegmentWithInfo:completionHandler:]_block_invoke_2 (SCRecordSession.m:525)
6  libdispatch.dylib              0x1841c0ae4 _dispatch_client_callout + 16
7  libdispatch.dylib              0x1841c9640 _dispatch_queue_barrier_sync_invoke_and_complete + 56
8  SCRecorder                     0x1089900b4 __56-[SCRecordSession endSegmentWithInfo:completionHandler:]_block_invoke (SCRecordSession.m:537)
9  SCRecorder                     0x10898df74 -[SCRecordSession dispatchSyncOnSessionQueue:] (SCRecordSession.m:159)
10 SCRecorder                     0x10898ffbc -[SCRecordSession endSegmentWithInfo:completionHandler:] (SCRecordSession.m:539)
11 SCRecorder                     0x1089849fc __20-[SCRecorder pause:]_block_invoke (SCRecorder.m:502)
12 libdispatch.dylib              0x1841c0b24 _dispatch_call_block_and_release + 24
13 libdispatch.dylib              0x1841c0ae4 _dispatch_client_callout + 16
14 libdispatch.dylib              0x1841caa38 _dispatch_queue_serial_drain$VARIANT$mp + 608
15 libdispatch.dylib              0x1841cb380 _dispatch_queue_invoke$VARIANT$mp + 336
16 libdispatch.dylib              0x1841cbd4c _dispatch_root_queue_drain_deferred_wlh$VARIANT$mp + 340
17 libdispatch.dylib              0x1841d411c _dispatch_workloop_worker_thread$VARIANT$mp + 668
18 libsystem_pthread.dylib        0x1844f3e70 _pthread_wqthread + 860
19 libsystem_pthread.dylib        0x1844f3b08 start_wqthread + 4

I believe what is happening is as described in this pseudocode

dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        // dispatch_get_current_queue() is B, but A is blocked, 
        // so a dispatch_sync(A,b) will deadlock.
        dispatch_sync(queueA, ^{
            // some task
        });
    });
});

because if you start at the method line 5 in the stack trace

- (BOOL)endSegmentWithInfo:(NSDictionary *)info completionHandler:(void(^)(SCRecordSessionSegment *segment, NSError* error))completionHandler {
    __block BOOL success = NO;

    [self dispatchSyncOnSessionQueue:^{
        dispatch_sync(_audioQueue, ^{
            if (_recordSegmentReady) {
                _recordSegmentReady = NO;
                success = YES;

                AVAssetWriter *writer = _assetWriter;

                if (writer != nil) {
                    BOOL currentSegmentEmpty = (!_currentSegmentHasVideo && !_currentSegmentHasAudio);

                    if (currentSegmentEmpty) {
                        [writer cancelWriting];
                        [self _destroyAssetWriter];

                        [self removeFile:writer.outputURL];

                        if (completionHandler != nil) {
                            dispatch_async(dispatch_get_main_queue(), ^{
                                completionHandler(nil, nil);
                            });
                        }
                    } else {
                        //                NSLog(@"Ending session at %fs", CMTimeGetSeconds(_currentSegmentDuration));
                        [writer endSessionAtSourceTime:CMTimeAdd(_currentSegmentDuration, _sessionStartTime)];

                        [writer finishWritingWithCompletionHandler: ^{
                            [self appendRecordSegmentUrl:writer.outputURL info:info error:writer.error completionHandler:completionHandler];
                        }];
                    }
                } else {
                    [_movieFileOutput stopRecording];
                }
            } else {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (completionHandler != nil) {
                        completionHandler(nil, [SCRecordSession createError:@"The current record segment is not ready for this operation"]);
                    }
                });
            }
        });
    }];

    return success;
}

this creates a similar nesting queue structure session queue -> audioQueue -> sessionQueue. I am not strong in this area though so might be going down the wrong path.