Skip to content

Make Crashlytics context Init unblocking main #14754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions Crashlytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [fixed] Improved startup time by putting some initialization steps on a background. (#13675, #13232)

# 11.9.0
- [fixed] Made on-demand fatal recording thread suspension configurable through setting to improve performance and avoid audio glitch on Unity. Change is for framework only.

Expand Down
4 changes: 3 additions & 1 deletion Crashlytics/Crashlytics/Components/FIRCLSContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ __BEGIN_DECLS
@class FIRCLSInstallIdentifierModel;
@class FIRCLSFileManager;
@class FIRCLSContextInitData;
@class FBLPromise;
#endif

typedef struct {
Expand Down Expand Up @@ -82,7 +83,8 @@ typedef struct {
FIRCLSAllocatorRef allocator;
} FIRCLSContext;
#ifdef __OBJC__
bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager);
FBLPromise* FIRCLSContextInitialize(FIRCLSContextInitData* initData,
FIRCLSFileManager* fileManager);
FIRCLSContextInitData* FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSFileManager* fileManager,
Expand Down
15 changes: 6 additions & 9 deletions Crashlytics/Crashlytics/Components/FIRCLSContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
// We need enough space here for the context, plus storage for strings.
#define CLS_MINIMUM_READABLE_SIZE (sizeof(FIRCLSReadOnlyContext) + 4096 * 4)

static const int64_t FIRCLSContextInitWaitTime = 5LL * NSEC_PER_SEC;

static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component);
static void FIRCLSContextAllocate(FIRCLSContext* context);

Expand Down Expand Up @@ -76,7 +74,8 @@
return initData;
}

bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager) {
FBLPromise* FIRCLSContextInitialize(FIRCLSContextInitData* initData,
FIRCLSFileManager* fileManager) {
if (!initData) {
return false;
}
Expand All @@ -102,6 +101,8 @@ bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager*
// some values that aren't tied to particular subsystem
_firclsContext.readonly->debuggerAttached = FIRCLSProcessDebuggerAttached();

__block FBLPromise* initPromise = [FBLPromise pendingPromise];

dispatch_group_async(group, queue, ^{
FIRCLSHostInitialize(&_firclsContext.readonly->host);
});
Expand Down Expand Up @@ -220,14 +221,10 @@ bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager*
if (!FIRCLSAllocatorProtect(_firclsContext.allocator)) {
FIRCLSSDKLog("Error: Memory protection failed\n");
}
[initPromise fulfill:nil];
});

if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, FIRCLSContextInitWaitTime)) !=
0) {
FIRCLSSDKLog("Error: Delayed initialization\n");
}

return true;
return initPromise;
}

void FIRCLSContextBaseInit(void) {
Expand Down
6 changes: 3 additions & 3 deletions Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ NS_ASSUME_NONNULL_BEGIN
/// a new Session ID.
@property(nonatomic, copy) NSString *appQualitySessionId;

- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager;
- (FBLPromise *)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager;

@end

Expand Down
6 changes: 3 additions & 3 deletions Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ - (instancetype)init {
return self;
}

- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager {
- (FBLPromise *)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager {
_report = report;
_settings = settings;
_fileManager = fileManager;
Expand Down
106 changes: 58 additions & 48 deletions Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -289,15 +289,26 @@ - (FBLPromise *)deleteUnsentReports {

BOOL launchFailure = [self.launchMarker checkForAndCreateLaunchMarker];

FIRCLSInternalReport *report = [self setupCurrentReport:executionIdentifier];
__block FIRCLSInternalReport *report = [self setupCurrentReport:executionIdentifier];
if (!report) {
FIRCLSErrorLog(@"Unable to setup a new report");
}

if (![self startCrashReporterWithProfilingReport:report]) {
FIRCLSErrorLog(@"Unable to start crash reporter");
report = nil;
}
FBLPromise<NSNumber *> *reportProfilingPromise;
reportProfilingPromise =
[[self startCrashReporterWithProfilingReport:report] then:^id _Nullable(id _Nullable value) {
if ([value isEqual:@NO]) {
FIRCLSErrorLog(@"Unable to start crash reporter");
report = nil;
return [FBLPromise resolvedWith:@NO];
}

// empty for disabled start-up time
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSStartTimeKey, @"");
});
return [FBLPromise resolvedWith:@YES];
}];

#if CLS_METRICKIT_SUPPORTED
if (@available(iOS 15, *)) {
Expand All @@ -317,9 +328,12 @@ - (FBLPromise *)deleteUnsentReports {
[self beginSettingsWithToken:dataCollectionToken];

// Wait for MetricKit data to be available, then continue to send reports and resolve promise.
promise = [[self waitForMetricKitData]
promise = [[reportProfilingPromise onQueue:_dispatchQueue
then:^id _Nullable(id _Nullable value) {
return [self waitForMetricKitData];
}]
onQueue:_dispatchQueue
then:^id _Nullable(id _Nullable metricKitValue) {
then:^id _Nullable(id _Nullable value) {
[self beginReportUploadsWithToken:dataCollectionToken blockingSend:launchFailure];

// If data collection is enabled, the SDK will not notify the user
Expand All @@ -335,36 +349,33 @@ - (FBLPromise *)deleteUnsentReports {

// Wait for an action to get sent, either from processReports: or automatic data collection,
// and for MetricKit data to be available.
promise = [[FBLPromise all:@[ [self waitForReportAction], [self waitForMetricKitData] ]]
promise = [[reportProfilingPromise
onQueue:_dispatchQueue
then:^id _Nullable(NSArray *_Nullable wrappedActionAndData) {
// Process the actions for the reports on disk.
FIRCLSReportAction action = [[wrappedActionAndData firstObject] reportActionValue];

if (action == FIRCLSReportActionSend) {
FIRCLSDebugLog(@"Sending unsent reports.");
FIRCLSDataCollectionToken *dataCollectionToken =
[FIRCLSDataCollectionToken validToken];

[self beginSettingsWithToken:dataCollectionToken];

[self beginReportUploadsWithToken:dataCollectionToken blockingSend:NO];

} else if (action == FIRCLSReportActionDelete) {
FIRCLSDebugLog(@"Deleting unsent reports.");
[self.existingReportManager deleteUnsentReports];
} else {
FIRCLSErrorLog(@"Unknown report action: %d", action);
}
return @(report != nil);
}];
}

if (report != nil) {
// empty for disabled start-up time
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSStartTimeKey, @"");
});
then:^id _Nullable(id _Nullable value) {
return [FBLPromise all:@[ [self waitForReportAction], [self waitForMetricKitData] ]];
}] onQueue:_dispatchQueue
then:^id _Nullable(NSArray *_Nullable wrappedActionAndData) {
// Process the actions for the reports on disk.
FIRCLSReportAction action =
[[wrappedActionAndData firstObject] reportActionValue];

if (action == FIRCLSReportActionSend) {
FIRCLSDebugLog(@"Sending unsent reports.");
FIRCLSDataCollectionToken *dataCollectionToken =
[FIRCLSDataCollectionToken validToken];

[self beginSettingsWithToken:dataCollectionToken];

[self beginReportUploadsWithToken:dataCollectionToken blockingSend:NO];

} else if (action == FIRCLSReportActionDelete) {
FIRCLSDebugLog(@"Deleting unsent reports.");
[self.existingReportManager deleteUnsentReports];
} else {
FIRCLSErrorLog(@"Unknown report action: %d", action);
}
return @(report != nil);
}];
}

// To make the code more predictable and therefore testable, don't resolve the startup promise
Expand Down Expand Up @@ -412,24 +423,23 @@ - (void)beginReportUploadsWithToken:(FIRCLSDataCollectionToken *)token
}
}

- (BOOL)startCrashReporterWithProfilingReport:(FIRCLSInternalReport *)report {
- (FBLPromise<NSNumber *> *)startCrashReporterWithProfilingReport:(FIRCLSInternalReport *)report {
if (!report) {
return NO;
}

if (![self.contextManager setupContextWithReport:report
settings:self.settings
fileManager:_fileManager]) {
return NO;
return [FBLPromise resolvedWith:@NO];
}

[self.notificationManager registerNotificationListener];
return [[self.contextManager setupContextWithReport:report
settings:self.settings
fileManager:_fileManager]
then:^id _Nullable(id _Nullable value) {
[self.notificationManager registerNotificationListener];

[self.analyticsManager registerAnalyticsListener];
[self.analyticsManager registerAnalyticsListener];

[self crashReportingSetupCompleted];
[self crashReportingSetupCompleted];

return YES;
return [FBLPromise resolvedWith:@YES];
}];
}

- (void)crashReportingSetupCompleted {
Expand Down
21 changes: 12 additions & 9 deletions Crashlytics/UnitTests/FIRCLSContextManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,10 @@ - (void)tearDown {
}

- (void)test_notSettingSessionID_protoHasNilSessionID {
[self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager];
FBLPromiseAwait([self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager],
nil);

FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path
googleAppId:@"TestGoogleAppID"
Expand All @@ -84,9 +85,10 @@ - (void)test_notSettingSessionID_protoHasNilSessionID {
- (void)test_settingSessionIDMultipleTimes_protoHasLastSessionID {
[self.contextManager setAppQualitySessionId:TestContextSessionID];

[self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager];
FBLPromiseAwait([self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager],
nil);

[self.contextManager setAppQualitySessionId:TestContextSessionID2];

Expand All @@ -101,9 +103,10 @@ - (void)test_settingSessionIDMultipleTimes_protoHasLastSessionID {
}

- (void)test_settingSessionIDOutOfOrder_protoHasLastSessionID {
[self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager];
FBLPromiseAwait([self.contextManager setupContextWithReport:self.report
settings:self.mockSettings
fileManager:self.fileManager],
nil);

[self.contextManager setAppQualitySessionId:TestContextSessionID];

Expand Down
7 changes: 4 additions & 3 deletions Crashlytics/UnitTests/FIRCLSOnDemandModelTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,10 @@ - (void)setUp {
executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"];

FIRCLSContextManager *contextManager = [[FIRCLSContextManager alloc] init];
[contextManager setupContextWithReport:report
settings:self.mockSettings
fileManager:self.fileManager];
FBLPromiseAwait([contextManager setupContextWithReport:report
settings:self.mockSettings
fileManager:self.fileManager],
nil);
}

- (void)tearDown {
Expand Down
7 changes: 4 additions & 3 deletions Crashlytics/UnitTests/FIRRecordExceptionModelTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ - (void)setUp {
executionIdentifier:@"TEST_EXECUTION_IDENTIFIER"];

FIRCLSContextManager *contextManager = [[FIRCLSContextManager alloc] init];
[contextManager setupContextWithReport:report
settings:self.mockSettings
fileManager:self.fileManager];
FBLPromiseAwait([contextManager setupContextWithReport:report
settings:self.mockSettings
fileManager:self.fileManager],
nil);
}

- (void)tearDown {
Expand Down
10 changes: 8 additions & 2 deletions Crashlytics/UnitTests/Mocks/FIRCLSMockReportManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif

#import "Crashlytics/UnitTests/Mocks/FIRCLSMockReportManager.h"

#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
Expand All @@ -22,10 +28,10 @@

@implementation FIRCLSMockReportManager

- (BOOL)startCrashReporterWithProfilingReport:(FIRCLSInternalReport *)report {
- (FBLPromise<NSNumber *> *)startCrashReporterWithProfilingReport:(FIRCLSInternalReport *)report {
NSLog(@"Crash Reporting system disabled for testing");

return YES;
return [FBLPromise resolvedWith:@YES];
}

- (BOOL)installCrashReportingHandlers:(FIRCLSContextInitData *)initData {
Expand Down
Loading