Skip to content

Commit 4f88262

Browse files
authored
fix(iot): fix race conditions and crashes (#5511)
* fix(iot): fix race conditions and crashes * fix syntax error * add tests * work on review comments
1 parent 86acd5a commit 4f88262

File tree

7 files changed

+1343
-16
lines changed

7 files changed

+1343
-16
lines changed

AWSIoT/AWSIoTDataManager.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ NS_ASSUME_NONNULL_BEGIN
359359
360360
*Objective-C*
361361
362-
AWSIoTDataManager *IoTDataManager = [AWSIoTDataManager IoTDataManagerForKey:@"USWest2IoTDataManager"];
362+
AWSIoTDataManager *IoTDataManager = [AWSIoTDataManager IoTDataManagerForKey:@"USWest2IoTDataManager"];
363363
364364
@warning After calling this method, do not modify the configuration object. It may cause unspecified behaviors.
365365
@@ -538,9 +538,10 @@ DEPRECATED_MSG_ATTRIBUTE("Use `updateUserMetaData` for updating the user meta da
538538
statusCallback:(void (^)(AWSIoTMQTTStatus status))callback;
539539

540540
/**
541-
Disconnect from a mqtt client (close current mqtt session)
541+
Disconnect from the AWS IoT Service. This will also cancel any active
542+
subscriptions to topics.
542543
*/
543-
- (void)disconnect;
544+
- (void) disconnect;
544545

545546
/**
546547
Get the current connection status

AWSIoT/AWSIoTDataManager.m

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,10 @@ - (void)addUserMetaData:(NSDictionary<NSString *, NSString *> *)userMetaDataMap
356356
if (userMetaDataMap) {
357357
for (id key in userMetaDataMap) {
358358
if (!([key isEqualToString:@"SDK"] || [key isEqualToString:@"Version"])) {
359-
[userMetaDataString appendFormat:@"&%@=%@", key, [userMetaDataMap objectForKey:key]];
359+
[userMetaDataString appendFormat:@"&%@", key];
360+
if (!([(NSString *)[userMetaDataMap objectForKey:key] isEqualToString:@""] || [userMetaDataMap objectForKey:key] == nil)){
361+
[userMetaDataString appendFormat:@"=%@", [userMetaDataMap objectForKey:key]];
362+
}
360363
} else {
361364
AWSDDLogWarn(@"Keynames 'SDK' and 'Version' are reserved and will be skipped");
362365
}
@@ -560,14 +563,37 @@ - (BOOL)connectUsingWebSocketWithClientId:(NSString *)clientId
560563
statusCallback:callback];
561564
}
562565

563-
- (void)disconnect{
564-
if ( !_userDidIssueConnect || _userDidIssueDisconnect ) {
565-
//Have to be connected to make this call. noop this call by returning
566-
return ;
566+
- (void)disconnect {
567+
// Check if already disconnected to prevent multiple disconnect calls
568+
if (!_userDidIssueConnect || _userDidIssueDisconnect) {
569+
// Already disconnected, no-op
570+
return;
567571
}
572+
573+
// Update state flags
568574
_userDidIssueConnect = NO;
569575
_userDidIssueDisconnect = YES;
570-
[self.mqttClient disconnect];
576+
577+
// Execute the disconnect on the main thread for stability
578+
// The AWS IoT MQTT client uses NSRunLoop which works best on the main thread
579+
if ([NSThread isMainThread]) {
580+
// Already on main thread, call directly
581+
[self.mqttClient disconnect];
582+
583+
// Force a runloop cycle to allow disconnect processing to start
584+
// This doesn't add a delay but ensures that disconnect processing has begun
585+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
586+
} else {
587+
// Not on main thread, dispatch to main thread and wait for it to complete
588+
// This ensures the disconnect is processed before any potential credential clearing
589+
dispatch_sync(dispatch_get_main_queue(), ^{
590+
[self.mqttClient disconnect];
591+
592+
// Force a runloop cycle to allow disconnect processing to start
593+
// This doesn't add a delay but ensures that disconnect processing has begun
594+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
595+
});
596+
}
571597
}
572598

573599
- (AWSIoTMQTTStatus) getConnectionStatus {

AWSIoT/Internal/AWSIoTMQTTClient.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,6 @@ - (void)initWebSocketConnectionForURL:(NSString *)urlString {
620620
}
621621

622622
- (void)disconnect {
623-
624623
if (self.userDidIssueDisconnect ) {
625624
//Issuing disconnect multiple times. Turn this function into a noop by returning here.
626625
return;
@@ -637,8 +636,13 @@ - (void)disconnect {
637636
[self.session disconnect];
638637
self.connectionAgeInSeconds = 0;
639638

640-
//Cancel the current streams thread
641-
[self.streamsThread cancelAndDisconnect:YES];
639+
//Cancel the current streams thread - synchronize this to prevent race conditions
640+
@synchronized(self) {
641+
if (self.streamsThread && !self.streamsThread.isCancelled) {
642+
AWSDDLogVerbose(@"Cancelling stream thread during disconnect: [%@]", self.streamsThread);
643+
[self.streamsThread cancelAndDisconnect:YES];
644+
}
645+
}
642646

643647
__weak AWSIoTMQTTClient *weakSelf = self;
644648
self.streamsThread.onStop = ^{

AWSIoT/Internal/AWSIoTStreamThread.m

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,21 @@ - (void)cleanUp {
143143
}
144144

145145
if (self.shouldDisconnect) {
146+
// Properly handle session closure first
146147
if (self.session) {
147148
[self.session close];
148149
self.session = nil;
149150
}
150151

152+
// Make sure we handle the streams in a thread-safe way
151153
if (self.outputStream) {
154+
// Remove from runLoop first before closing
155+
if (self.runLoopForStreamsThread) {
156+
[self.outputStream removeFromRunLoop:self.runLoopForStreamsThread
157+
forMode:NSDefaultRunLoopMode];
158+
}
152159
self.outputStream.delegate = nil;
153160
[self.outputStream close];
154-
[self.outputStream removeFromRunLoop:self.runLoopForStreamsThread
155-
forMode:NSDefaultRunLoopMode];
156161
self.outputStream = nil;
157162
}
158163

@@ -169,9 +174,13 @@ - (void)cleanUp {
169174
AWSDDLogVerbose(@"Skipping disconnect for thread: [%@]", (NSThread *)self);
170175
}
171176

172-
if (self.onStop) {
173-
self.onStop();
177+
// Make sure onStop is called on the main thread to avoid UI issues
178+
void (^stopBlock)(void) = self.onStop;
179+
if (stopBlock) {
174180
self.onStop = nil;
181+
dispatch_async(dispatch_get_main_queue(), ^{
182+
stopBlock();
183+
});
175184
}
176185
});
177186
}

0 commit comments

Comments
 (0)