فهرست منبع

Make readyState KV Observable and fully thread-safe. (#420)

Nikita Lutsenko 9 سال پیش
والد
کامیت
e22b67e56a

+ 22 - 0
SocketRocket.xcodeproj/project.pbxproj

@@ -33,6 +33,14 @@
 		8133640C1D091E1B0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; };
 		8133640E1D091E1B0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; };
 		8133640F1D091E1C0062E28D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; };
+		817491A81D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; };
+		817491A91D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; };
+		817491AA1D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; };
+		817491AB1D1C8C33006E09DF /* SRMutex.h in Headers */ = {isa = PBXBuildFile; fileRef = 817491A61D1C8C33006E09DF /* SRMutex.h */; };
+		817491AC1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; };
+		817491AD1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; };
+		817491AE1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; };
+		817491AF1D1C8C33006E09DF /* SRMutex.m in Sources */ = {isa = PBXBuildFile; fileRef = 817491A71D1C8C33006E09DF /* SRMutex.m */; };
 		817995861CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
 		817995871CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
 		817995881CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
@@ -193,6 +201,8 @@
 		8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnOperation.m; sourceTree = "<group>"; };
 		8105E5271CDD98E100AA12DB /* autobahn_configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = autobahn_configuration.json; sourceTree = "<group>"; };
 		811934B11CDAF711003AB243 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		817491A61D1C8C33006E09DF /* SRMutex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRMutex.h; sourceTree = "<group>"; };
+		817491A71D1C8C33006E09DF /* SRMutex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRMutex.m; sourceTree = "<group>"; };
 		817995841CE139700084DA37 /* SRDelegateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRDelegateController.h; sourceTree = "<group>"; };
 		817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = "<group>"; };
 		8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnUtilities.h; sourceTree = "<group>"; };
@@ -432,6 +442,8 @@
 				81C22BC11D124168007BFDDF /* SRHTTPConnectMessage.m */,
 				81900A4A1D18C9CC0015A290 /* SRLog.h */,
 				81900A4B1D18C9CC0015A290 /* SRLog.m */,
+				817491A61D1C8C33006E09DF /* SRMutex.h */,
+				817491A71D1C8C33006E09DF /* SRMutex.m */,
 				81C22BF61D1256E1007BFDDF /* SRRandom.h */,
 				81C22BF71D1256E1007BFDDF /* SRRandom.m */,
 				81B22EE21CE43ECC0073C636 /* SRURLUtilities.h */,
@@ -574,6 +586,7 @@
 				81C22BF91D1256E1007BFDDF /* SRRandom.h in Headers */,
 				81C22BC31D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */,
 				817995871CE139700084DA37 /* SRDelegateController.h in Headers */,
+				817491A91D1C8C33006E09DF /* SRMutex.h in Headers */,
 				81B22EC61CE42D7E0073C636 /* SRError.h in Headers */,
 				81B31C601CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
@@ -597,6 +610,7 @@
 				81C22BFB1D1256E1007BFDDF /* SRRandom.h in Headers */,
 				81C22BC51D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */,
 				817995891CE139700084DA37 /* SRDelegateController.h in Headers */,
+				817491AB1D1C8C33006E09DF /* SRMutex.h in Headers */,
 				81B22EC81CE42D7E0073C636 /* SRError.h in Headers */,
 				81B31C621CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
@@ -620,6 +634,7 @@
 				81C22BFA1D1256E1007BFDDF /* SRRandom.h in Headers */,
 				81C22BC41D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */,
 				817995881CE139700084DA37 /* SRDelegateController.h in Headers */,
+				817491AA1D1C8C33006E09DF /* SRMutex.h in Headers */,
 				81B22EC71CE42D7E0073C636 /* SRError.h in Headers */,
 				81B31C611CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
@@ -643,6 +658,7 @@
 				81C22BF81D1256E1007BFDDF /* SRRandom.h in Headers */,
 				81C22BC21D124168007BFDDF /* SRHTTPConnectMessage.h in Headers */,
 				817995861CE139700084DA37 /* SRDelegateController.h in Headers */,
+				817491A81D1C8C33006E09DF /* SRMutex.h in Headers */,
 				81B22EC51CE42D7E0073C636 /* SRError.h in Headers */,
 				81B31C5F1CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
@@ -846,6 +862,7 @@
 				81B31C211CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B22EE91CE43ECC0073C636 /* SRURLUtilities.m in Sources */,
 				8133640C1D091E1B0062E28D /* SRProxyConnect.m in Sources */,
+				817491AD1D1C8C33006E09DF /* SRMutex.m in Sources */,
 				81B31C641CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81900A511D18C9CC0015A290 /* SRLog.m in Sources */,
 				81B31C321CDC406B00D86D43 /* SRHash.m in Sources */,
@@ -868,6 +885,7 @@
 				81B31C231CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B22EEB1CE43ECC0073C636 /* SRURLUtilities.m in Sources */,
 				8133640F1D091E1C0062E28D /* SRProxyConnect.m in Sources */,
+				817491AF1D1C8C33006E09DF /* SRMutex.m in Sources */,
 				81B31C661CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81900A531D18C9CC0015A290 /* SRLog.m in Sources */,
 				81B31C341CDC406B00D86D43 /* SRHash.m in Sources */,
@@ -901,6 +919,7 @@
 				81B31C221CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B22EEA1CE43ECC0073C636 /* SRURLUtilities.m in Sources */,
 				8133640E1D091E1B0062E28D /* SRProxyConnect.m in Sources */,
+				817491AE1D1C8C33006E09DF /* SRMutex.m in Sources */,
 				81B31C651CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81900A521D18C9CC0015A290 /* SRLog.m in Sources */,
 				81B31C331CDC406B00D86D43 /* SRHash.m in Sources */,
@@ -923,6 +942,7 @@
 				F6A12CD2145119B700C1D980 /* SRWebSocket.m in Sources */,
 				81B31C201CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B22EE81CE43ECC0073C636 /* SRURLUtilities.m in Sources */,
+				817491AC1D1C8C33006E09DF /* SRMutex.m in Sources */,
 				81B31C631CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81900A501D18C9CC0015A290 /* SRLog.m in Sources */,
 				81B31C311CDC406B00D86D43 /* SRHash.m in Sources */,
@@ -1274,6 +1294,7 @@
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PUBLIC_HEADERS_FOLDER_PATH = include/$PRODUCT_NAME;
 				SKIP_INSTALL = YES;
+				WARNING_CFLAGS = "-Wthread-safety";
 			};
 			name = Debug;
 		};
@@ -1287,6 +1308,7 @@
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PUBLIC_HEADERS_FOLDER_PATH = include/$PRODUCT_NAME;
 				SKIP_INSTALL = YES;
+				WARNING_CFLAGS = "-Wthread-safety";
 			};
 			name = Release;
 		};

+ 22 - 0
SocketRocket/Internal/Utilities/SRMutex.h

@@ -0,0 +1,22 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef __attribute__((capability("mutex"))) pthread_mutex_t *SRMutex;
+
+extern SRMutex SRMutexInitRecursive(void);
+extern void SRMutexDestroy(SRMutex mutex);
+
+extern void SRMutexLock(SRMutex mutex) __attribute__((acquire_capability(mutex)));
+extern void SRMutexUnlock(SRMutex mutex) __attribute__((release_capability(mutex)));
+
+NS_ASSUME_NONNULL_END

+ 46 - 0
SocketRocket/Internal/Utilities/SRMutex.m

@@ -0,0 +1,46 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import "SRMutex.h"
+
+#import <pthread/pthread.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+SRMutex SRMutexInitRecursive(void)
+{
+    pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
+    pthread_mutexattr_t attributes;
+
+    pthread_mutexattr_init(&attributes);
+    pthread_mutexattr_settype(&attributes, PTHREAD_MUTEX_RECURSIVE);
+    pthread_mutex_init(mutex, &attributes);
+    pthread_mutexattr_destroy(&attributes);
+
+    return mutex;
+}
+
+void SRMutexDestroy(SRMutex mutex)
+{
+    pthread_mutex_destroy(mutex);
+}
+
+__attribute__((no_thread_safety_analysis))
+void SRMutexLock(SRMutex mutex)
+{
+    pthread_mutex_lock(mutex);
+}
+
+__attribute__((no_thread_safety_analysis))
+void SRMutexUnlock(SRMutex mutex)
+{
+    pthread_mutex_unlock(mutex);
+}
+
+NS_ASSUME_NONNULL_END

+ 3 - 1
SocketRocket/SRWebSocket.h

@@ -90,8 +90,10 @@ extern NSString *const SRHTTPResponseErrorKey;
 
 /**
  Current ready state of the socket. Default: `SR_CONNECTING`.
+
+ This property is Key-Value Observable and fully thread-safe.
  */
-@property (nonatomic, assign, readonly) SRReadyState readyState;
+@property (atomic, assign, readonly) SRReadyState readyState;
 
 /**
  An instance of `NSURL` that this socket connects to.

+ 41 - 7
SocketRocket/SRWebSocket.m

@@ -25,6 +25,8 @@
 #import <CoreServices/CoreServices.h>
 #endif
 
+#import <libkern/OSAtomic.h>
+
 #import "SRDelegateController.h"
 #import "SRIOConsumer.h"
 #import "SRIOConsumerPool.h"
@@ -38,6 +40,7 @@
 #import "SRHTTPConnectMessage.h"
 #import "SRRandom.h"
 #import "SRLog.h"
+#import "SRMutex.h"
 
 #if !__has_feature(objc_arc)
 #error SocketRocket must be compiled with ARC enabled
@@ -86,7 +89,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
 @interface SRWebSocket ()  <NSStreamDelegate>
 
-@property (nonatomic, assign, readwrite) SRReadyState readyState;
+@property (atomic, assign, readwrite) SRReadyState readyState;
 
 // Specifies whether SSL trust chain should NOT be evaluated.
 // By default this flag is set to NO, meaning only secure SSL connections are allowed.
@@ -99,6 +102,9 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 @end
 
 @implementation SRWebSocket {
+    SRMutex _kvoLock;
+    OSSpinLock _propertyLock;
+
     dispatch_queue_t _workQueue;
     NSMutableArray<SRIOConsumer *> *_consumers;
 
@@ -151,6 +157,8 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     SRProxyConnect *_proxyConnect;
 }
 
+@synthesize readyState = _readyState;
+
 ///--------------------------------------
 #pragma mark - Init
 ///--------------------------------------
@@ -173,6 +181,8 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
     _readyState = SR_CONNECTING;
 
+    _propertyLock = OS_SPINLOCK_INIT;
+    _kvoLock = SRMutexInitRecursive();
     _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
 
     // Going to set a specific on the queue so we can validate we're on the work queue
@@ -241,21 +251,45 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
         CFRelease(_receivedHTTPHeaders);
         _receivedHTTPHeaders = NULL;
     }
+
+    SRMutexDestroy(_kvoLock);
 }
 
 ///--------------------------------------
 #pragma mark - Accessors
 ///--------------------------------------
 
-#ifndef NDEBUG
+#pragma mark readyState
 
-- (void)setReadyState:(SRReadyState)aReadyState;
+- (void)setReadyState:(SRReadyState)readyState
 {
-    assert(aReadyState > _readyState);
-    _readyState = aReadyState;
+    @try {
+        SRMutexLock(_kvoLock);
+        if (_readyState != readyState) {
+            [self willChangeValueForKey:@"readyState"];
+            OSSpinLockLock(&_propertyLock);
+            _readyState = readyState;
+            OSSpinLockUnlock(&_propertyLock);
+            [self didChangeValueForKey:@"readyState"];
+        }
+    }
+    @finally {
+        SRMutexUnlock(_kvoLock);
+    }
 }
 
-#endif
+- (SRReadyState)readyState
+{
+    SRReadyState state = 0;
+    OSSpinLockLock(&_propertyLock);
+    state = _readyState;
+    OSSpinLockUnlock(&_propertyLock);
+    return state;
+}
+
++ (BOOL)automaticallyNotifiesObserversOfReadyState {
+    return NO;
+}
 
 ///--------------------------------------
 #pragma mark - Open / Close
@@ -264,7 +298,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 - (void)open
 {
     assert(_url);
-    NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once.");
+    NSAssert(self.readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once.");
 
     _selfRetain = self;