@@ -120,8 +120,9 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address,
120
120
*
121
121
* The socket queue is optional.
122
122
* If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
123
- * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
124
- *
123
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
124
+ * then please see the discussion for the method markSocketQueueTargetQueue.
125
+ *
125
126
* The delegate queue and socket queue can optionally be the same.
126
127
**/
127
128
- (id )init ;
@@ -766,6 +767,80 @@ typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address,
766
767
- (void )closeAfterSending ;
767
768
768
769
#pragma mark Advanced
770
+ /* *
771
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
772
+ * In most cases, the instance creates this queue itself.
773
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
774
+ * This allows for some advanced options such as controlling socket priority via target queues.
775
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
776
+ *
777
+ * For example, imagine there are 2 queues:
778
+ * dispatch_queue_t socketQueue;
779
+ * dispatch_queue_t socketTargetQueue;
780
+ *
781
+ * If you do this (pseudo-code):
782
+ * socketQueue.targetQueue = socketTargetQueue;
783
+ *
784
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
785
+ * This is fine and works great in most situations.
786
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
787
+ * you could potentially get deadlock. Imagine the following code:
788
+ *
789
+ * - (BOOL)socketHasSomething
790
+ * {
791
+ * __block BOOL result = NO;
792
+ * dispatch_block_t block = ^{
793
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
794
+ * }
795
+ * if (is_executing_on_queue(socketQueue))
796
+ * block();
797
+ * else
798
+ * dispatch_sync(socketQueue, block);
799
+ *
800
+ * return result;
801
+ * }
802
+ *
803
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
804
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
805
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
806
+ * If we had this information, we could easily avoid deadlock.
807
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
808
+ *
809
+ * IF you pass a socketQueue via the init method,
810
+ * AND you've configured the passed socketQueue with a targetQueue,
811
+ * THEN you should pass the end queue in the target hierarchy.
812
+ *
813
+ * For example, consider the following queue hierarchy:
814
+ * socketQueue -> ipQueue -> moduleQueue
815
+ *
816
+ * This example demonstrates priority shaping within some server.
817
+ * All incoming client connections from the same IP address are executed on the same target queue.
818
+ * And all connections for a particular module are executed on the same target queue.
819
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
820
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
821
+ *
822
+ * Here's how you would accomplish something like that:
823
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
824
+ * {
825
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
826
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
827
+ *
828
+ * dispatch_set_target_queue(socketQueue, ipQueue);
829
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
830
+ *
831
+ * return socketQueue;
832
+ * }
833
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
834
+ * {
835
+ * [clientConnections addObject:newSocket];
836
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
837
+ * }
838
+ *
839
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
840
+ * This is often NOT the case, as such queues are used solely for execution shaping.
841
+ **/
842
+ - (void )markSocketQueueTargetQueue : (dispatch_queue_t )socketQueuesPreConfiguredTargetQueue ;
843
+ - (void )unmarkSocketQueueTargetQueue : (dispatch_queue_t )socketQueuesPreviouslyConfiguredTargetQueue ;
769
844
770
845
/* *
771
846
* It's not thread-safe to access certain variables from outside the socket's internal queue.
0 commit comments