Skip to content

Commit 80711f4

Browse files
authored
fix(IoT): Fixing a potential race condition in the timer ring queue (#5461)
1 parent 582e289 commit 80711f4

File tree

5 files changed

+114
-18
lines changed

5 files changed

+114
-18
lines changed

AWSIoT/Internal/MQTTSDK/AWSMQTTSession.m

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#import "AWSMQTTEncoder.h"
2020
#import "AWSMQttTxFlow.h"
2121
#import "AWSIoTMessage.h"
22+
#import "AWSMQTTTimerRing.h"
2223
#import "AWSIoTMessage+AWSMQTTMessage.h"
2324

2425
@interface AWSMQTTSession () <AWSMQTTDecoderDelegate,AWSMQTTEncoderDelegate> {
@@ -58,7 +59,7 @@ - (void)send:(AWSMQTTMessage*)msg;
5859
- (UInt16)nextMsgId;
5960

6061
@property (strong,atomic) NSMutableArray* queue; //Queue to temporarily hold messages if encoder is busy sending another message
61-
@property (strong,atomic) NSMutableArray* timerRing; // circular array of 60. Each element is a set that contains the messages that need to be retried.
62+
@property (strong,atomic) AWSMQTTTimerRing* timerRing; // A collection of messages that need to be retried.
6263
@property (nonatomic, strong) dispatch_queue_t drainSenderSerialQueue;
6364
@property (nonatomic, strong) AWSMQTTEncoder* encoder; //Low level protocol handler that converts a message into out bound network data
6465
@property (nonatomic, strong) AWSMQTTDecoder* decoder; //Low level protocol handler that converts in bound network data into a Message
@@ -103,11 +104,7 @@ - (id)initWithClientId:(NSString*)theClientId
103104
txMsgId = 1;
104105
txFlows = [[NSMutableDictionary alloc] init];
105106
rxFlows = [[NSMutableDictionary alloc] init];
106-
self.timerRing = [[NSMutableArray alloc] initWithCapacity:60];
107-
int i;
108-
for (i = 0; i < 60; i++) {
109-
[self.timerRing addObject:[NSMutableSet new]];
110-
}
107+
self.timerRing = [[AWSMQTTTimerRing alloc] init];
111108
serialQueue = dispatch_queue_create("com.amazon.aws.iot.test-queue", DISPATCH_QUEUE_SERIAL);
112109
ticks = 0;
113110
status = AWSMQTTSessionStatusCreated;
@@ -233,7 +230,7 @@ - (UInt16)publishDataAtLeastOnce:(NSData*)data
233230
AWSMQttTxFlow *flow = [AWSMQttTxFlow flowWithMsg:msg
234231
deadline:deadline];
235232
[txFlows setObject:flow forKey:[NSNumber numberWithUnsignedInt:msgId]];
236-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] addObject:[NSNumber numberWithUnsignedInt:msgId]];
233+
[self.timerRing addMsgId:[NSNumber numberWithUnsignedInt:msgId] atTick:[flow deadline]];
237234
AWSDDLogDebug(@"Published message %hu for QOS 1", msgId);
238235
[self send:msg];
239236
return msgId;
@@ -267,7 +264,7 @@ - (UInt16)publishDataExactlyOnce:(NSData*)data
267264
AWSMQttTxFlow *flow = [AWSMQttTxFlow flowWithMsg:msg
268265
deadline:(ticks + 60)];
269266
[txFlows setObject:flow forKey:[NSNumber numberWithUnsignedInt:msgId]];
270-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] addObject:[NSNumber numberWithUnsignedInt:msgId]];
267+
[self.timerRing addMsgId:[NSNumber numberWithUnsignedInt:msgId] atTick:[flow deadline]];
271268
[self send:msg];
272269
return msgId;
273270
}
@@ -299,7 +296,7 @@ - (void)timerHandler:(NSTimer*)theTimer {
299296
dispatch_sync(serialQueue, ^{
300297
ticks++;
301298
});
302-
NSEnumerator *e = [[[self.timerRing objectAtIndex:(ticks % 60)] allObjects] objectEnumerator];
299+
NSEnumerator *e = [[self.timerRing allMsgIdsAtTick:ticks] objectEnumerator];
303300
id msgId;
304301

305302
//Stay under the throttle here and move the work to the next tick if throttle is breached.
@@ -321,8 +318,8 @@ - (void)timerHandler:(NSTimer*)theTimer {
321318
while ((msgId = [e nextObject])) {
322319
AWSMQttTxFlow *flow = [txFlows objectForKey:msgId];
323320
[flow setDeadline:((ticks +1) % 60)];
324-
[[self.timerRing objectAtIndex:((ticks + 1) % 60)] addObject:msgId];
325-
[[self.timerRing objectAtIndex:(ticks % 60)] removeObject:msgId];
321+
[self.timerRing addMsgId:msgId atTick:(ticks + 1)];
322+
[self.timerRing removeMsgId:msgId atTick:ticks];
326323
}
327324

328325
if (count > 0 ) {
@@ -567,8 +564,8 @@ - (void)handlePuback:(AWSMQTTMessage*)msg {
567564
if ([[flow msg] type] != AWSMQTTPublish || [[flow msg] qos] != 1) {
568565
return;
569566
}
570-
571-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] removeObject:msgId];
567+
568+
[self.timerRing removeMsgId:msgId atTick:[flow deadline]];
572569
[txFlows removeObjectForKey:msgId];
573570
AWSDDLogDebug(@"Removing msgID %@ from internal store for QOS1 guarantee", msgId);
574571
[self.delegate session:self newAckForMessageId:msgId.unsignedShortValue];
@@ -594,10 +591,10 @@ - (void)handlePubrec:(AWSMQTTMessage*)msg {
594591
}
595592
msg = [AWSMQTTMessage pubrelMessageWithMessageId:[msgId unsignedIntValue]];
596593
[flow setMsg:msg];
597-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] removeObject:msgId];
594+
[self.timerRing removeMsgId:msgId atTick:[flow deadline]];
598595
[flow setDeadline:(ticks + 60)];
599-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] addObject:msgId];
600-
596+
[self.timerRing addMsgId:msgId atTick:[flow deadline]];
597+
601598
[self send:msg];
602599
}
603600

@@ -638,8 +635,8 @@ - (void)handlePubcomp:(AWSMQTTMessage*)msg {
638635
if (flow == nil || [[flow msg] type] != AWSMQTTPubrel) {
639636
return;
640637
}
641-
642-
[[self.timerRing objectAtIndex:([flow deadline] % 60)] removeObject:msgId];
638+
639+
[self.timerRing removeMsgId:msgId atTick:[flow deadline]];
643640
[txFlows removeObjectForKey:msgId];
644641

645642
AWSDDLogDebug(@"Removing msgID %@ from internal store for QOS2 guarantee", msgId);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//
2+
// Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License").
5+
// You may not use this file except in compliance with the License.
6+
// A copy of the License is located at
7+
//
8+
// http://aws.amazon.com/apache2.0
9+
//
10+
// or in the "license" file accompanying this file. This file is distributed
11+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
// express or implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
//
15+
16+
#import <Foundation/Foundation.h>
17+
18+
NS_ASSUME_NONNULL_BEGIN
19+
20+
/// A circular collection containing the messages that need to be retried at a given clock tick.
21+
/// The maximum number of ticks is 60
22+
@interface AWSMQTTTimerRing: NSObject
23+
24+
- (void)addMsgId:(NSNumber *)msgId atTick:(NSUInteger)tick;
25+
- (void)removeMsgId:(NSNumber *)msgId atTick:(NSUInteger)tick;
26+
- (NSArray<NSNumber *> *)allMsgIdsAtTick:(NSUInteger)tick;
27+
28+
@end
29+
30+
NS_ASSUME_NONNULL_END
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// Copyright 2010-2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License").
5+
// You may not use this file except in compliance with the License.
6+
// A copy of the License is located at
7+
//
8+
// http://aws.amazon.com/apache2.0
9+
//
10+
// or in the "license" file accompanying this file. This file is distributed
11+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
// express or implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
//
15+
16+
#import "AWSMQTTTimerRing.h"
17+
18+
@interface AWSMQTTTimerRing()
19+
20+
@property (nonatomic, strong) NSLock *lock;
21+
// Array of 60, with each index being a tick, and its value a set containing the messages that need to be retried.
22+
@property (strong,atomic) NSMutableArray<NSMutableSet *>* timerRing;
23+
24+
@end
25+
26+
@implementation AWSMQTTTimerRing
27+
28+
- (instancetype)init
29+
{
30+
self = [super init];
31+
if (self) {
32+
_lock = [[NSLock alloc] init];
33+
_timerRing = [[NSMutableArray alloc] initWithCapacity:60];
34+
int i;
35+
for (i = 0; i < 60; i++) {
36+
[_timerRing addObject:[NSMutableSet new]];
37+
}
38+
}
39+
return self;
40+
}
41+
- (void)addMsgId:(NSNumber *)msgId atTick:(NSUInteger)tick {
42+
[self.lock lock];
43+
[[self.timerRing objectAtIndex:(tick % 60)] addObject:msgId];
44+
[self.lock unlock];
45+
}
46+
47+
- (void)removeMsgId:(NSNumber *)msgId atTick:(NSUInteger)tick {
48+
[self.lock lock];
49+
[[self.timerRing objectAtIndex:(tick % 60)] removeObject:msgId];
50+
[self.lock unlock];
51+
}
52+
53+
- (NSArray<NSNumber *> *)allMsgIdsAtTick:(NSUInteger)tick {
54+
[self.lock lock];
55+
NSArray<NSNumber *> *result = [[self.timerRing objectAtIndex:(tick % 60)] allObjects];
56+
[self.lock unlock];
57+
return result;
58+
}
59+
60+
@end

AWSiOSSDKv2.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,8 @@
598598
5C1590172755727C00F88085 /* AWSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE0D416D1C6A66E5006B91B5 /* AWSCore.framework */; };
599599
5C1978DD2702364800F9C11E /* AWSLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1978DC2702364800F9C11E /* AWSLocationTests.swift */; };
600600
5C71F33F295672B8001183A4 /* guten_tag.wav in Resources */ = {isa = PBXBuildFile; fileRef = 5C71F33E295672B8001183A4 /* guten_tag.wav */; };
601+
685AA2112CDA7843008EFC7B /* AWSMQTTTimerRing.h in Headers */ = {isa = PBXBuildFile; fileRef = 685AA20F2CDA7843008EFC7B /* AWSMQTTTimerRing.h */; };
602+
685AA2122CDA7843008EFC7B /* AWSMQTTTimerRing.m in Sources */ = {isa = PBXBuildFile; fileRef = 685AA2102CDA7843008EFC7B /* AWSMQTTTimerRing.m */; };
601603
687952932B8FE2C5001E8990 /* AWSDDLog+Optional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 687952922B8FE2C5001E8990 /* AWSDDLog+Optional.swift */; };
602604
6883619E2B72D1C200D74FF4 /* AWSS3PreSignedURLBuilderUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6883619D2B72D1C200D74FF4 /* AWSS3PreSignedURLBuilderUnitTests.swift */; };
603605
688361A12B73D25B00D74FF4 /* AWSIoTStreamThreadTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688361A02B73D25B00D74FF4 /* AWSIoTStreamThreadTests.m */; };
@@ -3215,6 +3217,8 @@
32153217
5C1978DB2702364800F9C11E /* AWSLocationTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AWSLocationTests-Bridging-Header.h"; sourceTree = "<group>"; };
32163218
5C1978DC2702364800F9C11E /* AWSLocationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSLocationTests.swift; sourceTree = "<group>"; };
32173219
5C71F33E295672B8001183A4 /* guten_tag.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = guten_tag.wav; sourceTree = "<group>"; };
3220+
685AA20F2CDA7843008EFC7B /* AWSMQTTTimerRing.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AWSMQTTTimerRing.h; sourceTree = "<group>"; };
3221+
685AA2102CDA7843008EFC7B /* AWSMQTTTimerRing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWSMQTTTimerRing.m; sourceTree = "<group>"; };
32183222
687952922B8FE2C5001E8990 /* AWSDDLog+Optional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSDDLog+Optional.swift"; sourceTree = "<group>"; };
32193223
6883619D2B72D1C200D74FF4 /* AWSS3PreSignedURLBuilderUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AWSS3PreSignedURLBuilderUnitTests.swift; sourceTree = "<group>"; };
32203224
688361A02B73D25B00D74FF4 /* AWSIoTStreamThreadTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AWSIoTStreamThreadTests.m; sourceTree = "<group>"; };
@@ -7244,6 +7248,8 @@
72447248
CE9DE6461C6A78D70060793F /* AWSMQTTSession.m */,
72457249
CE9DE6471C6A78D70060793F /* AWSMQttTxFlow.h */,
72467250
CE9DE6481C6A78D70060793F /* AWSMQttTxFlow.m */,
7251+
685AA20F2CDA7843008EFC7B /* AWSMQTTTimerRing.h */,
7252+
685AA2102CDA7843008EFC7B /* AWSMQTTTimerRing.m */,
72477253
);
72487254
path = MQTTSDK;
72497255
sourceTree = "<group>";
@@ -8455,6 +8461,7 @@
84558461
files = (
84568462
CE9DE6521C6A78D70060793F /* AWSIoTDataResources.h in Headers */,
84578463
CE9DE65A1C6A78D70060793F /* AWSIoTResources.h in Headers */,
8464+
685AA2112CDA7843008EFC7B /* AWSMQTTTimerRing.h in Headers */,
84588465
68EE1A6C2B713D8100B7CF41 /* AWSIoTStreamThread.h in Headers */,
84598466
CE9DE6231C6A78AF0060793F /* AWSIoT.h in Headers */,
84608467
CE9DE6561C6A78D70060793F /* AWSIoTManager.h in Headers */,
@@ -13398,6 +13405,7 @@
1339813405
CE9DE66D1C6A78D70060793F /* AWSMQTTSession.m in Sources */,
1339913406
CE9DE6551C6A78D70060793F /* AWSIoTDataService.m in Sources */,
1340013407
0342776A269D185200379263 /* AWSIoTMessage+AWSMQTTMessage.m in Sources */,
13408+
685AA2122CDA7843008EFC7B /* AWSMQTTTimerRing.m in Sources */,
1340113409
CE9DE66B1C6A78D70060793F /* AWSMQTTMessage.m in Sources */,
1340213410
CE9DE65D1C6A78D70060793F /* AWSIoTService.m in Sources */,
1340313411
68DD11872C5AF52B004E1C37 /* AWSIoTAtomicDictionary.m in Sources */,

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- **AWSIoT**
88
- Fixing a race condition when invalidating/creating the reconnect timer (#5454)
9+
- Fixing a potential race condition in the timer ring queue (#5461)
910

1011
## 2.38.0
1112

0 commit comments

Comments
 (0)