Skip to content

Commit be2a695

Browse files
committed
Fixing Google Code issue robbiehanson#133 - Improving thread-safety documentation, and undoing unwanted data copy.
1 parent fe662b3 commit be2a695

File tree

3 files changed

+128
-56
lines changed

3 files changed

+128
-56
lines changed

GCD/GCDAsyncSocket.h

Lines changed: 76 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,10 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
449449
* If the bufferOffset is greater than the length of the given buffer,
450450
* the method will do nothing, and the delegate will not be called.
451451
*
452-
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
452+
* If you pass a buffer, you must not alter it in any way while the socket is using it.
453453
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
454-
* That is, it will reference the bytes that were appended to the given buffer.
454+
* That is, it will reference the bytes that were appended to the given buffer via
455+
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
455456
**/
456457
- (void)readDataWithTimeout:(NSTimeInterval)timeout
457458
buffer:(NSMutableData *)buffer
@@ -471,9 +472,10 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
471472
* If the bufferOffset is greater than the length of the given buffer,
472473
* the method will do nothing, and the delegate will not be called.
473474
*
474-
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
475+
* If you pass a buffer, you must not alter it in any way while the socket is using it.
475476
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
476-
* That is, it will reference the bytes that were appended to the given buffer.
477+
* That is, it will reference the bytes that were appended to the given buffer via
478+
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
477479
**/
478480
- (void)readDataWithTimeout:(NSTimeInterval)timeout
479481
buffer:(NSMutableData *)buffer
@@ -504,7 +506,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
504506
*
505507
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
506508
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
507-
* That is, it will reference the bytes that were appended to the given buffer.
509+
* That is, it will reference the bytes that were appended to the given buffer via
510+
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
508511
**/
509512
- (void)readDataToLength:(NSUInteger)length
510513
withTimeout:(NSTimeInterval)timeout
@@ -518,11 +521,20 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
518521
* If the timeout value is negative, the read operation will not use a timeout.
519522
*
520523
* If you pass nil or zero-length data as the "data" parameter,
521-
* the method will do nothing, and the delegate will not be called.
524+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
522525
*
523526
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
524-
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
525-
* a character, the read will prematurely end.
527+
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
528+
* part of the data between separators.
529+
* For example, imagine you want to send several small documents over a socket.
530+
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
531+
* In this particular example, it would be better to use a protocol similar to HTTP with
532+
* a header that includes the length of the document.
533+
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
534+
*
535+
* The given data (separator) parameter should be immutable.
536+
* For performance reasons, the socket will retain it, not copy it.
537+
* So if it is immutable, don't modify it while the socket is using it.
526538
**/
527539
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
528540

@@ -535,15 +547,25 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
535547
* If the buffer if nil, a buffer will automatically be created for you.
536548
*
537549
* If the bufferOffset is greater than the length of the given buffer,
538-
* the method will do nothing, and the delegate will not be called.
550+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
539551
*
540-
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
552+
* If you pass a buffer, you must not alter it in any way while the socket is using it.
541553
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
542-
* That is, it will reference the bytes that were appended to the given buffer.
554+
* That is, it will reference the bytes that were appended to the given buffer via
555+
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
543556
*
544557
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
545-
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
546-
* a character, the read will prematurely end.
558+
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
559+
* part of the data between separators.
560+
* For example, imagine you want to send several small documents over a socket.
561+
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
562+
* In this particular example, it would be better to use a protocol similar to HTTP with
563+
* a header that includes the length of the document.
564+
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
565+
*
566+
* The given data (separator) parameter should be immutable.
567+
* For performance reasons, the socket will retain it, not copy it.
568+
* So if it is immutable, don't modify it while the socket is using it.
547569
**/
548570
- (void)readDataToData:(NSData *)data
549571
withTimeout:(NSTimeInterval)timeout
@@ -562,13 +584,22 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
562584
* The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
563585
*
564586
* If you pass nil or zero-length data as the "data" parameter,
565-
* the method will do nothing, and the delegate will not be called.
587+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
566588
* If you pass a maxLength parameter that is less than the length of the data parameter,
567-
* the method will do nothing, and the delegate will not be called.
589+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
568590
*
569591
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
570-
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
571-
* a character, the read will prematurely end.
592+
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
593+
* part of the data between separators.
594+
* For example, imagine you want to send several small documents over a socket.
595+
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
596+
* In this particular example, it would be better to use a protocol similar to HTTP with
597+
* a header that includes the length of the document.
598+
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
599+
*
600+
* The given data (separator) parameter should be immutable.
601+
* For performance reasons, the socket will retain it, not copy it.
602+
* So if it is immutable, don't modify it while the socket is using it.
572603
**/
573604
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
574605

@@ -585,18 +616,28 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
585616
* it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
586617
* The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
587618
*
588-
* If you pass a maxLength parameter that is less than the length of the data parameter,
589-
* the method will do nothing, and the delegate will not be called.
619+
* If you pass a maxLength parameter that is less than the length of the data (separator) parameter,
620+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
590621
* If the bufferOffset is greater than the length of the given buffer,
591-
* the method will do nothing, and the delegate will not be called.
622+
* the method will do nothing (except maybe print a warning), and the delegate will not be called.
592623
*
593-
* If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
624+
* If you pass a buffer, you must not alter it in any way while the socket is using it.
594625
* After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
595-
* That is, it will reference the bytes that were appended to the given buffer.
626+
* That is, it will reference the bytes that were appended to the given buffer via
627+
* the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
596628
*
597629
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
598-
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for
599-
* a character, the read will prematurely end.
630+
* If you're developing your own custom protocol, be sure your separator can not occur naturally as
631+
* part of the data between separators.
632+
* For example, imagine you want to send several small documents over a socket.
633+
* Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
634+
* In this particular example, it would be better to use a protocol similar to HTTP with
635+
* a header that includes the length of the document.
636+
* Also be careful that your separator cannot occur naturally as part of the encoding for a character.
637+
*
638+
* The given data (separator) parameter should be immutable.
639+
* For performance reasons, the socket will retain it, not copy it.
640+
* So if it is immutable, don't modify it while the socket is using it.
600641
**/
601642
- (void)readDataToData:(NSData *)data
602643
withTimeout:(NSTimeInterval)timeout
@@ -612,6 +653,17 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
612653
*
613654
* If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
614655
* If the timeout value is negative, the write operation will not use a timeout.
656+
*
657+
* Thread-Safety Note:
658+
* If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
659+
* the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
660+
* socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
661+
* This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
662+
* This is for performance reasons. Often times, if NSMutableData is passed, it is because
663+
* a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
664+
* If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
665+
* completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
666+
* when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
615667
**/
616668
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
617669

GCD/GCDAsyncSocket.m

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -741,7 +741,7 @@ - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
741741
{
742742
if((self = [super init]))
743743
{
744-
buffer = [d copy]; // If d is immutable, this is just a retain
744+
buffer = [d retain]; // Retain not copy. For performance as documented in header file.
745745
bytesDone = 0;
746746
timeout = t;
747747
tag = i;
@@ -1250,10 +1250,13 @@ - (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
12501250
return [self acceptOnInterface:nil port:port error:errPtr];
12511251
}
12521252

1253-
- (BOOL)acceptOnInterface:(NSString *)interface port:(uint16_t)port error:(NSError **)errPtr
1253+
- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
12541254
{
12551255
LogTrace();
12561256

1257+
// Just in-case interface parameter is immutable.
1258+
NSString *interface = [[inInterface copy] autorelease];
1259+
12571260
__block BOOL result = NO;
12581261
__block NSError *err = nil;
12591262

@@ -1783,14 +1786,18 @@ - (BOOL)connectToHost:(NSString *)host
17831786
return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
17841787
}
17851788

1786-
- (BOOL)connectToHost:(NSString *)host
1789+
- (BOOL)connectToHost:(NSString *)inHost
17871790
onPort:(uint16_t)port
1788-
viaInterface:(NSString *)interface
1791+
viaInterface:(NSString *)inInterface
17891792
withTimeout:(NSTimeInterval)timeout
17901793
error:(NSError **)errPtr
17911794
{
17921795
LogTrace();
17931796

1797+
// Just in case immutable objects were passed
1798+
NSString *host = [[inHost copy] autorelease];
1799+
NSString *interface = [[inInterface copy] autorelease];
1800+
17941801
__block BOOL result = NO;
17951802
__block NSError *err = nil;
17961803

@@ -1872,13 +1879,17 @@ - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeou
18721879
return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
18731880
}
18741881

1875-
- (BOOL)connectToAddress:(NSData *)remoteAddr
1876-
viaInterface:(NSString *)interface
1882+
- (BOOL)connectToAddress:(NSData *)inRemoteAddr
1883+
viaInterface:(NSString *)inInterface
18771884
withTimeout:(NSTimeInterval)timeout
18781885
error:(NSError **)errPtr
18791886
{
18801887
LogTrace();
18811888

1889+
// Just in case immutable objects were passed
1890+
NSData *remoteAddr = [[inRemoteAddr copy] autorelease];
1891+
NSString *interface = [[inInterface copy] autorelease];
1892+
18821893
__block BOOL result = NO;
18831894
__block NSError *err = nil;
18841895

@@ -3629,7 +3640,10 @@ - (void)readDataWithTimeout:(NSTimeInterval)timeout
36293640
maxLength:(NSUInteger)length
36303641
tag:(long)tag
36313642
{
3632-
if (offset > [buffer length]) return;
3643+
if (offset > [buffer length]) {
3644+
LogWarn(@"Cannot read: offset > [buffer length]");
3645+
return;
3646+
}
36333647

36343648
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
36353649
startOffset:offset
@@ -3669,8 +3683,14 @@ - (void)readDataToLength:(NSUInteger)length
36693683
bufferOffset:(NSUInteger)offset
36703684
tag:(long)tag
36713685
{
3672-
if (length == 0) return;
3673-
if (offset > [buffer length]) return;
3686+
if (length == 0) {
3687+
LogWarn(@"Cannot read: length == 0");
3688+
return;
3689+
}
3690+
if (offset > [buffer length]) {
3691+
LogWarn(@"Cannot read: offset > [buffer length]");
3692+
return;
3693+
}
36743694

36753695
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
36763696
startOffset:offset
@@ -3722,16 +3742,25 @@ - (void)readDataToData:(NSData *)data
37223742
withTimeout:(NSTimeInterval)timeout
37233743
buffer:(NSMutableData *)buffer
37243744
bufferOffset:(NSUInteger)offset
3725-
maxLength:(NSUInteger)length
3745+
maxLength:(NSUInteger)maxLength
37263746
tag:(long)tag
37273747
{
3728-
if ([data length] == 0) return;
3729-
if (offset > [buffer length]) return;
3730-
if (length > 0 && length < [data length]) return;
3748+
if ([data length] == 0) {
3749+
LogWarn(@"Cannot read: [data length] == 0");
3750+
return;
3751+
}
3752+
if (offset > [buffer length]) {
3753+
LogWarn(@"Cannot read: offset > [buffer length]");
3754+
return;
3755+
}
3756+
if (maxLength > 0 && maxLength < [data length]) {
3757+
LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
3758+
return;
3759+
}
37313760

37323761
GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
37333762
startOffset:offset
3734-
maxLength:length
3763+
maxLength:maxLength
37353764
timeout:timeout
37363765
readLength:0
37373766
terminator:data
@@ -6659,8 +6688,7 @@ - (int)socketFD
66596688
{
66606689
if (dispatch_get_current_queue() != socketQueue)
66616690
{
6662-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6663-
THIS_FILE, THIS_METHOD);
6691+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
66646692
return SOCKET_NULL;
66656693
}
66666694

@@ -6674,8 +6702,7 @@ - (int)socket4FD
66746702
{
66756703
if (dispatch_get_current_queue() != socketQueue)
66766704
{
6677-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6678-
THIS_FILE, THIS_METHOD);
6705+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
66796706
return SOCKET_NULL;
66806707
}
66816708

@@ -6686,8 +6713,7 @@ - (int)socket6FD
66866713
{
66876714
if (dispatch_get_current_queue() != socketQueue)
66886715
{
6689-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6690-
THIS_FILE, THIS_METHOD);
6716+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
66916717
return SOCKET_NULL;
66926718
}
66936719

@@ -6700,8 +6726,7 @@ - (CFReadStreamRef)readStream
67006726
{
67016727
if (dispatch_get_current_queue() != socketQueue)
67026728
{
6703-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6704-
THIS_FILE, THIS_METHOD);
6729+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
67056730
return NULL;
67066731
}
67076732

@@ -6715,8 +6740,7 @@ - (CFWriteStreamRef)writeStream
67156740
{
67166741
if (dispatch_get_current_queue() != socketQueue)
67176742
{
6718-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6719-
THIS_FILE, THIS_METHOD);
6743+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
67206744
return NULL;
67216745
}
67226746

@@ -6743,7 +6767,6 @@ - (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
67436767

67446768
if (!r1 || !r2)
67456769
{
6746-
LogError(@"Error setting voip type");
67476770
return NO;
67486771
}
67496772

@@ -6764,8 +6787,7 @@ - (BOOL)enableBackgroundingOnSocket
67646787

67656788
if (dispatch_get_current_queue() != socketQueue)
67666789
{
6767-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6768-
THIS_FILE, THIS_METHOD);
6790+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
67696791
return NO;
67706792
}
67716793

@@ -6782,8 +6804,7 @@ - (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
67826804

67836805
if (dispatch_get_current_queue() != socketQueue)
67846806
{
6785-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6786-
THIS_FILE, THIS_METHOD);
6807+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
67876808
return NO;
67886809
}
67896810

@@ -6796,8 +6817,7 @@ - (SSLContextRef)sslContext
67966817
{
67976818
if (dispatch_get_current_queue() != socketQueue)
67986819
{
6799-
LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
6800-
THIS_FILE, THIS_METHOD);
6820+
LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
68016821
return NULL;
68026822
}
68036823

GCD/Xcode/SimpleHTTPClient/Desktop/SimpleHTTPClient.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@
178178
DC80C9F213C3978F00D29D06 /* Project object */ = {
179179
isa = PBXProject;
180180
attributes = {
181-
LastUpgradeCheck = 0420;
181+
LastUpgradeCheck = 0430;
182182
};
183183
buildConfigurationList = DC80C9F513C3978F00D29D06 /* Build configuration list for PBXProject "SimpleHTTPClient" */;
184184
compatibilityVersion = "Xcode 3.2";

0 commit comments

Comments
 (0)