diff --git a/CHANGELOG.md b/CHANGELOG.md index f5b6867061..822ed9864b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes - View hierarchy not sent for crashes (#2781) +- Crash in Tracer for idle timeout (#2834) ## 8.3.2 diff --git a/SentryTestUtils/TestSentryDispatchQueueWrapper.swift b/SentryTestUtils/TestSentryDispatchQueueWrapper.swift index ced28591b7..0dafb6b855 100644 --- a/SentryTestUtils/TestSentryDispatchQueueWrapper.swift +++ b/SentryTestUtils/TestSentryDispatchQueueWrapper.swift @@ -62,4 +62,13 @@ public class TestSentryDispatchQueueWrapper: SentryDispatchQueueWrapper { public override func dispatchOnce(_ predicate: UnsafeMutablePointer, block: @escaping () -> Void) { block() } + + public var createDispatchBlockReturnsNULL = false + public override func createDispatchBlock(_ block: @escaping () -> Void) -> (() -> Void)? { + if createDispatchBlockReturnsNULL { + return nil + } + return super.createDispatchBlock(block) + } + } diff --git a/Sources/Sentry/SentryDispatchQueueWrapper.m b/Sources/Sentry/SentryDispatchQueueWrapper.m index 88a89fef3e..9703570dcb 100644 --- a/Sources/Sentry/SentryDispatchQueueWrapper.m +++ b/Sources/Sentry/SentryDispatchQueueWrapper.m @@ -77,6 +77,11 @@ - (void)dispatchOnce:(dispatch_once_t *)predicate block:(void (^)(void))block dispatch_once(predicate, block); } +- (nullable dispatch_block_t)createDispatchBlock:(void (^)(void))block +{ + return dispatch_block_create(0, block); +} + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index ea94a6fcad..44245d23f3 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -217,14 +217,21 @@ - (void)dispatchIdleTimeout [self.dispatchQueueWrapper dispatchCancel:_idleTimeoutBlock]; } __weak SentryTracer *weakSelf = self; - _idleTimeoutBlock = dispatch_block_create(0, ^{ + _idleTimeoutBlock = [self.dispatchQueueWrapper createDispatchBlock:^{ if (weakSelf == nil) { SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything."); return; } [weakSelf finishInternal]; - }); - [self.dispatchQueueWrapper dispatchAfter:self.idleTimeout block:_idleTimeoutBlock]; + }]; + if (_idleTimeoutBlock == NULL) { + SENTRY_LOG_WARN(@"Couln't create idle time out block. Can't schedule idle timeout. " + @"Finishing transaction"); + // If the transaction has no children, the SDK will discard it. + [self finishInternal]; + } else { + [self.dispatchQueueWrapper dispatchAfter:self.idleTimeout block:_idleTimeoutBlock]; + } } - (BOOL)hasIdleTimeout diff --git a/Sources/Sentry/include/SentryDispatchQueueWrapper.h b/Sources/Sentry/include/SentryDispatchQueueWrapper.h index 06bcfaba1e..0a7948fedc 100644 --- a/Sources/Sentry/include/SentryDispatchQueueWrapper.h +++ b/Sources/Sentry/include/SentryDispatchQueueWrapper.h @@ -21,6 +21,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)dispatchOnce:(dispatch_once_t *)predicate block:(void (^)(void))block; +- (nullable dispatch_block_t)createDispatchBlock:(void (^)(void))block; + @end NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Performance/SentryTracerTests.swift b/Tests/SentryTests/Performance/SentryTracerTests.swift index 6d79b6d136..2e870726a6 100644 --- a/Tests/SentryTests/Performance/SentryTracerTests.swift +++ b/Tests/SentryTests/Performance/SentryTracerTests.swift @@ -224,6 +224,26 @@ class SentryTracerTests: XCTestCase { XCTAssertEqual(status, sut.status) } + func testIdleTransaction_CreatingDispatchBlockFails_NoTransactionCaptured() { + fixture.dispatchQueue.createDispatchBlockReturnsNULL = true + + let sut = fixture.getSut(idleTimeout: fixture.idleTimeout, dispatchQueueWrapper: fixture.dispatchQueue) + + assertTransactionNotCaptured(sut) + } + + func testIdleTransaction_CreatingDispatchBlockFailsForFirstChild_FinishesTransaction() { + let sut = fixture.getSut(idleTimeout: fixture.idleTimeout, dispatchQueueWrapper: fixture.dispatchQueue) + + fixture.dispatchQueue.createDispatchBlockReturnsNULL = true + + let child = sut.startChild(operation: fixture.transactionOperation) + advanceTime(bySeconds: 0.1) + child.finish() + + assertOneTransactionCaptured(sut) + } + func testWaitForChildrenTransactionWithStatus_OverwriteStatusInFinish() { let sut = fixture.getSut() sut.status = .aborted