Skip to content

Commit

Permalink
Fix: Remove sync call to main thread from SentryUIDeviceWrapper (#3295)
Browse files Browse the repository at this point in the history
SentryUIDeviceWrapper previously had a synchronized call to the main thread when the class was initialized in a background thread, potentially leading to deadlocks with SentryDependencyContainer.

To address this, the synchronized call has been replaced with an asynchronous one. Additionally, a new start function has been introduced and is invoked from SentrySDK.start to ensure initialization as early as possible.
  • Loading branch information
brustolin authored Sep 25, 2023
1 parent 72c8d84 commit 898177b
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 24 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- Remove sync call to main thread from SentryUIDeviceWrapper (#3295)

### Features

- Record changes to network connectivity in breadcrumbs (#3232)
Expand Down
7 changes: 7 additions & 0 deletions SentryTestUtils/TestSentryDispatchQueueWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ public class TestSentryDispatchQueueWrapper: SentryDispatchQueueWrapper {
public var blockOnMainInvocations = Invocations<() -> Void>()
public var blockBeforeMainBlock: () -> Bool = { true }

public override func dispatch(onMainQueue block: @escaping () -> Void) {
blockOnMainInvocations.record(block)
if blockBeforeMainBlock() {
block()
}
}

public override func dispatchAsync(onMainQueue block: @escaping () -> Void) {
blockOnMainInvocations.record(block)
if blockBeforeMainBlock() {
Expand Down
6 changes: 6 additions & 0 deletions Sources/Sentry/SentryDispatchQueueWrapper.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "SentryDispatchQueueWrapper.h"
#import "SentryThreadWrapper.h"
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -41,6 +42,11 @@ - (void)dispatchAsyncOnMainQueue:(void (^)(void))block
});
}

- (void)dispatchOnMainQueue:(void (^)(void))block
{
[SentryThreadWrapper onMainThread:block];
}

- (void)dispatchSyncOnMainQueue:(void (^)(void))block
{
if ([NSThread isMainThread]) {
Expand Down
11 changes: 9 additions & 2 deletions Sources/Sentry/SentrySDK.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import "SentryMeta.h"
#import "SentryOptions+Private.h"
#import "SentryScope.h"
#import "SentryUIDeviceWrapper.h"

@interface
SentrySDK ()
Expand Down Expand Up @@ -152,6 +153,9 @@ + (void)startWithOptions:(SentryOptions *)options

[SentryCrashWrapper.sharedInstance startBinaryImageCache];
[SentryDependencyContainer.sharedInstance.binaryImageCache start];
#if TARGET_OS_IOS
[SentryDependencyContainer.sharedInstance.uiDeviceWrapper start];
#endif
}

+ (void)startWithConfigureOptions:(void (^)(SentryOptions *options))configureOptions
Expand Down Expand Up @@ -401,11 +405,14 @@ + (void)close

[SentrySDK setCurrentHub:nil];

[SentryDependencyContainer reset];

[SentryCrashWrapper.sharedInstance stopBinaryImageCache];
[SentryDependencyContainer.sharedInstance.binaryImageCache stop];

#if TARGET_OS_IOS
[SentryDependencyContainer.sharedInstance.uiDeviceWrapper stop];
#endif

[SentryDependencyContainer reset];
SENTRY_LOG_DEBUG(@"SDK closed!");
}

Expand Down
39 changes: 19 additions & 20 deletions Sources/Sentry/SentryUIDeviceWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,32 @@ @implementation SentryUIDeviceWrapper

#if TARGET_OS_IOS

- (instancetype)init
- (void)start
{
if (self = [super init]) {
[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchSyncOnMainQueue:^{
// Needed to read the device orientation on demand
if (!UIDevice.currentDevice.isGeneratingDeviceOrientationNotifications) {
self.cleanupDeviceOrientationNotifications = YES;
[UIDevice.currentDevice beginGeneratingDeviceOrientationNotifications];
}

// Needed so we can read the battery level
if (!UIDevice.currentDevice.isBatteryMonitoringEnabled) {
self.cleanupBatteryMonitoring = YES;
UIDevice.currentDevice.batteryMonitoringEnabled = YES;
}
}];
}
return self;
[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchOnMainQueue:^{
if (!UIDevice.currentDevice.isGeneratingDeviceOrientationNotifications) {
self.cleanupDeviceOrientationNotifications = YES;
[UIDevice.currentDevice beginGeneratingDeviceOrientationNotifications];
}

// Needed so we can read the battery level
if (!UIDevice.currentDevice.isBatteryMonitoringEnabled) {
self.cleanupBatteryMonitoring = YES;
UIDevice.currentDevice.batteryMonitoringEnabled = YES;
}
}];
}

- (void)stop
{
[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchSyncOnMainQueue:^{
if (self.cleanupDeviceOrientationNotifications) {
BOOL needsCleanUp = self.cleanupDeviceOrientationNotifications;
BOOL needsDisablingBattery = self.cleanupBatteryMonitoring;

[SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchOnMainQueue:^{
if (needsCleanUp) {
[UIDevice.currentDevice endGeneratingDeviceOrientationNotifications];
}
if (self.cleanupBatteryMonitoring) {
if (needsDisablingBattery) {
UIDevice.currentDevice.batteryMonitoringEnabled = NO;
}
}];
Expand Down
2 changes: 2 additions & 0 deletions Sources/Sentry/include/SentryDispatchQueueWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ NS_ASSUME_NONNULL_BEGIN

- (void)dispatchAsyncOnMainQueue:(void (^)(void))block;

- (void)dispatchOnMainQueue:(void (^)(void))block;

- (void)dispatchSyncOnMainQueue:(void (^)(void))block;

- (void)dispatchAfter:(NSTimeInterval)interval block:(dispatch_block_t)block;
Expand Down
2 changes: 1 addition & 1 deletion Sources/Sentry/include/SentryUIDeviceWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface SentryUIDeviceWrapper : NSObject

#if TARGET_OS_IOS
- (instancetype)init;
- (void)start;
- (void)stop;
- (UIDeviceOrientation)orientation;
- (BOOL)isBatteryMonitoringEnabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class SentryUIDeviceWrapperTests: XCTestCase {
let dispatchQueue = TestSentryDispatchQueueWrapper()
SentryDependencyContainer.sharedInstance().dispatchQueueWrapper = dispatchQueue
let sut = SentryUIDeviceWrapper()
sut.start()
XCTAssertEqual(dispatchQueue.blockOnMainInvocations.count, 1)

sut.stop()
Expand Down
8 changes: 8 additions & 0 deletions Tests/SentryTests/SentryCrash/TestSentryUIDeviceWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ class TestSentryUIDeviceWrapper: SentryUIDeviceWrapper {
var internalIsBatteryMonitoringEnabled = true
var internalBatteryLevel: Float = 0.6
var internalBatteryState = UIDevice.BatteryState.charging
var started = false

override func start() {
started = true
}
override func stop() {
started = false
}

override func orientation() -> UIDeviceOrientation {
return internalOrientation
Expand Down
19 changes: 18 additions & 1 deletion Tests/SentryTests/SentrySDKTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,24 @@ class SentrySDKTests: XCTestCase {
XCTAssertTrue(testTTDTracker.registerFullDisplayCalled)
}
#endif


#if os(iOS)
func testSentryUIDeviceWrapperStarted() {
let deviceWrapper = TestSentryUIDeviceWrapper()
SentryDependencyContainer.sharedInstance().uiDeviceWrapper = deviceWrapper
SentrySDK.start(options: fixture.options)
XCTAssertTrue(deviceWrapper.started)
}

func testSentryUIDeviceWrapperStopped() {
let deviceWrapper = TestSentryUIDeviceWrapper()
SentryDependencyContainer.sharedInstance().uiDeviceWrapper = deviceWrapper
SentrySDK.start(options: fixture.options)
SentrySDK.close()
XCTAssertFalse(deviceWrapper.started)
}
#endif

func testClose_SetsClientToNil() {
SentrySDK.start { options in
options.dsn = SentrySDKTests.dsnAsString
Expand Down

0 comments on commit 898177b

Please sign in to comment.