Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

- Fix rare memory access issue for auto tracing (#4894). For more details, see issue [#4887](https://github.com/getsentry/sentry-cocoa/issues/4887).
- Move assignment of file IO span origin outside of block (#4888)
- Deadline timeout crash in SentryTracer (#4911)

## 8.45.0

Expand Down
143 changes: 71 additions & 72 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#import "SentryInternalDefines.h"
#import "SentryLog.h"
#import "SentryNSDictionarySanitize.h"
#import "SentryNSTimerFactory.h"
#import "SentryNoOpSpan.h"
#import "SentryOptions+Private.h"
#import "SentryProfilingConditionals.h"
Expand Down Expand Up @@ -72,7 +71,6 @@
* @c -[finish] doesn't necessarily lead to finishing the tracer, because it could still wait for
* child spans to finish if @c waitForChildren is @c YES . */
@property (nonatomic) BOOL wasFinishCalled;
@property (nonatomic, nullable, strong) NSTimer *deadlineTimer;
@property (nonnull, strong) SentryTracerConfiguration *configuration;
@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue;
@property (nonatomic, strong) SentryDebugImageProvider *debugImageProvider;
Expand All @@ -91,10 +89,11 @@
SentryAppStartMeasurement *appStartMeasurement;
#endif // SENTRY_HAS_UIKIT
NSMutableDictionary<NSString *, SentryMeasurementValue *> *_measurements;
NSObject *_dispatchTimeoutLock;
dispatch_block_t _idleTimeoutBlock;
dispatch_block_t _deadlineTimeoutBlock;
NSMutableArray<id<SentrySpan>> *_children;
BOOL _startTimeChanged;
NSObject *_idleTimeoutLock;

#if SENTRY_HAS_UIKIT
NSUInteger initTotalFrames;
Expand Down Expand Up @@ -147,10 +146,6 @@
_measurements = [[NSMutableDictionary alloc] init];
self.finishStatus = kSentrySpanStatusUndefined;

if (_configuration.timerFactory == nil) {
_configuration.timerFactory = [[SentryNSTimerFactory alloc] init];
}

#if SENTRY_HAS_UIKIT
[hub configureScope:^(SentryScope *scope) {
if (scope.currentScreen != nil) {
Expand All @@ -165,13 +160,13 @@

#endif // SENTRY_HAS_UIKIT

_idleTimeoutLock = [[NSObject alloc] init];
_dispatchTimeoutLock = [[NSObject alloc] init];
if ([self hasIdleTimeout]) {
[self dispatchIdleTimeout];
[self startIdleTimeout];

Check warning on line 165 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L165

Added line #L165 was not covered by tests
}

if ([self isAutoGeneratedTransaction]) {
[self startDeadlineTimer];
[self startDeadlineTimeout];
}

#if SENTRY_HAS_UIKIT
Expand Down Expand Up @@ -213,80 +208,71 @@
sentry_discardProfilerForTracer(self.internalID);
}
#endif // SENTRY_TARGET_PROFILING_SUPPORTED
[self cancelDeadlineTimeout];
}

- (nullable SentryTracer *)tracer
{
return self;
}

- (void)dispatchIdleTimeout
{
@synchronized(_idleTimeoutLock) {
if (_idleTimeoutBlock != NULL) {
[_dispatchQueue dispatchCancel:_idleTimeoutBlock];
}
__weak SentryTracer *weakSelf = self;
_idleTimeoutBlock = [_dispatchQueue createDispatchBlock:^{
if (weakSelf == nil) {
SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything.");
return;
}
[weakSelf finishInternal];
}];

if (_idleTimeoutBlock == NULL) {
SENTRY_LOG_WARN(@"Couldn'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 {
[_dispatchQueue dispatchAfter:_configuration.idleTimeout block:_idleTimeoutBlock];
}
}
}
#pragma mark - Timeouts

- (BOOL)hasIdleTimeout
{
return _configuration.idleTimeout > 0;
}

- (BOOL)isAutoGeneratedTransaction
- (void)startIdleTimeout
{
return _configuration.waitForChildren || [self hasIdleTimeout];
__weak SentryTracer *weakSelf = self;
dispatch_block_t newBlock = [_dispatchQueue createDispatchBlock:^{
if (weakSelf == nil) {
SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything.");
return;

Check warning on line 232 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L228-L232

Added lines #L228 - L232 were not covered by tests
}
[weakSelf finishInternal];
}];

Check warning on line 235 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L234-L235

Added lines #L234 - L235 were not covered by tests

@synchronized(_dispatchTimeoutLock) {
[self dispatchTimeout:_idleTimeoutBlock
newBlock:newBlock
interval:_configuration.idleTimeout];
_idleTimeoutBlock = newBlock;

Check warning on line 241 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L237-L241

Added lines #L237 - L241 were not covered by tests
}
}

- (void)cancelIdleTimeout
{
@synchronized(_idleTimeoutLock) {
@synchronized(_dispatchTimeoutLock) {
if ([self hasIdleTimeout]) {
[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper
dispatchCancel:_idleTimeoutBlock];
[_dispatchQueue dispatchCancel:_idleTimeoutBlock];

Check warning on line 249 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L249

Added line #L249 was not covered by tests
}
}
}

- (void)startDeadlineTimer
- (void)startDeadlineTimeout
{
__weak SentryTracer *weakSelf = self;
[_dispatchQueue dispatchAsyncOnMainQueue:^{
weakSelf.deadlineTimer = [weakSelf.configuration.timerFactory
scheduledTimerWithTimeInterval:SENTRY_AUTO_TRANSACTION_DEADLINE
repeats:NO
block:^(NSTimer *_Nonnull timer) {
if (weakSelf == nil) {
SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not calling "
@"deadlineTimerFired.");
return;
}
[weakSelf deadlineTimerFired];
}];
dispatch_block_t newBlock = [_dispatchQueue createDispatchBlock:^{
if (weakSelf == nil) {
SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not doing anything.");
return;

Check warning on line 260 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L258-L260

Added lines #L258 - L260 were not covered by tests
}
[weakSelf deadlineTimeoutExceeded];

Check warning on line 262 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L262

Added line #L262 was not covered by tests
}];

@synchronized(_dispatchTimeoutLock) {
[self dispatchTimeout:_deadlineTimeoutBlock
newBlock:newBlock
interval:SENTRY_AUTO_TRANSACTION_DEADLINE];
_deadlineTimeoutBlock = newBlock;
}
}

- (void)deadlineTimerFired
- (void)deadlineTimeoutExceeded
{
SENTRY_LOG_DEBUG(@"Sentry tracer deadline fired");
SENTRY_LOG_DEBUG(@"Sentry tracer deadline exceeded");

Check warning on line 275 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L275

Added line #L275 was not covered by tests
@synchronized(self) {
// This try to minimize a race condition with a proper call to `finishInternal`.
if (self.isFinished) {
Expand All @@ -305,25 +291,38 @@
[self finishInternal];
}

- (void)cancelDeadlineTimer
- (void)cancelDeadlineTimeout
{
if (self.deadlineTimer == nil) {
return;
@synchronized(_dispatchTimeoutLock) {
if (_deadlineTimeoutBlock != NULL) {
[_dispatchQueue dispatchCancel:_deadlineTimeoutBlock];
_deadlineTimeoutBlock = NULL;
}
}
}

// If the main thread is busy the tracer could be deallocated in between.
__weak SentryTracer *weakSelf = self;
- (void)dispatchTimeout:(dispatch_block_t)currentBlock
newBlock:(dispatch_block_t)newBlock
interval:(NSTimeInterval)timeInterval
{
if (currentBlock != NULL) {
[_dispatchQueue dispatchCancel:currentBlock];

Check warning on line 309 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L309

Added line #L309 was not covered by tests
}

// The timer must be invalidated from the thread on which the timer was installed, see
// https://developer.apple.com/documentation/foundation/nstimer/1415405-invalidate#1770468
[_dispatchQueue dispatchAsyncOnMainQueue:^{
if (weakSelf == nil) {
SENTRY_LOG_DEBUG(@"WeakSelf is nil. Not invalidating deadlineTimer.");
return;
}
[weakSelf.deadlineTimer invalidate];
weakSelf.deadlineTimer = nil;
}];
if (newBlock == NULL) {
SENTRY_LOG_WARN(@"Couldn't create dispatch after block. Finishing transaction.");

Check warning on line 313 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L313

Added line #L313 was not covered by tests
// If the transaction has no children, the SDK will discard it.
[self finishInternal];

Check warning on line 315 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L315

Added line #L315 was not covered by tests
} else {
[_dispatchQueue dispatchAfter:timeInterval block:newBlock];
}
}

#pragma mark - Tracer

- (BOOL)isAutoGeneratedTransaction
{
return _configuration.waitForChildren || [self hasIdleTimeout];
}

- (id<SentrySpan>)getActiveSpan
Expand Down Expand Up @@ -499,7 +498,7 @@
SENTRY_LOG_DEBUG(
@"Span with id %@ isn't waiting on children and needs idle timeout dispatched.",
self.spanId.sentrySpanIdString);
[self dispatchIdleTimeout];
[self startIdleTimeout];

Check warning on line 501 in Sources/Sentry/SentryTracer.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryTracer.m#L501

Added line #L501 was not covered by tests
return;
}

Expand Down Expand Up @@ -575,7 +574,7 @@
- (BOOL)finishTracer:(SentrySpanStatus)unfinishedSpansFinishStatus shouldCleanUp:(BOOL)shouldCleanUp
{
if (shouldCleanUp) {
[self cancelDeadlineTimer];
[self cancelDeadlineTimeout];
}

if (self.isFinished) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/SentryUIEventTrackerTransactionMode.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
if (sameAction) {
SENTRY_LOG_DEBUG(@"Dispatching idle timeout for transaction with span id %@",
currentActiveTransaction.spanId.sentrySpanIdString);
[currentActiveTransaction dispatchIdleTimeout];
[currentActiveTransaction startIdleTimeout];

Check warning on line 52 in Sources/Sentry/SentryUIEventTrackerTransactionMode.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryUIEventTrackerTransactionMode.m#L52

Added line #L52 was not covered by tests
return;
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/include/SentryTracer.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ static const NSTimeInterval SENTRY_AUTO_TRANSACTION_MAX_DURATION = 500.0;
*/
+ (nullable SentryTracer *)getTracer:(id<SentrySpan>)span;

- (void)dispatchIdleTimeout;
- (void)startIdleTimeout;

/**
* This method is designed to be used when the app crashes. It finishes the transaction and stores
Expand Down
6 changes: 0 additions & 6 deletions Sources/Sentry/include/SentryTracerConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
NS_ASSUME_NONNULL_BEGIN

@class SentryDispatchQueueWrapper;
@class SentryNSTimerFactory;
@class SentrySamplerDecision;

@interface SentryTracerConfiguration : NSObject
Expand Down Expand Up @@ -49,11 +48,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic) NSTimeInterval idleTimeout;

/**
* A writer around NSTimer, to make it testable
*/
@property (nonatomic, strong, nullable) SentryNSTimerFactory *timerFactory;

+ (SentryTracerConfiguration *)configurationWithBlock:
(void (^)(SentryTracerConfiguration *configuration))block;

Expand Down
1 change: 0 additions & 1 deletion Tests/SentryProfilerTests/SentryProfileTestFixture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ class SentryProfileTestFixture {
$0.idleTimeout = idleTimeout
}
$0.waitForChildren = true
$0.timerFactory = self.timeoutTimerFactory
}))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,26 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {

private class Fixture {
let dateProvider: TestCurrentDateProvider = TestCurrentDateProvider()
lazy var timerFactory = TestSentryNSTimerFactory(currentDateProvider: dateProvider)

let dispatchQueue = TestSentryDispatchQueueWrapper()
var displayLinkWrapper = TestDisplayLinkWrapper()
var framesTracker: SentryFramesTracker

init() {
framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: dateProvider, dispatchQueueWrapper: TestSentryDispatchQueueWrapper(),
framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: dateProvider, dispatchQueueWrapper: dispatchQueue,
notificationCenter: TestNSNotificationCenterWrapper(), keepDelayedFramesDuration: 0)
SentryDependencyContainer.sharedInstance().framesTracker = framesTracker
framesTracker.start()
}

func getSut(name: String, waitForFullDisplay: Bool) -> SentryTimeToDisplayTracker {
return SentryTimeToDisplayTracker(name: name, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper())
return SentryTimeToDisplayTracker(name: name, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: dispatchQueue)
}

func getTracer() throws -> SentryTracer {
let options = Options()
let hub = TestHub(client: SentryClient(options: options, fileManager: try TestFileManager(options: options), deleteOldEnvelopeItems: false), andScope: nil)
return SentryTracer(transactionContext: TransactionContext(operation: "ui.load"), hub: hub, configuration: SentryTracerConfiguration(block: {
$0.waitForChildren = true
$0.timerFactory = self.timerFactory
}))
}
}
Expand All @@ -40,7 +38,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {
override func setUp() {
super.setUp()
SentryDependencyContainer.sharedInstance().dateProvider = fixture.dateProvider
SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = TestSentryDispatchQueueWrapper()
SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = fixture.dispatchQueue
}

override func tearDown() {
Expand Down Expand Up @@ -279,8 +277,8 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {

fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11))

// Timeout for tracer times out
try fixture.timerFactory.fire()
// Deadline timeout for tracer times out
fixture.dispatchQueue.invokeLastDispatchAfter()

fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12))
sut.reportFullyDisplayed()
Expand Down Expand Up @@ -329,8 +327,8 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {

fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 11))

// Timeout for tracer times out
try fixture.timerFactory.fire()
// Deadline timeout for tracer times out
fixture.dispatchQueue.invokeLastDispatchAfter()

fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 12))
sut.reportFullyDisplayed()
Expand Down
Loading
Loading