Skip to content

Commit

Permalink
fix: Ensure accurate app/device info after restart (match Android SDK…
Browse files Browse the repository at this point in the history
… behavior)

* Store app and device info in session record

This brings the iOS SDK in line with the Android SDK to ensure that session begin and end events always have the same app and device info such as app and iOS versions. Previously, if the app was updated before all sessions were finished, the end session event would have the new app version and not match up with the begin session event. This could also apply to any additional events from that previous session that were waiting to be uploaded.

* Fix thread deadlock race condition in MPApplication

After finally getting it to reproduce reliably, I was able to narrow the problem down to the call to the `badgeNumber` property accessor in `MPApplication`.

The root issue was the fact that the `applicationIconBadgeNumber` property of `UIApplication` must be read from the main thread, so there was a dispatch_sync to the main queue used to read it. However, after moving the timing of that read from the time of event upload to the time of session creation, it could trigger a deadlock if the main thread happened to currently be dispatch_syncing a block to the serial message queue while this was running on the message queue and dispatch_syncing to the main queue.

To address this, I’ve changed the behavior of this property to instead read from user defaults and to update that value when `MPStateMachine` is initialized, as well as when the app enters the background or foreground (also inside `MPStateMachine` for consistency). This removes the need for the dispatch_sync at all as user defaults is thread safe and the updating of the value always happens on the main thread (or is asynced to the main thread if not).
  • Loading branch information
einsteinx2 committed Jul 29, 2021
1 parent c274e42 commit e1fdc94
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 35 deletions.
8 changes: 7 additions & 1 deletion mParticle-Apple-SDK/Data Model/MPSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
@property (nonatomic, unsafe_unretained, readonly) BOOL persisted;
@property (nonatomic, strong, readwrite, nonnull) NSNumber *userId;
@property (nonatomic, strong, readwrite, nonnull) NSString *sessionUserIds;
@property (nonatomic, strong, readwrite, nullable) NSDictionary<NSString *, id> *appInfo;
@property (nonatomic, strong, readwrite, nullable) NSDictionary *deviceInfo;


- (nonnull instancetype)initWithStartTime:(NSTimeInterval)timestamp userId:(nonnull NSNumber *)userId;
- (nonnull instancetype)initWithStartTime:(NSTimeInterval)timestamp userId:(nonnull NSNumber *)userId uuid:(nullable NSString *)uuid;
Expand All @@ -29,7 +32,10 @@
eventCounter:(uint)eventCounter
suspendTime:(NSTimeInterval)suspendTime
userId:(nonnull NSNumber *)userId
sessionUserIds:(nonnull NSString *)sessionUserIds __attribute__((objc_designated_initializer));
sessionUserIds:(nonnull NSString *)sessionUserIds
appInfo:(nullable NSDictionary<NSString *, id> *)appInfo
deviceInfo:(nullable NSDictionary *)deviceInfo
__attribute__((objc_designated_initializer));

- (void)incrementCounter;
- (void)suspendSession;
Expand Down
39 changes: 36 additions & 3 deletions mParticle-Apple-SDK/Data Model/MPSession.m
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
#import "MPSession.h"
#import "MPIConstants.h"
#import "MPPersistenceController.h"
#import "MPApplication.h"
#import "MPDevice.h"

NSString *const sessionUUIDKey = @"sessionId";

@implementation MPSession

- (instancetype)init {
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
return [self initWithSessionId:0 UUID:[[NSUUID UUID] UUIDString] backgroundTime:0.0 startTime:now endTime:now attributes:nil numberOfInterruptions:0 eventCounter:0 suspendTime:0 userId:[MPPersistenceController mpId] sessionUserIds:[[MPPersistenceController mpId] stringValue]];
NSNumber *mpId = [MPPersistenceController mpId];
return [self initWithSessionId:0
UUID:[[NSUUID UUID] UUIDString]
backgroundTime:0.0
startTime:now
endTime:now
attributes:nil
numberOfInterruptions:0
eventCounter:0
suspendTime:0
userId:mpId
sessionUserIds:[mpId stringValue]
appInfo:nil
deviceInfo:nil];
}

- (instancetype)initWithStartTime:(NSTimeInterval)timestamp userId:(NSNumber *)userId {
Expand All @@ -19,7 +34,19 @@ - (instancetype)initWithStartTime:(NSTimeInterval)timestamp userId:(NSNumber *)u

- (instancetype)initWithStartTime:(NSTimeInterval)timestamp userId:(NSNumber *)userId uuid:(NSString *)uuid {
NSString *uuidString = uuid ?: [[NSUUID UUID] UUIDString];
self = [self initWithSessionId:0 UUID:uuidString backgroundTime:0.0 startTime:timestamp endTime:timestamp attributes:nil numberOfInterruptions:0 eventCounter:0 suspendTime:0 userId:userId sessionUserIds:[userId stringValue]];
self = [self initWithSessionId:0
UUID:uuidString
backgroundTime:0.0
startTime:timestamp
endTime:timestamp
attributes:nil
numberOfInterruptions:0
eventCounter:0
suspendTime:0
userId:userId
sessionUserIds:[userId stringValue]
appInfo:nil
deviceInfo:nil];

return self;
}
Expand All @@ -35,6 +62,8 @@ - (instancetype)initWithSessionId:(int64_t)sessionId
suspendTime:(NSTimeInterval)suspendTime
userId:(NSNumber *)userId
sessionUserIds:(NSString *)sessionUserIds
appInfo:(nullable NSDictionary<NSString *,id> *)appInfo
deviceInfo:(nullable NSDictionary *)deviceInfo
{
self = [super init];
if (!self) {
Expand All @@ -52,6 +81,8 @@ - (instancetype)initWithSessionId:(int64_t)sessionId
_numberOfInterruptions = numberOfInterruptions;
_suspendTime = suspendTime;
_sessionUserIds = sessionUserIds;
_appInfo = appInfo;
_deviceInfo = deviceInfo;

_attributesDictionary = attributesDictionary != nil ? attributesDictionary : [[NSMutableDictionary alloc] init];

Expand Down Expand Up @@ -88,7 +119,9 @@ - (id)copyWithZone:(NSZone *)zone {
eventCounter:_eventCounter
suspendTime:_suspendTime
userId:_userId
sessionUserIds:_sessionUserIds];
sessionUserIds:_sessionUserIds
appInfo:_appInfo
deviceInfo:_deviceInfo];

return copyObject;
}
Expand Down
14 changes: 12 additions & 2 deletions mParticle-Apple-SDK/MPBackendController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#import "MPArchivist.h"
#import "MPListenerController.h"
#import "MParticleWebView.h"
#import "MPDevice.h"

#if TARGET_OS_IOS == 1
#import "MPLocationManager.h"
Expand Down Expand Up @@ -1146,11 +1147,20 @@ - (void)beginSessionWithIsManual:(BOOL)isManual date:(NSDate *)date {
MPStateMachine *stateMachine = [MParticle sharedInstance].stateMachine;
MPPersistenceController *persistence = [MParticle sharedInstance].persistenceController;

NSNumber *mpId = [MPPersistenceController mpId];
date = date ?: [NSDate date];
if (tempSession) {
self.session = [[MPSession alloc] initWithStartTime:[date timeIntervalSince1970] userId:[MPPersistenceController mpId] uuid:tempSession.UUID];
self.session = [[MPSession alloc] initWithStartTime:[date timeIntervalSince1970] userId:mpId uuid:tempSession.UUID];
} else {
self.session = [[MPSession alloc] initWithStartTime:[date timeIntervalSince1970] userId:[MPPersistenceController mpId]];
self.session = [[MPSession alloc] initWithStartTime:[date timeIntervalSince1970] userId:mpId];
}

// Set the app and device info dicts if they weren't already created
if (!_session.appInfo) {
_session.appInfo = [[[MPApplication alloc] init] dictionaryRepresentation];
}
if (!_session.deviceInfo) {
_session.deviceInfo = [[[MPDevice alloc] init] dictionaryRepresentationWithMpid:mpId];
}

[persistence saveSession:_session];
Expand Down
17 changes: 14 additions & 3 deletions mParticle-Apple-SDK/Persistence/MPDatabaseMigrationController.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,13 @@ - (void)migrateSessionsFromDatabase:(sqlite3 *)oldDatabase version:(NSNumber *)o
selectStatement = "SELECT uuid, start_time, end_time, attributes_data, session_number, background_time, number_interruptions, event_count, suspend_time FROM sessions ORDER BY _id";
} else if (oldVersionValue < 26) {
selectStatement = "SELECT uuid, start_time, end_time, attributes_data, session_number, background_time, number_interruptions, event_count, suspend_time, length FROM sessions ORDER BY _id";
} else {
} else if (oldVersionValue < 30) {
selectStatement = "SELECT uuid, start_time, end_time, attributes_data, session_number, background_time, number_interruptions, event_count, suspend_time, length, mpid, session_user_ids FROM sessions ORDER BY _id";
} else {
selectStatement = "SELECT uuid, start_time, end_time, attributes_data, session_number, background_time, number_interruptions, event_count, suspend_time, length, mpid, session_user_ids, app_info, device_info FROM sessions ORDER BY _id";
}

insertStatement = "INSERT INTO sessions (uuid, background_time, start_time, end_time, attributes_data, session_number, number_interruptions, event_count, suspend_time, length, mpid, session_user_ids) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
insertStatement = "INSERT INTO sessions (uuid, background_time, start_time, end_time, attributes_data, session_number, number_interruptions, event_count, suspend_time, length, mpid, session_user_ids, app_info, device_info) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";

sqlite3_prepare_v2(oldDatabase, selectStatement, -1, &selectStatementHandle, NULL);
sqlite3_prepare_v2(newDatabase, insertStatement, -1, &insertStatementHandle, NULL);
Expand Down Expand Up @@ -168,7 +170,16 @@ - (void)migrateSessionsFromDatabase:(sqlite3 *)oldDatabase version:(NSNumber *)o
sqlite3_bind_double(insertStatementHandle, 10, length); // length
sqlite3_bind_int64(insertStatementHandle, 11, [mpId longLongValue]); // mpid
sqlite3_bind_text(insertStatementHandle, 12, (const char *)[mpId stringValue].UTF8String, -1, SQLITE_TRANSIENT); // session_user_ids


if (oldVersionValue > 29) {
sqlite3_bind_blob(insertStatementHandle, 13, sqlite3_column_blob(selectStatementHandle, 12), sqlite3_column_bytes(selectStatementHandle, 12), SQLITE_TRANSIENT);
sqlite3_bind_blob(insertStatementHandle, 14, sqlite3_column_blob(selectStatementHandle, 13), sqlite3_column_bytes(selectStatementHandle, 13), SQLITE_TRANSIENT);
} else {
// Columns don't exist in old table, so just explicitly bind null
sqlite3_bind_null(insertStatementHandle, 13);
sqlite3_bind_null(insertStatementHandle, 14);
}

sqlite3_step(insertStatementHandle);
sqlite3_reset(insertStatementHandle);
}
Expand Down
1 change: 1 addition & 0 deletions mParticle-Apple-SDK/Persistence/MPPersistenceController.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
- (void)saveUpload:(nonnull MPUpload *)upload;
- (void)updateConsumerInfo:(nonnull MPConsumerInfo *)consumerInfo;
- (void)updateSession:(nonnull MPSession *)session;
- (nonnull NSDictionary<NSString *, NSDictionary *> *)appAndDeviceInfoForSessionId:(nonnull NSNumber *)sessionId;

@end

Loading

0 comments on commit e1fdc94

Please sign in to comment.