Skip to content
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

[PLAT-8966] Add attemptDeliveryOnCrash #1488

Merged
merged 1 commit into from
Sep 30, 2022
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
29 changes: 26 additions & 3 deletions Bugsnag/BSGCrashSentry.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@

#import "BSGCrashSentry.h"

#import "BSGDefines.h"
#import "BSGEventUploader.h"
#import "BSGFileLocations.h"
#import "BSGUtils.h"
#import "BSG_KSCrash.h"
#import "BSG_KSCrashC.h"
#import "BSG_KSMach.h"
#import "BugsnagConfiguration.h"
#import "BugsnagErrorTypes.h"
#import "BugsnagClient+Private.h"
#import "BugsnagInternals.h"
#import "BugsnagLogger.h"

NSTimeInterval BSGCrashSentryDeliveryTimeout = 3;

static void BSGCrashSentryAttemptyDelivery(void);

void BSGCrashSentryInstall(BugsnagConfiguration *config, BSG_KSReportWriteCallback onCrash) {
BSG_KSCrash *ksCrash = [BSG_KSCrash sharedInstance];

Expand All @@ -36,6 +40,10 @@ void BSGCrashSentryInstall(BugsnagConfiguration *config, BSG_KSReportWriteCallba
} else {
crashTypes = BSG_KSCrashTypeFromBugsnagErrorTypes(config.enabledErrorTypes);
}
if (config.attemptDeliveryOnCrash) {
bsg_log_debug(@"Enabling on-crash delivery");
crashContext()->crash.attemptDelivery = BSGCrashSentryAttemptyDelivery;
}
}

NSString *crashReportsDirectory = BSGFileLocations.current.kscrashReports;
Expand Down Expand Up @@ -68,3 +76,18 @@ BSG_KSCrashType BSG_KSCrashTypeFromBugsnagErrorTypes(BugsnagErrorTypes *errorTyp
#endif
0);
}

static void BSGCrashSentryAttemptyDelivery(void) {
NSString *file = @(crashContext()->config.crashReportFilePath);
bsg_log_info(@"Attempting crash-time delivery of %@", file);
int64_t timeout = (int64_t)(BSGCrashSentryDeliveryTimeout * NSEC_PER_SEC);
dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, timeout);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[Bugsnag.client.eventUploader uploadKSCrashReportWithFile:file completionHandler:^{
bsg_log_debug(@"Sent crash.");
dispatch_semaphore_signal(semaphore);
}];
if (dispatch_semaphore_wait(semaphore, deadline)) {
bsg_log_debug(@"Timed out waiting for crash to be sent.");
}
}
6 changes: 4 additions & 2 deletions Bugsnag/BugsnagInternals.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
* or bugfix releases, and should not be used by projects outside of Bugsnag.
*/

#import "BugsnagHandledState.h"
#import "BugsnagNotifier.h"
#import "Payload/BugsnagHandledState.h"
#import "Payload/BugsnagNotifier.h"

@interface BSGFeatureFlagStore : NSObject <NSCopying>
@end
Expand Down Expand Up @@ -239,4 +239,6 @@ BUGSNAG_EXTERN NSString * BSGGetDefaultDeviceId(void);

BUGSNAG_EXTERN NSDictionary * BSGGetSystemInfo(void);

BUGSNAG_EXTERN NSTimeInterval BSGCrashSentryDeliveryTimeout;

NS_ASSUME_NONNULL_END
2 changes: 2 additions & 0 deletions Bugsnag/Configuration/BugsnagConfiguration.m
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ - (nonnull id)copyWithZone:(nullable NSZone *)zone {
[copy setRedactedKeys:self.redactedKeys];
[copy setLaunchDurationMillis:self.launchDurationMillis];
[copy setSendLaunchCrashesSynchronously:self.sendLaunchCrashesSynchronously];
[copy setAttemptDeliveryOnCrash:self.attemptDeliveryOnCrash];
[copy setMaxPersistedEvents:self.maxPersistedEvents];
[copy setMaxPersistedSessions:self.maxPersistedSessions];
[copy setMaxStringValueLength:self.maxStringValueLength];
Expand Down Expand Up @@ -169,6 +170,7 @@ - (instancetype)initWithApiKey:(NSString *)apiKey {
_enabledBreadcrumbTypes = BSGEnabledBreadcrumbTypeAll;
_launchDurationMillis = 5000;
_sendLaunchCrashesSynchronously = YES;
_attemptDeliveryOnCrash = NO;
_maxBreadcrumbs = 100;
_maxPersistedEvents = 32;
_maxPersistedSessions = 128;
Expand Down
2 changes: 2 additions & 0 deletions Bugsnag/Delivery/BSGEventUploader.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ BSG_OBJC_DIRECT_MEMBERS

- (void)uploadEvent:(BugsnagEvent *)event completionHandler:(nullable void (^)(void))completionHandler;

- (void)uploadKSCrashReportWithFile:(NSString *)file completionHandler:(nullable void (^)(void))completionHandler;

- (void)uploadStoredEvents;

- (void)uploadStoredEventsAfterDelay:(NSTimeInterval)delay;
Expand Down
6 changes: 6 additions & 0 deletions Bugsnag/Delivery/BSGEventUploader.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ - (void)uploadEvent:(BugsnagEvent *)event completionHandler:(nullable void (^)(v
[self.uploadQueue addOperation:operation];
}

- (void)uploadKSCrashReportWithFile:(NSString *)file completionHandler:(nullable void (^)(void))completionHandler {
BSGEventUploadKSCrashReportOperation *operation = [[BSGEventUploadKSCrashReportOperation alloc] initWithFile:file delegate:self];
operation.completionBlock = completionHandler;
[self.uploadQueue addOperation:operation];
}

- (void)uploadStoredEvents {
if (self.scanQueue.operationCount > 1) {
// Prevent too many scan operations being scheduled
Expand Down
1 change: 1 addition & 0 deletions Bugsnag/Helpers/BSGTelemetry.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ static BOOL IsStaticallyLinked(void) {
config[@"reportBackgroundAppHangs"] = BooleanValue(configuration.reportBackgroundAppHangs, defaults.reportBackgroundAppHangs);
#endif

config[@"attemptDeliveryOnCrash"] = BooleanValue(configuration.attemptDeliveryOnCrash, defaults.attemptDeliveryOnCrash);
config[@"autoDetectErrors"] = BooleanValue(configuration.autoDetectErrors, defaults.autoDetectErrors);
config[@"autoTrackSessions"] = BooleanValue(configuration.autoTrackSessions, defaults.autoTrackSessions);
config[@"discardClassesCount"] = IntegerValue(configuration.discardClasses.count, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,15 @@ void bsg_kscrashsentry_resumeThreads(void) {

void bsg_kscrashsentry_clearContext(BSG_KSCrash_SentryContext *context) {
void (*onCrash)(void *) = context->onCrash;
void (*attemptDelivery)(void) = context->attemptDelivery;
bool threadTracingEnabled = context->threadTracingEnabled;
thread_t reservedThreads[BSG_KSCrashReservedThreadTypeCount];
memcpy(reservedThreads, context->reservedThreads, sizeof(reservedThreads));

memset(context, 0, sizeof(*context));
context->onCrash = onCrash;

context->onCrash = onCrash;
context->attemptDelivery = attemptDelivery;
context->threadTracingEnabled = threadTracingEnabled;
memcpy(context->reservedThreads, reservedThreads, sizeof(reservedThreads));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ typedef struct BSG_KSCrash_SentryContext {
/** Called by the crash handler when a crash is detected. */
void (*onCrash)(void *);

/** BSGCrashSentryAttemptyDelivery */
void (*attemptDelivery)(void);

/**
* The methodology used for tracing threads.
* If true, will capture traces for all running threads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ void bsg_ksnsexc_i_handleException(NSException *exception) {
BSG_KSLOG_DEBUG(
"Crash handling complete. Restoring original handlers.");
bsg_kscrashsentry_uninstall(BSG_KSCrashTypeAll);

// Must run before endHandlingCrash unblocks secondary crashed threads.
BSG_KSCrash_Context *context = crashContext();
if (context->crash.attemptDelivery) {
BSG_KSLOG_DEBUG("Attempting delivery.");
context->crash.attemptDelivery();
}

bsg_kscrashsentry_endHandlingCrash();
}

Expand Down
27 changes: 27 additions & 0 deletions Bugsnag/include/Bugsnag/BugsnagConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,33 @@ BUGSNAG_EXTERN
*/
@property (nonatomic) BSGTelemetryOptions telemetry;

// =============================================================================
// MARK: - Experimental
// =============================================================================

/**
* Whether Bugsnag should try to send crashing errors prior to app termination.
*
* Delivery will only be attempted for uncaught Objective-C exceptions, and
* while in progress will block the crashing thread for up to 3 seconds.
*
* Delivery will be unreliable due to the necessary short timeout and potential
* memory corruption that caused the crashing error in the first place.
*
* If it fails prior to termination, delivery will be reattempted at next launch
* (the default behavior).
*
* Use of this feature is discouraged because it:
* - may cause the app to hang while delivery occurs and impact the hang rate
* reported in Xcode Organizer
* - will result in duplicate crashes in your dashboard for crashes that were
* fully sent but without receiving an HTTP response within the timeout
* - may prevent other crash reporters from detecting the crash.
*
* By default this value is false.
*/
@property (nonatomic) BOOL attemptDeliveryOnCrash;

// =============================================================================
// MARK: - Plugins
// =============================================================================
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

## TBD

### Enhancements

* Add (experimental) `configuration.attemptDeliveryOnCrash` to allow uncaught
Objective-C exceptions to be sent at crash time, prior to app termination.
[#1488](https://github.com/bugsnag/bugsnag-cocoa/pull/1488)

## 6.23.1 (2022-09-21)

### Bug fixes
Expand Down
15 changes: 15 additions & 0 deletions features/delivery.feature
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,18 @@ Feature: Delivery of errors
And the event "usage.system.breadcrumbsRemoved" equals 17
And the event "usage.system.stringCharsTruncated" is not null
And the event "usage.system.stringsTruncated" is not null

Scenario: Attempt Delivery On Crash
When I run "AttemptDeliveryOnCrashScenario"
And I wait to receive an error
Then the error is valid for the error reporting API
And the event "context" equals "OnSendError"
And the event "metaData.error.nsexception.name" equals "NSRangeException"
And the event "metaData.error.type" equals "nsexception"
And the event "unhandled" is true
And the event "usage.config.attemptDeliveryOnCrash" is true
And I discard the oldest error
And I relaunch the app after a crash
And I configure Bugsnag for "AttemptDeliveryOnCrashScenario"
And I wait to receive 2 sessions
Then I should receive no error
8 changes: 4 additions & 4 deletions features/fixtures/ios/iOSTestApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
017DCFA028743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCF9F28743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift */; };
01847DD626453D4E00ADA4C7 /* InvalidCrashReportScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01847DD526453D4E00ADA4C7 /* InvalidCrashReportScenario.m */; };
0184DBE028C63F51006AF50B /* CouldNotCreateDirectoryScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0184DBDF28C63F50006AF50B /* CouldNotCreateDirectoryScenario.swift */; };
018F9B6528E57DBE00EAA02F /* AttemptDeliveryOnCrashScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018F9B6428E57DBE00EAA02F /* AttemptDeliveryOnCrashScenario.swift */; };
01AF6A53258A112F00FFC803 /* BareboneTestHandledScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AF6A52258A112F00FFC803 /* BareboneTestHandledScenario.swift */; };
01AFCFCB282CE9F700D48D45 /* OldSessionScenario.m in Sources */ = {isa = PBXBuildFile; fileRef = 01AFCFCA282CE9F700D48D45 /* OldSessionScenario.m */; };
01B6BB7525D5748800FC4DE6 /* LastRunInfoScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B6BB7425D5748800FC4DE6 /* LastRunInfoScenario.swift */; };
Expand Down Expand Up @@ -258,6 +259,7 @@
017DCF9F28743FB5000ECB22 /* TelemetryUsageDisabledScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelemetryUsageDisabledScenario.swift; sourceTree = "<group>"; };
01847DD526453D4E00ADA4C7 /* InvalidCrashReportScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InvalidCrashReportScenario.m; sourceTree = "<group>"; };
0184DBDF28C63F50006AF50B /* CouldNotCreateDirectoryScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CouldNotCreateDirectoryScenario.swift; sourceTree = "<group>"; };
018F9B6428E57DBE00EAA02F /* AttemptDeliveryOnCrashScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttemptDeliveryOnCrashScenario.swift; sourceTree = "<group>"; };
01AF6A52258A112F00FFC803 /* BareboneTestHandledScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BareboneTestHandledScenario.swift; sourceTree = "<group>"; };
01AFCFCA282CE9F700D48D45 /* OldSessionScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OldSessionScenario.m; sourceTree = "<group>"; };
01B6BB7425D5748800FC4DE6 /* LastRunInfoScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LastRunInfoScenario.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -353,7 +355,6 @@
E7767F10221C21D90006648C /* StoppedSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoppedSessionScenario.swift; sourceTree = "<group>"; };
E7767F12221C21E30006648C /* ResumedSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumedSessionScenario.swift; sourceTree = "<group>"; };
E7767F14221C223C0006648C /* NewSessionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewSessionScenario.swift; sourceTree = "<group>"; };
E77AFEF72449C6460082B8BB /* AttachCustomStacktraceHook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AttachCustomStacktraceHook.h; sourceTree = "<group>"; };
E7A324D7247E70B2008B0052 /* SessionCallbackOverrideScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallbackOverrideScenario.swift; sourceTree = "<group>"; };
E7A324D9247E70C4008B0052 /* SessionCallbackCrashScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallbackCrashScenario.swift; sourceTree = "<group>"; };
E7A324DD247E70E6008B0052 /* SessionCallbackOrderScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallbackOrderScenario.swift; sourceTree = "<group>"; };
Expand All @@ -370,7 +371,6 @@
E7B79CD5247FD7750039FB88 /* AutoContextNSErrorScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoContextNSErrorScenario.swift; sourceTree = "<group>"; };
E7B79CD7247FD7810039FB88 /* AutoContextNSExceptionScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoContextNSExceptionScenario.swift; sourceTree = "<group>"; };
E7B79CD924800A5D0039FB88 /* MetadataMergeScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataMergeScenario.swift; sourceTree = "<group>"; };
E7F6087B244F0A3A00F1532A /* BugsnagHooks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BugsnagHooks.h; sourceTree = "<group>"; };
F429511ED32FC9FB46649CAE /* ObjCMsgSendScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCMsgSendScenario.m; sourceTree = "<group>"; };
F429521A8EEB435DCB6EACE1 /* ObjCExceptionScenario.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjCExceptionScenario.m; sourceTree = "<group>"; };
F429526319377A8848136413 /* HandledExceptionScenario.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HandledExceptionScenario.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -479,7 +479,7 @@
01F1474325F282E600C2DC65 /* AppHangScenario.swift */,
01018B9F25E40ADD000312C6 /* AsyncSafeMallocScenario.m */,
F4295CA6B6792DECA15C450B /* AsyncSafeThreadScenario.m */,
E77AFEF72449C6460082B8BB /* AttachCustomStacktraceHook.h */,
018F9B6428E57DBE00EAA02F /* AttemptDeliveryOnCrashScenario.swift */,
001E5501243B8FDA0009E31D /* AutoCaptureRunScenario.m */,
E7B79CD5247FD7750039FB88 /* AutoContextNSErrorScenario.swift */,
E7B79CD7247FD7810039FB88 /* AutoContextNSExceptionScenario.swift */,
Expand All @@ -499,7 +499,6 @@
E7A324E7247E9D9A008B0052 /* BreadcrumbCallbackOrderScenario.swift */,
E7A324E9247E9DA5008B0052 /* BreadcrumbCallbackOverrideScenario.swift */,
E7A324EC247E9DB3008B0052 /* BreadcrumbCallbackRemovalScenario.m */,
E7F6087B244F0A3A00F1532A /* BugsnagHooks.h */,
F42956D34274D4ED16B4D491 /* BuiltinTrapScenario.m */,
01DCB82A27985D2C0048640A /* ConcurrentCrashesScenario.mm */,
0184DBDF28C63F50006AF50B /* CouldNotCreateDirectoryScenario.swift */,
Expand Down Expand Up @@ -837,6 +836,7 @@
E7A324EA247E9DA5008B0052 /* BreadcrumbCallbackOverrideScenario.swift in Sources */,
F42955DB6D08642528917FAB /* CxxExceptionScenario.mm in Sources */,
017B4134276B8D9B0054C91D /* OnSendErrorPersistenceScenario.m in Sources */,
018F9B6528E57DBE00EAA02F /* AttemptDeliveryOnCrashScenario.swift in Sources */,
010BAB2D2833D0F20003FF36 /* DiscardClassesUnhandledExceptionScenario.swift in Sources */,
8A3B5F2B240807EE00CE4A3A /* ModifyBreadcrumbInNotifyScenario.swift in Sources */,
CBB787912578FC0C0071BDE4 /* MarkUnhandledHandledScenario.m in Sources */,
Expand Down
Loading